From 1f588913b03e07fd35af4d52c5691ef40bde96b4 Mon Sep 17 00:00:00 2001 From: chenyisong Date: Fri, 15 Aug 2025 17:45:59 +0800 Subject: [PATCH 1/5] add tuya_p2p component --- src/Kconfig | 1 + src/tuya_cloud_service/cloud/tuya_iot.c | 15 +- src/tuya_cloud_service/cloud/tuya_iot.h | 1 + src/tuya_p2p/CMakeLists.txt | 23 + src/tuya_p2p/Kconfig | 6 + src/tuya_p2p/base_ice/CMakeLists.txt | 53 + .../base_ice/include/tuya_common_types.h | 93 + .../base_ice/include/tuya_media_service_rtc.h | 435 +++ src/tuya_p2p/base_ice/src/ikcp.c | 1385 +++++++ src/tuya_p2p/base_ice/src/ikcp.h | 406 ++ src/tuya_p2p/base_ice/src/pj_ice.c | 581 +++ src/tuya_p2p/base_ice/src/pj_ice.h | 40 + src/tuya_p2p/base_ice/src/pj_sdp.c | 48 + src/tuya_p2p/base_ice/src/pj_sdp.h | 11 + src/tuya_p2p/base_ice/src/pj_sync_condition.c | 56 + src/tuya_p2p/base_ice/src/pj_sync_condition.h | 18 + src/tuya_p2p/base_ice/src/queue.h | 99 + src/tuya_p2p/base_ice/src/tuya_error.c | 63 + src/tuya_p2p/base_ice/src/tuya_error.h | 82 + src/tuya_p2p/base_ice/src/tuya_log.h | 20 + .../base_ice/src/tuya_media_service_rtc.c | 2077 +++++++++++ src/tuya_p2p/base_ice/src/tuya_misc.c | 417 +++ src/tuya_p2p/base_ice/src/tuya_misc.h | 47 + src/tuya_p2p/base_ice/src/tuya_rtp.h | 151 + src/tuya_p2p/base_ice/src/tuya_sdp.c | 1290 +++++++ src/tuya_p2p/base_ice/src/tuya_sdp.h | 105 + src/tuya_p2p/lib_rtp/CMakeLists.txt | 56 + src/tuya_p2p/lib_rtp/include/rtcp-header.h | 398 ++ src/tuya_p2p/lib_rtp/include/rtp-demuxer.h | 44 + src/tuya_p2p/lib_rtp/include/rtp-ext.h | 349 ++ .../lib_rtp/include/rtp-header-extension.h | 312 ++ src/tuya_p2p/lib_rtp/include/rtp-header.h | 28 + src/tuya_p2p/lib_rtp/include/rtp-internal.h | 67 + .../lib_rtp/include/rtp-member-list.h | 17 + src/tuya_p2p/lib_rtp/include/rtp-member.h | 42 + src/tuya_p2p/lib_rtp/include/rtp-packet.h | 24 + src/tuya_p2p/lib_rtp/include/rtp-param.h | 15 + src/tuya_p2p/lib_rtp/include/rtp-payload.h | 73 + src/tuya_p2p/lib_rtp/include/rtp-profile.h | 133 + src/tuya_p2p/lib_rtp/include/rtp-queue.h | 35 + src/tuya_p2p/lib_rtp/include/rtp-util.h | 67 + src/tuya_p2p/lib_rtp/include/rtp.h | 136 + src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c | 330 ++ src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c | 377 ++ .../lib_rtp/payload/rtp-h264-bitstream.c | 130 + src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c | 194 + .../lib_rtp/payload/rtp-h264-unpack.c | 329 ++ src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c | 187 + .../lib_rtp/payload/rtp-h265-unpack.c | 280 ++ src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c | 189 + .../lib_rtp/payload/rtp-h266-unpack.c | 279 ++ .../lib_rtp/payload/rtp-mp4a-latm-pack.c | 137 + .../lib_rtp/payload/rtp-mp4a-latm-unpack.c | 84 + .../lib_rtp/payload/rtp-mp4v-es-pack.c | 121 + .../lib_rtp/payload/rtp-mp4v-es-unpack.c | 50 + .../lib_rtp/payload/rtp-mpeg1or2es-pack.c | 335 ++ .../lib_rtp/payload/rtp-mpeg1or2es-unpack.c | 63 + .../lib_rtp/payload/rtp-mpeg4-generic-pack.c | 145 + .../payload/rtp-mpeg4-generic-unpack.c | 95 + src/tuya_p2p/lib_rtp/payload/rtp-pack.c | 104 + .../lib_rtp/payload/rtp-payload-helper.c | 130 + .../lib_rtp/payload/rtp-payload-helper.h | 29 + .../lib_rtp/payload/rtp-payload-internal.h | 76 + src/tuya_p2p/lib_rtp/payload/rtp-payload.c | 223 ++ src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c | 71 + src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c | 126 + src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c | 51 + src/tuya_p2p/lib_rtp/payload/rtp-unpack.c | 38 + src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c | 116 + src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c | 162 + src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c | 121 + src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c | 204 ++ .../lib_rtp/rtpext/rtp-ext-abs-send-time.c | 44 + .../rtpext/rtp-ext-absolute-capture-time.c | 107 + .../lib_rtp/rtpext/rtp-ext-color-space.c | 124 + .../lib_rtp/rtpext/rtp-ext-csrc-audio-level.c | 57 + .../lib_rtp/rtpext/rtp-ext-frame-marking.c | 67 + .../lib_rtp/rtpext/rtp-ext-inband-cn.c | 84 + .../lib_rtp/rtpext/rtp-ext-playout-delay.c | 98 + src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c | 64 + .../lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c | 52 + src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c | 35 + .../rtpext/rtp-ext-transport-wide-cc.c | 69 + .../rtpext/rtp-ext-video-content-type.c | 40 + .../rtpext/rtp-ext-video-frame-tracking-id.c | 33 + .../rtpext/rtp-ext-video-layers-allocation.c | 85 + .../rtpext/rtp-ext-video-orientation.c | 89 + .../lib_rtp/rtpext/rtp-ext-video-timing.c | 81 + src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c | 260 ++ src/tuya_p2p/lib_rtp/src/rtcp-app.c | 54 + src/tuya_p2p/lib_rtp/src/rtcp-bye.c | 64 + src/tuya_p2p/lib_rtp/src/rtcp-interval.c | 96 + src/tuya_p2p/lib_rtp/src/rtcp-psfb.c | 845 +++++ src/tuya_p2p/lib_rtp/src/rtcp-rr.c | 90 + src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c | 1187 ++++++ src/tuya_p2p/lib_rtp/src/rtcp-sdec.c | 123 + src/tuya_p2p/lib_rtp/src/rtcp-sr.c | 168 + src/tuya_p2p/lib_rtp/src/rtcp-xr.c | 753 ++++ src/tuya_p2p/lib_rtp/src/rtcp.c | 235 ++ src/tuya_p2p/lib_rtp/src/rtp-demuxer.c | 247 ++ src/tuya_p2p/lib_rtp/src/rtp-member-list.c | 129 + src/tuya_p2p/lib_rtp/src/rtp-member.c | 68 + src/tuya_p2p/lib_rtp/src/rtp-packet.c | 138 + src/tuya_p2p/lib_rtp/src/rtp-profile.c | 68 + src/tuya_p2p/lib_rtp/src/rtp-queue.c | 467 +++ src/tuya_p2p/lib_rtp/src/rtp-ssrc.c | 54 + src/tuya_p2p/lib_rtp/src/rtp-time.c | 88 + src/tuya_p2p/lib_rtp/src/rtp.c | 180 + src/tuya_p2p/pjproject/CMakeLists.txt | 68 + .../pjproject/pjlib-util/include/pjlib-util.h | 74 + .../pjlib-util/include/pjlib-util/base64.h | 83 + .../pjlib-util/include/pjlib-util/cli.h | 506 +++ .../include/pjlib-util/cli_console.h | 107 + .../pjlib-util/include/pjlib-util/cli_imp.h | 194 + .../include/pjlib-util/cli_telnet.h | 160 + .../pjlib-util/include/pjlib-util/config.h | 353 ++ .../pjlib-util/include/pjlib-util/crc32.h | 87 + .../pjlib-util/include/pjlib-util/dns.h | 417 +++ .../include/pjlib-util/dns_server.h | 103 + .../pjlib-util/include/pjlib-util/errno.h | 445 +++ .../pjlib-util/include/pjlib-util/getopt.h | 139 + .../pjlib-util/include/pjlib-util/hmac_md5.h | 98 + .../pjlib-util/include/pjlib-util/hmac_sha1.h | 95 + .../include/pjlib-util/http_client.h | 467 +++ .../pjlib-util/include/pjlib-util/json.h | 210 ++ .../pjlib-util/include/pjlib-util/md5.h | 68 + .../pjlib-util/include/pjlib-util/pcap.h | 179 + .../pjlib-util/include/pjlib-util/resolver.h | 470 +++ .../pjlib-util/include/pjlib-util/scanner.h | 503 +++ .../include/pjlib-util/scanner_cis_bitwise.h | 91 + .../include/pjlib-util/scanner_cis_uint.h | 79 + .../pjlib-util/include/pjlib-util/sha1.h | 71 + .../include/pjlib-util/srv_resolver.h | 203 + .../pjlib-util/include/pjlib-util/string.h | 97 + .../include/pjlib-util/stun_simple.h | 261 ++ .../pjlib-util/include/pjlib-util/types.h | 90 + .../pjlib-util/include/pjlib-util/xml.h | 225 ++ .../pjlib-util/src/pjlib-util/base64.c | 155 + .../pjproject/pjlib-util/src/pjlib-util/cli.c | 1225 +++++++ .../pjlib-util/src/pjlib-util/cli_console.c | 512 +++ .../pjlib-util/src/pjlib-util/cli_telnet.c | 1859 ++++++++++ .../pjlib-util/src/pjlib-util/crc32.c | 179 + .../pjproject/pjlib-util/src/pjlib-util/dns.c | 714 ++++ .../pjlib-util/src/pjlib-util/dns_dump.c | 169 + .../pjlib-util/src/pjlib-util/dns_server.c | 511 +++ .../pjlib-util/src/pjlib-util/errno.c | 171 + .../pjlib-util/src/pjlib-util/getopt.c | 644 ++++ .../pjlib-util/src/pjlib-util/hmac_md5.c | 88 + .../pjlib-util/src/pjlib-util/hmac_sha1.c | 89 + .../pjlib-util/src/pjlib-util/http_client.c | 1507 ++++++++ .../pjlib-util/src/pjlib-util/json.c | 589 +++ .../pjproject/pjlib-util/src/pjlib-util/md5.c | 262 ++ .../pjlib-util/src/pjlib-util/pcap.c | 367 ++ .../pjlib-util/src/pjlib-util/resolver.c | 1761 +++++++++ .../pjlib-util/src/pjlib-util/scanner.c | 603 +++ .../src/pjlib-util/scanner_cis_bitwise.c | 69 + .../src/pjlib-util/scanner_cis_uint.c | 42 + .../pjlib-util/src/pjlib-util/sha1.c | 322 ++ .../pjlib-util/src/pjlib-util/srv_resolver.c | 761 ++++ .../pjlib-util/src/pjlib-util/string.c | 100 + .../pjlib-util/src/pjlib-util/stun_simple.c | 127 + .../src/pjlib-util/stun_simple_client.c | 338 ++ .../pjlib-util/src/pjlib-util/symbols.c | 80 + .../pjproject/pjlib-util/src/pjlib-util/xml.c | 519 +++ .../pjproject/pjlib/include/pj/activesock.h | 536 +++ .../pjproject/pjlib/include/pj/addr_resolv.h | 175 + .../pjproject/pjlib/include/pj/array.h | 84 + .../pjproject/pjlib/include/pj/assert.h | 86 + .../pjlib/include/pj/compat/assert.h | 35 + .../pjlib/include/pj/compat/cc_armcc.h | 56 + .../pjlib/include/pj/compat/cc_gcc.h | 74 + .../pjlib/include/pj/compat/cc_gcce.h | 51 + .../pjproject/pjlib/include/pj/compat/ctype.h | 42 + .../pjproject/pjlib/include/pj/compat/errno.h | 41 + .../pjlib/include/pj/compat/high_precision.h | 84 + .../pjlib/include/pj/compat/limits.h | 66 + .../pjlib/include/pj/compat/m_auto.h.in | 59 + .../pjlib/include/pj/compat/m_x86_64.h | 33 + .../pjlib/include/pj/compat/malloc.h | 33 + .../pjlib/include/pj/compat/os_auto.h.in | 243 ++ .../pjlib/include/pj/compat/os_linux.h | 130 + .../pjproject/pjlib/include/pj/compat/rand.h | 53 + .../pjlib/include/pj/compat/setjmp.h | 47 + .../pjlib/include/pj/compat/size_t.h | 30 + .../pjlib/include/pj/compat/socket.h | 219 ++ .../pjlib/include/pj/compat/stdarg.h | 31 + .../pjlib/include/pj/compat/stdfileio.h | 31 + .../pjlib/include/pj/compat/string.h | 146 + .../pjproject/pjlib/include/pj/compat/time.h | 39 + .../pjproject/pjlib/include/pj/config.h | 1393 +++++++ .../pjproject/pjlib/include/pj/config_site.h | 15 + .../pjlib/include/pj/config_site_sample.h | 476 +++ .../pjlib/include/pj/config_site_test.h | 11 + .../pjproject/pjlib/include/pj/ctype.h | 205 ++ .../pjproject/pjlib/include/pj/doxygen.h | 986 +++++ .../pjproject/pjlib/include/pj/errno.h | 565 +++ .../pjproject/pjlib/include/pj/except.h | 421 +++ .../pjproject/pjlib/include/pj/fifobuf.h | 41 + .../pjproject/pjlib/include/pj/file_access.h | 102 + .../pjproject/pjlib/include/pj/file_io.h | 167 + .../pjproject/pjlib/include/pj/guid.h | 113 + .../pjproject/pjlib/include/pj/hash.h | 228 ++ .../pjproject/pjlib/include/pj/ioqueue.h | 869 +++++ .../pjproject/pjlib/include/pj/ip_helper.h | 137 + .../pjproject/pjlib/include/pj/limits.h | 50 + .../pjproject/pjlib/include/pj/list.h | 245 ++ .../pjproject/pjlib/include/pj/list_i.h | 113 + .../pjproject/pjlib/include/pj/lock.h | 411 +++ src/tuya_p2p/pjproject/pjlib/include/pj/log.h | 476 +++ .../pjproject/pjlib/include/pj/math.h | 203 + src/tuya_p2p/pjproject/pjlib/include/pj/os.h | 1408 +++++++ .../pjproject/pjlib/include/pj/pool.h | 877 +++++ .../pjproject/pjlib/include/pj/pool_alt.h | 186 + .../pjproject/pjlib/include/pj/pool_buf.h | 101 + .../pjproject/pjlib/include/pj/pool_i.h | 127 + .../pjproject/pjlib/include/pj/rand.h | 59 + .../pjproject/pjlib/include/pj/rbtree.h | 195 + .../pjproject/pjlib/include/pj/sock.h | 1466 ++++++++ .../pjproject/pjlib/include/pj/sock_qos.h | 401 ++ .../pjproject/pjlib/include/pj/sock_select.h | 140 + .../pjproject/pjlib/include/pj/ssl_sock.h | 1384 +++++++ .../pjproject/pjlib/include/pj/string.h | 822 +++++ .../pjproject/pjlib/include/pj/string_i.h | 386 ++ .../pjproject/pjlib/include/pj/timer.h | 353 ++ .../pjproject/pjlib/include/pj/types.h | 546 +++ .../pjproject/pjlib/include/pj/unicode.h | 126 + src/tuya_p2p/pjproject/pjlib/include/pjlib.h | 61 + .../pjproject/pjlib/src/pj/activesock.c | 829 +++++ .../pjproject/pjlib/src/pj/addr_resolv_sock.c | 308 ++ src/tuya_p2p/pjproject/pjlib/src/pj/array.c | 58 + .../pjlib/src/pj/compat/sigjmp.c.old | 39 + .../pjlib/src/pj/compat/string.c.old | 44 + .../pjlib/src/pj/compat/string_compat.c.old | 82 + src/tuya_p2p/pjproject/pjlib/src/pj/config.c | 82 + src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c | 26 + src/tuya_p2p/pjproject/pjlib/src/pj/errno.c | 298 ++ src/tuya_p2p/pjproject/pjlib/src/pj/except.c | 172 + src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c | 179 + .../pjlib/src/pj/file_access_unistd.c | 107 + .../pjproject/pjlib/src/pj/file_io_ansi.c | 167 + src/tuya_p2p/pjproject/pjlib/src/pj/guid.c | 47 + .../pjproject/pjlib/src/pj/guid_simple.c | 79 + src/tuya_p2p/pjproject/pjlib/src/pj/hash.c | 326 ++ .../pjlib/src/pj/ioqueue_common_abs.c | 1318 +++++++ .../pjlib/src/pj/ioqueue_common_abs.h | 128 + .../pjproject/pjlib/src/pj/ioqueue_epoll.c | 1019 ++++++ .../pjproject/pjlib/src/pj/ioqueue_select.c | 1061 ++++++ .../pjlib/src/pj/ip_helper_generic.c | 573 +++ src/tuya_p2p/pjproject/pjlib/src/pj/list.c | 23 + src/tuya_p2p/pjproject/pjlib/src/pj/lock.c | 706 ++++ src/tuya_p2p/pjproject/pjlib/src/pj/log.c | 546 +++ .../pjlib/src/pj/log_writer_stdout.c | 53 + .../pjproject/pjlib/src/pj/os_core_unix.c | 2055 +++++++++++ .../pjproject/pjlib/src/pj/os_error_unix.c | 65 + src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c | 323 ++ .../pjproject/pjlib/src/pj/os_info_iphone.m | 52 + .../pjproject/pjlib/src/pj/os_rwmutex.c | 157 + .../pjproject/pjlib/src/pj/os_time_bsd.c | 34 + .../pjproject/pjlib/src/pj/os_time_common.c | 109 + .../pjproject/pjlib/src/pj/os_time_unix.c | 45 + .../pjlib/src/pj/os_timestamp_common.c | 200 + .../pjlib/src/pj/os_timestamp_posix.c | 333 ++ src/tuya_p2p/pjproject/pjlib/src/pj/pool.c | 281 ++ .../pjproject/pjlib/src/pj/pool_buf.c | 108 + .../pjproject/pjlib/src/pj/pool_caching.c | 306 ++ .../pjproject/pjlib/src/pj/pool_dbg.c | 191 + .../pjlib/src/pj/pool_policy_malloc.c | 95 + .../pjproject/pjlib/src/pj/pool_signature.h | 65 + src/tuya_p2p/pjproject/pjlib/src/pj/rand.c | 33 + src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c | 410 +++ .../pjproject/pjlib/src/pj/sock_bsd.c | 833 +++++ .../pjproject/pjlib/src/pj/sock_common.c | 1432 ++++++++ .../pjproject/pjlib/src/pj/sock_qos_bsd.c | 153 + .../pjproject/pjlib/src/pj/sock_qos_common.c | 133 + .../pjproject/pjlib/src/pj/sock_qos_dummy.c | 69 + .../pjproject/pjlib/src/pj/sock_select.c | 103 + .../pjproject/pjlib/src/pj/ssl_sock_apple.m | 2211 +++++++++++ .../pjproject/pjlib/src/pj/ssl_sock_common.c | 179 + .../pjproject/pjlib/src/pj/ssl_sock_dump.c | 129 + .../pjproject/pjlib/src/pj/ssl_sock_gtls.c | 1143 ++++++ .../pjlib/src/pj/ssl_sock_imp_common.c | 2081 +++++++++++ .../pjlib/src/pj/ssl_sock_imp_common.h | 261 ++ .../pjproject/pjlib/src/pj/ssl_sock_ossl.c | 2371 ++++++++++++ src/tuya_p2p/pjproject/pjlib/src/pj/string.c | 491 +++ src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c | 343 ++ src/tuya_p2p/pjproject/pjlib/src/pj/timer.c | 902 +++++ src/tuya_p2p/pjproject/pjlib/src/pj/types.c | 44 + .../pjproject/pjmedia/include/pjmedia.h | 80 + .../pjmedia/include/pjmedia/config.h | 1587 ++++++++ .../pjproject/pjmedia/include/pjmedia/errno.h | 675 ++++ .../pjproject/pjmedia/include/pjmedia/sdp.h | 715 ++++ .../pjmedia/include/pjmedia/sdp_neg.h | 762 ++++ .../pjproject/pjmedia/include/pjmedia/types.h | 316 ++ .../pjproject/pjmedia/src/pjmedia/sdp.c | 1655 +++++++++ .../pjproject/pjmedia/src/pjmedia/sdp_cmp.c | 284 ++ .../pjproject/pjmedia/src/pjmedia/sdp_neg.c | 1600 ++++++++ .../pjproject/pjnath/include/pjnath.h | 45 + .../pjproject/pjnath/include/pjnath/config.h | 555 +++ .../pjproject/pjnath/include/pjnath/errno.h | 218 ++ .../pjnath/include/pjnath/ice_session.h | 1034 ++++++ .../pjnath/include/pjnath/ice_strans.h | 1037 ++++++ .../pjnath/include/pjnath/nat_detect.h | 216 ++ .../pjnath/include/pjnath/stun_auth.h | 406 ++ .../pjnath/include/pjnath/stun_config.h | 122 + .../pjnath/include/pjnath/stun_msg.h | 1697 +++++++++ .../pjnath/include/pjnath/stun_session.h | 697 ++++ .../pjnath/include/pjnath/stun_sock.h | 456 +++ .../pjnath/include/pjnath/stun_transaction.h | 262 ++ .../pjnath/include/pjnath/turn_session.h | 862 +++++ .../pjnath/include/pjnath/turn_sock.h | 616 ++++ .../pjproject/pjnath/include/pjnath/types.h | 69 + .../pjproject/pjnath/include/pjnath/upnp.h | 137 + .../pjproject/pjnath/src/pjnath/errno.c | 187 + .../pjproject/pjnath/src/pjnath/ice_session.c | 3259 +++++++++++++++++ .../pjproject/pjnath/src/pjnath/ice_strans.c | 2596 +++++++++++++ .../pjproject/pjnath/src/pjnath/nat_detect.c | 825 +++++ .../pjproject/pjnath/src/pjnath/stun_auth.c | 561 +++ .../pjproject/pjnath/src/pjnath/stun_msg.c | 2253 ++++++++++++ .../pjnath/src/pjnath/stun_msg_dump.c | 245 ++ .../pjnath/src/pjnath/stun_session.c | 1325 +++++++ .../pjproject/pjnath/src/pjnath/stun_sock.c | 921 +++++ .../pjnath/src/pjnath/stun_transaction.c | 431 +++ .../pjnath/src/pjnath/turn_session.c | 2002 ++++++++++ .../pjproject/pjnath/src/pjnath/turn_sock.c | 1733 +++++++++ .../pjproject/pjnath/src/pjnath/upnp.c | 849 +++++ src/tuya_p2p/svc_ipc_core/CMakeLists.txt | 53 + .../svc_ipc_core/include/tuya_ipc_skill.h | 137 + .../svc_ipc_core/include/tuya_p2p_sdk.h | 39 + .../svc_ipc_core/src/tuya_ipc_skill.c | 627 ++++ src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c | 219 ++ src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt | 53 + .../include/tuya_ipc_media.h | 213 ++ .../include/tuya_ipc_media_adapter.h | 434 +++ .../include/tuya_ipc_media_stream.h | 120 + .../include/tuya_ipc_media_stream_common.h | 38 + .../include/tuya_ipc_media_stream_event.h | 787 ++++ .../svc_streaming_p2p/include/tuya_ipc_p2p.h | 120 + .../include/tuya_ipc_p2p_common.h | 154 + .../include/tuya_ipc_p2p_error.h | 46 + .../include/tuya_ipc_p2p_inner.h | 284 ++ .../svc_streaming_p2p/include/tuya_p2p_api.h | 200 + .../svc_streaming_p2p/src/tuya_ipc_p2p.c | 1753 +++++++++ .../src/tuya_ipc_p2p_common.c | 468 +++ 343 files changed, 123577 insertions(+), 1 deletion(-) create mode 100755 src/tuya_p2p/CMakeLists.txt create mode 100755 src/tuya_p2p/Kconfig create mode 100755 src/tuya_p2p/base_ice/CMakeLists.txt create mode 100755 src/tuya_p2p/base_ice/include/tuya_common_types.h create mode 100755 src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h create mode 100755 src/tuya_p2p/base_ice/src/ikcp.c create mode 100755 src/tuya_p2p/base_ice/src/ikcp.h create mode 100755 src/tuya_p2p/base_ice/src/pj_ice.c create mode 100755 src/tuya_p2p/base_ice/src/pj_ice.h create mode 100755 src/tuya_p2p/base_ice/src/pj_sdp.c create mode 100755 src/tuya_p2p/base_ice/src/pj_sdp.h create mode 100755 src/tuya_p2p/base_ice/src/pj_sync_condition.c create mode 100755 src/tuya_p2p/base_ice/src/pj_sync_condition.h create mode 100755 src/tuya_p2p/base_ice/src/queue.h create mode 100755 src/tuya_p2p/base_ice/src/tuya_error.c create mode 100755 src/tuya_p2p/base_ice/src/tuya_error.h create mode 100755 src/tuya_p2p/base_ice/src/tuya_log.h create mode 100755 src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c create mode 100755 src/tuya_p2p/base_ice/src/tuya_misc.c create mode 100755 src/tuya_p2p/base_ice/src/tuya_misc.h create mode 100755 src/tuya_p2p/base_ice/src/tuya_rtp.h create mode 100755 src/tuya_p2p/base_ice/src/tuya_sdp.c create mode 100755 src/tuya_p2p/base_ice/src/tuya_sdp.h create mode 100755 src/tuya_p2p/lib_rtp/CMakeLists.txt create mode 100755 src/tuya_p2p/lib_rtp/include/rtcp-header.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-demuxer.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-ext.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-header-extension.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-header.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-internal.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-member-list.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-member.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-packet.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-param.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-payload.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-profile.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-queue.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp-util.h create mode 100755 src/tuya_p2p/lib_rtp/include/rtp.h create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-payload.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c create mode 100755 src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c create mode 100755 src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-app.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-bye.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-interval.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-psfb.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-rr.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-sdec.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-sr.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp-xr.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtcp.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-demuxer.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-member-list.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-member.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-packet.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-profile.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-queue.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-ssrc.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp-time.c create mode 100755 src/tuya_p2p/lib_rtp/src/rtp.c create mode 100755 src/tuya_p2p/pjproject/CMakeLists.txt create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c create mode 100755 src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/array.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/assert.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/config.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/errno.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/except.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/guid.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/hash.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/limits.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/list.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/lock.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/log.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/math.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/os.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/pool.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/rand.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/sock.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/string.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/timer.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/types.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h create mode 100755 src/tuya_p2p/pjproject/pjlib/include/pjlib.h create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/array.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/config.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/errno.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/except.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/guid.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/hash.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/list.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/lock.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/log.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/pool.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/rand.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/string.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/timer.c create mode 100755 src/tuya_p2p/pjproject/pjlib/src/pj/types.c create mode 100755 src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h create mode 100755 src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h create mode 100755 src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h create mode 100755 src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h create mode 100755 src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h create mode 100755 src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h create mode 100755 src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c create mode 100755 src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c create mode 100755 src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h create mode 100755 src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c create mode 100755 src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c create mode 100755 src/tuya_p2p/svc_ipc_core/CMakeLists.txt create mode 100755 src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h create mode 100755 src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h create mode 100755 src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c create mode 100755 src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c create mode 100755 src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h create mode 100755 src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c create mode 100755 src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c diff --git a/src/Kconfig b/src/Kconfig index 00a0eca2a..2728d7f21 100755 --- a/src/Kconfig +++ b/src/Kconfig @@ -8,4 +8,5 @@ menu "configure tuyaopen" rsource "tal_system/Kconfig" rsource "liblvgl/Kconfig" rsource "peripherals/Kconfig" + rsource "tuya_p2p/Kconfig" endmenu diff --git a/src/tuya_cloud_service/cloud/tuya_iot.c b/src/tuya_cloud_service/cloud/tuya_iot.c index a2cc54001..bdf976342 100644 --- a/src/tuya_cloud_service/cloud/tuya_iot.c +++ b/src/tuya_cloud_service/cloud/tuya_iot.c @@ -441,6 +441,18 @@ static void check_auto_upgrade_timeout_on(TIMER_ID timer, void *user_data) tal_sw_timer_start(timer, AUTO_UPGRADE_CHECK_INTERVAL, TAL_TIMER_ONCE); } +static void mqtt_rtc_req_notify_cb(tuya_protocol_event_t *ev) +{ + tuya_iot_client_t *client = ev->user_data; + cJSON *data = (cJSON *)(ev->data); + char* pRootJson = cJSON_PrintUnformatted(data); + client->event.id = TUYA_EVENT_RTC_REQ; + client->event.type = TUYA_DATE_TYPE_JSON; + client->event.value.asJSON = data; + iot_dispatch_event(client); + return; +} + /* -------------------------------------------------------------------------- */ /* Internal machine state process */ /* -------------------------------------------------------------------------- */ @@ -485,7 +497,8 @@ static int run_state_mqtt_connect_start(tuya_iot_client_t *client) tuya_mqtt_protocol_register(&client->mqctx, PRO_GW_RESET, mqtt_service_reset_cmd_on, client); tuya_mqtt_protocol_register(&client->mqctx, PRO_UPGD_REQ, mqtt_service_upgrade_notify_on, client); tuya_mqtt_protocol_register(&client->mqctx, PRO_MQ_DPCACHE_NOTIFY, mqtt_atop_dp_cache_notify_cb, client); - + tuya_mqtt_protocol_register(&client->mqctx, PRO_RTC_REQ, mqtt_rtc_req_notify_cb, client); + return rt; } diff --git a/src/tuya_cloud_service/cloud/tuya_iot.h b/src/tuya_cloud_service/cloud/tuya_iot.h index 9e29afbf6..4a95c6140 100644 --- a/src/tuya_cloud_service/cloud/tuya_iot.h +++ b/src/tuya_cloud_service/cloud/tuya_iot.h @@ -73,6 +73,7 @@ typedef enum { TUYA_EVENT_DPCACHE_NOTIFY, TUYA_EVENT_BINDED_NOTIFY, TUYA_EVENT_DIRECT_MQTT_CONNECTED, + TUYA_EVENT_RTC_REQ, } tuya_event_id_t; #define EVENT_ID2STR(S) \ diff --git a/src/tuya_p2p/CMakeLists.txt b/src/tuya_p2p/CMakeLists.txt new file mode 100755 index 000000000..aebbe7cdb --- /dev/null +++ b/src/tuya_p2p/CMakeLists.txt @@ -0,0 +1,23 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +if (CONFIG_ENABLE_TUYA_P2P STREQUAL "y") + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/base_ice) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib_rtp) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/pjproject) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/svc_ipc_core) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/svc_streaming_p2p) + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +endif() \ No newline at end of file diff --git a/src/tuya_p2p/Kconfig b/src/tuya_p2p/Kconfig new file mode 100755 index 000000000..5aac78222 --- /dev/null +++ b/src/tuya_p2p/Kconfig @@ -0,0 +1,6 @@ +# Ktuyaconf +config ENABLE_TUYA_P2P + bool "Enable Tuya P2P" + default n + help + Enable Tuya P2P diff --git a/src/tuya_p2p/base_ice/CMakeLists.txt b/src/tuya_p2p/base_ice/CMakeLists.txt new file mode 100755 index 000000000..7d2a12afc --- /dev/null +++ b/src/tuya_p2p/base_ice/CMakeLists.txt @@ -0,0 +1,53 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/base_ice/include/tuya_common_types.h b/src/tuya_p2p/base_ice/include/tuya_common_types.h new file mode 100755 index 000000000..f4118a353 --- /dev/null +++ b/src/tuya_p2p/base_ice/include/tuya_common_types.h @@ -0,0 +1,93 @@ +#ifndef __TUYA_COMMON_TYPES_H__ +#define __TUYA_COMMON_TYPES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int OPERATE_RET; +typedef long long DLONG_T; +typedef DLONG_T *PDLONG_T; +typedef float FLOAT_T; +typedef FLOAT_T *PFLOAT_T; +typedef signed int INT_T; +typedef int *PINT_T; +typedef void *PVOID_T; +typedef char CHAR_T; +typedef char *PCHAR_T; +typedef signed char SCHAR_T; +typedef unsigned char UCHAR_T; +typedef short SHORT_T; +typedef unsigned short USHORT_T; +typedef short *PSHORT_T; +typedef long LONG_T; +typedef unsigned long ULONG_T; +typedef long *PLONG_T; +typedef unsigned char BYTE_T; +typedef BYTE_T *PBYTE_T; +typedef unsigned int UINT_T; +typedef unsigned int *PUINT_T; +typedef int BOOL_T; +typedef BOOL_T *PBOOL_T; +typedef long long int INT64_T; +typedef INT64_T *PINT64_T; +typedef unsigned long long int UINT64_T; +typedef UINT64_T *PUINT64_T; +typedef unsigned int UINT32_T; +typedef unsigned int *PUINT32_T; +typedef int INT32_T; +typedef int *PINT32_T; +typedef short INT16_T; +typedef INT16_T *PINT16_T; +typedef unsigned short UINT16_T; +typedef UINT16_T *PUINT16_T; +typedef signed char INT8_T; +typedef INT8_T *PINT8_T; +typedef unsigned char UINT8_T; +typedef UINT8_T *PUINT8_T; +typedef ULONG_T TIME_MS; +typedef ULONG_T TIME_S; +typedef unsigned int TIME_T; +typedef double DOUBLE_T; +typedef unsigned short WORD_T; +typedef WORD_T *PWORD_T; +typedef unsigned int DWORD_T; +typedef DWORD_T *PDWORD_T; + +#ifndef IN +#define IN +#endif + +#ifndef OUT +#define OUT +#endif + +#ifndef INOUT +#define INOUT +#endif + +#ifndef VOID +#define VOID void +#endif + +#ifndef VOID_T +#define VOID_T void +#endif + +#ifndef CONST +#define CONST const +#endif + +#ifndef STATIC +#define STATIC static +#endif + +#ifndef SIZEOF +#define SIZEOF sizeof +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h b/src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h new file mode 100755 index 000000000..a62778377 --- /dev/null +++ b/src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h @@ -0,0 +1,435 @@ +// +// ====== token format ====== +// { +// "username" : "12334939:mbzrxpgjys", +// "password" : "adfsaflsjfldssia", +// "ttl" : 86400, +// "uris" : [ +// "turn:1.2.3.4:9991?transport=udp", +// "turn:1.2.3.4:9992?transport=tcp", +// "turns:1.2.3.4:443?transport=tcp" +// ] +// } +#ifndef __TUYA_MEDIA_SERVICE_RTC_H__ +#define __TUYA_MEDIA_SERVICE_RTC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define TUYA_P2P_ID_LEN_MAX 80 +#define TUYA_P2P_CHANNEL_NUMBER_MAX 320 +#define TUYA_P2P_SESSION_NUMBER_MAX 1024 +#define TUYA_P2P_VIDEO_BITRATE_MIN (600) +#define TUYA_P2P_VIDEO_BITRATE_MAX (4000) + +#define TUYA_P2P_ERROR_SUCCESSFUL 0 +#define TUYA_P2P_ERROR_NOT_INITIALIZED -1 +#define TUYA_P2P_ERROR_ALREADY_INITIALIZED -2 +#define TUYA_P2P_ERROR_TIME_OUT -3 +#define TUYA_P2P_ERROR_INVALID_ID -4 +#define TUYA_P2P_ERROR_INVALID_PARAMETER -5 +#define TUYA_P2P_ERROR_DEVICE_NOT_ONLINE -6 +#define TUYA_P2P_ERROR_FAIL_TO_RESOLVE_NAME -7 +#define TUYA_P2P_ERROR_INVALID_PREFIX -8 +#define TUYA_P2P_ERROR_ID_OUT_OF_DATE -9 +#define TUYA_P2P_ERROR_NO_RELAY_SERVER_AVAILABLE -10 +#define TUYA_P2P_ERROR_INVALID_SESSION_HANDLE -11 +#define TUYA_P2P_ERROR_SESSION_CLOSED_REMOTE -12 +#define TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT -13 +#define TUYA_P2P_ERROR_SESSION_CLOSED_CALLED -14 +#define TUYA_P2P_ERROR_REMOTE_SITE_BUFFER_FULL -15 +#define TUYA_P2P_ERROR_USER_LISTEN_BREAK -16 +#define TUYA_P2P_ERROR_MAX_SESSION -17 +#define TUYA_P2P_ERROR_UDP_PORT_BIND_FAILED -18 +#define TUYA_P2P_ERROR_USER_CONNECT_BREAK -19 +#define TUYA_P2P_ERROR_SESSION_CLOSED_INSUFFICIENT_MEMORY -20 +#define TUYA_P2P_ERROR_INVALID_APILICENSE -21 +#define TUYA_P2P_ERROR_FAIL_TO_CREATE_THREAD -22 +#define TUYA_P2P_ERROR_OUT_OF_SESSION -23 +#define TUYA_P2P_ERROR_INVALID_PRE_SESSION -24 +#define TUYA_P2P_ERROR_PRE_SESSION_NOT_CONNECTED -25 +#define TUYA_P2P_ERROR_PRE_SESSION_ALREADY_ACTIVE -26 +#define TUYA_P2P_ERROR_PRE_SESSION_NOT_ACTIVE -27 +#define TUYA_P2P_ERROR_PRE_SESSION_SUSPENDED -28 +#define TUYA_P2P_ERROR_OUT_OF_MEMORY -29 +#define TUYA_P2P_ERROR_HTTP_FAILED -30 +#define TUYA_P2P_ERROR_PRECONNECT_UNSUPPORTED -31 +#define TUYA_P2P_ERROR_DTLS_HANDSHAKE_FAILED_FINGERPRINT -32 +#define TUYA_P2P_ERROR_GET_TOKEN_TIMEOUT -33 +#define TUYA_P2P_ERROR_AUTH_FAILED -34 +#define TUYA_P2P_ERROR_INIT_MBEDTLS_MD_AND_AES_FAILED -35 +#define TUYA_P2P_ERROR_HEARTBEAT_TIMEOUT -36 +#define TUYA_P2P_ERROR_DTLS_HANDSHAKE_FAILED -37 +#define TUYA_P2P_ERROR_DTLS_HANDSHAKE_TIMEOUT -38 +#define TUYA_P2P_ERROR_REMOTE_NO_RESPONSE -39 +#define TUYA_P2P_ERROR_PRE_SESSION_RESERVE_TIMEOUT -40 +#define TUYA_P2P_ERROR_RESET -41 +#define TUYA_P2P_ERROR_UV_TIMER_INIT_FAILED -42 +#define TUYA_P2P_ERROR_SDP_INIT_FAILED -43 +#define TUYA_P2P_ERROR_SDP_ADD_MEDIA_FAILED -44 +#define TUYA_P2P_ERROR_SDP_ADD_CODEC_FAILED -45 +#define TUYA_P2P_ERROR_CHANNEL_INIT_FAILED -46 +#define TUYA_P2P_ERROR_INVALID_AES_KEY -47 +#define TUYA_P2P_ERROR_SDP_SET_AES_KEY_FAILED -48 +#define TUYA_P2P_ERROR_INVALID_TOKEN -49 +#define TUYA_P2P_ERROR_TIME_OUT_NO_ANSWER -50 +#define TUYA_P2P_ERROR_TIME_OUT_LOCAL_NO_HOST_CAND -51 +#define TUYA_P2P_ERROR_TIME_OUT_LOCAL_NAT -52 +#define TUYA_P2P_ERROR_TIME_OUT_REMOTE_NAT -53 +#define TUYA_P2P_ERROR_TIME_COWBOY_NO_RESPONSE -54 +#define TUYA_P2P_ERROR_DEVICE_SECRET_MODE -102 +#define TUYA_P2P_ERROR_DEVICE_CREATE_SEND_THREAD_FAILED -103 +#define TUYA_P2P_ERROR_DEVICE_OUT_OF_SESSION -104 +#define TUYA_P2P_ERROR_DEVICE_AUTH_FAILED -105 +#define TUYA_P2P_ERROR_DEVICE_SESSION_CLOSED -106 +#define TUYA_P2P_ERROR_DEVICE_CREATE_WEBRTC_THREAD_FAILED -107 +#define TUYA_P2P_ERROR_DEVICE_ZOMBI_SESSION -108 +#define TUYA_P2P_ERROR_DEVICE_USER_CLOSE -109 +#define TUYA_P2P_ERROR_DEVICE_USER_EXIT -110 +#define TUYA_P2P_ERROR_DEVICE_IN_SECRET_MODE -111 +#define TUYA_P2P_ERROR_DEVICE_CALLING -113 +#define TUYA_P2P_ERROR_DEVICE_WEBRTC_EXIT -300 +#define TUYA_P2P_ERROR_REMOTE -100 + +typedef enum tuya_p2p_rtc_security_level { + TUYA_P2P_SECURITY_LEVEL_0, + TUYA_P2P_SECURITY_LEVEL_1, + TUYA_P2P_SECURITY_LEVEL_2, + TUYA_P2P_SECURITY_LEVEL_3, + TUYA_P2P_SECURITY_LEVEL_4, + TUYA_P2P_SECURITY_LEVEL_5, + /*-----------------------*/ + TUYA_P2P_SECURITY_LEVEL_MAX +} tuya_p2p_rtc_security_level_e; + +typedef enum tuya_p2p_rtc_log_level { + TUYA_P2P_LOG_TRACE, + TUYA_P2P_LOG_DEBUG, + TUYA_P2P_LOG_INFO, + TUYA_P2P_LOG_WARN, + TUYA_P2P_LOG_ERROR, + TUYA_P2P_LOG_FATAL +} tuya_p2p_rtc_log_level_e; + +typedef enum { + tuya_p2p_rtc_frame_type_audio, + tuya_p2p_rtc_frame_type_video_p, + tuya_p2p_rtc_frame_type_video_i +} tuya_p2p_rtc_frame_type_e; + +typedef enum tuya_p2p_rtc_connection_type { + tuya_p2p_rtc_connection_type_p2p, + tuya_p2p_rtc_connection_type_webrtc +} tuya_p2p_rtc_connection_type_e; + +typedef enum { + tuya_p2p_rtc_upnp_port_protocol_udp, + tuya_p2p_rtc_upnp_port_protocol_tcp +} tuya_p2p_rtc_upnp_port_protocol; + +#define TUYA_P2P_UPNP_ADDRESS_LENGTH 16 // IP address length +#define TUYA_P2P_UPNP_PROTOCOL_LENGTH 4 // Protocol length tcp or udp + +typedef struct _tuya_p2p_rtc_upnp_port_link_t { + char protocol[TUYA_P2P_UPNP_PROTOCOL_LENGTH]; + int external_port; + char remount_host[TUYA_P2P_UPNP_ADDRESS_LENGTH]; + int internal_port; + char internal_client[TUYA_P2P_UPNP_ADDRESS_LENGTH]; + int route_level; + int index; + struct _tuya_p2p_rtc_upnp_port_link_t *next; +} tuya_p2p_rtc_upnp_port_link_t; + +typedef struct tuya_p2p_rtc_audio_codec { + char name[64]; + int sample_rate; + int channel_number; +} tuya_p2p_rtc_audio_codec_t; + +typedef struct tuya_p2p_rtc_video_codec { + char name[64]; + int clock_rate; +} tuya_p2p_rtc_video_codec_t; + +typedef struct { + char *buf; + uint32_t size; + uint32_t len; + uint64_t pts; + uint64_t timestamp; + tuya_p2p_rtc_frame_type_e frame_type; +} tuya_p2p_rtc_frame_t; + +typedef enum rtc_state { + RTC_STATE_GET_TOKEN, + RTC_STATE_P2P_CONNECT, + RTC_STATE_DTLS_SRTP_KEY_NEGO, + RTC_STATE_STREAM, + RTC_STATE_FAILED, + RTC_STATE_NUMBER, +} rtc_state_e; + +typedef enum { RTC_PRE_NOT_ACTIVE = 0, RTC_PRE_ACTIVATING, RTC_PRE_ACTIVE, RTC_PRE_SUSPENDING } rtc_active_state_e; + +typedef struct { + int32_t handle; + int32_t is_pre; + rtc_state_e state; + rtc_active_state_e active_state; + tuya_p2p_rtc_connection_type_e connection_type; + tuya_p2p_rtc_audio_codec_t audio_codec; + tuya_p2p_rtc_video_codec_t video_codec; + char trace_id[128]; + char session_id[64]; + char sub_dev_id[64]; + int32_t stream_type; // Stream type 0 -- main stream 1 -- sub stream + int32_t is_replay; + char start_time[32]; + char end_time[32]; +} tuya_p2p_rtc_session_info_t; + +// tuya p2p sdk depends on several external services, implemented through callbacks or interfaces: +// 1. Signaling transmission +// When tuya p2p sdk needs to send signaling, it will call the upper layer implemented tuya_p2p_rtc_signaling_cb_t +// for transmission +// 2. Signaling reception +// When the upper layer receives signaling, it is set to tuya p2p sdk through the tuya_p2p_rtc_set_signaling +// interface +// 3. HTTP service +// When tuya p2p sdk needs to send logs, it will call the upper layer implemented tuya_p2p_rtc_log_cb_t for +// transmission + +// Signaling callback, used for sending signaling +// remote_id: indicates sending signaling to remote_id +// signaling: starting address of signaling content +// len: length of signaling content +typedef void (*tuya_p2p_rtc_signaling_cb_t)(char *remote_id, char *signaling, uint32_t len); + +// Log callback, used for sending logs +// log: starting address of log content +// len: length of log content +typedef void (*tuya_p2p_rtc_log_cb_t)(int level, char *log, uint32_t len); +typedef int (*tuya_p2p_rtc_log_get_level_cb_t)(); + +// Authentication callback, used to verify if offer is valid +// Use hmac-sha256 to calculate hash of buf content, then calculate base64 encoding of hash result, +// Calculation method: base64(hmac-sha256(key=password, content=buf, length=len)) +// Compare the result with md, return 0 if same, return -1 if different +typedef int32_t (*tuya_p2p_rtc_auth_cb_t)(char *buf, uint32_t len, char *md, uint32_t md_len); + +// http callback, used for sending http requests +// api: http interface name +// devId: device ID +// content: request content +// content_len: content length +typedef int32_t (*tuya_p2p_rtc_http_cb_t)(char *api, char *devId, char *content, uint32_t content_len); + +// crypt callback function +// mode: "aes", "ecb", ... +// crypt: "encrypt", "decrypt" +typedef int (*tuya_p2p_rtc_aes_create_cb_t)(void **handle, char *mode, char *crypt, char *key, int key_bits); +typedef int (*tuya_p2p_rtc_aes_destroy_cb_t)(void *handle); +typedef int (*tuya_p2p_rtc_aes_encrypt_cb_t)(void *handle, int length, char *iv, char *input, char *output); +typedef int (*tuya_p2p_rtc_aes_decrypt_cb_t)(void *handle, int length, char *iv, char *input, char *output); + +// upnp callback functions +typedef int (*tuya_p2p_rtc_upnp_alloc_port_cb_t)(tuya_p2p_rtc_upnp_port_protocol protocol, int *local_port, + char *address, int *port); +typedef int (*tuya_p2p_rtc_upnp_release_port_cb_t)(tuya_p2p_rtc_upnp_port_protocol protocol, int local_port); +typedef int (*tuya_p2p_rtc_upnp_bind_result_cb_t)(tuya_p2p_rtc_upnp_port_protocol protocol, int local_port, + int error_code); +typedef tuya_p2p_rtc_upnp_port_link_t *(*tuya_p2p_rtc_upnp_request_port_list_cb_t)( + tuya_p2p_rtc_upnp_port_protocol protocol, unsigned int port); + +// session state callback +typedef int (*tuya_p2p_rtc_session_state_cb_t)(char *remote_id, int handle, int is_pre, rtc_state_e state, + rtc_active_state_e active_state, int error); + +// localhost callback +// addresses size is 1024 +typedef int (*tuya_p2p_rtc_session_get_address_cb_t)(char *address); + +typedef struct tuya_p2p_rtc_cb { + tuya_p2p_rtc_signaling_cb_t on_signaling; // Signaling callback, application layer sends signaling through mqtt + tuya_p2p_rtc_signaling_cb_t on_moto_signaling; // Signaling callback, application layer sends signaling through moto + tuya_p2p_rtc_signaling_cb_t + on_lan_signaling; // Signaling callback, application layer sends signaling through LAN signaling channel + tuya_p2p_rtc_log_cb_t on_log; // Log callback, application layer reports log content to cloud + tuya_p2p_rtc_log_get_level_cb_t on_log_get_level; // Log callback, application layer reports log content to cloud + tuya_p2p_rtc_auth_cb_t on_auth; // Authentication callback, used by device side + tuya_p2p_rtc_http_cb_t on_http; // http callback, used by app side, called when tuya p2p sdk needs http interface + struct { + // External encryption/decryption interface, set to NULL if not needed + tuya_p2p_rtc_aes_create_cb_t on_create; + tuya_p2p_rtc_aes_destroy_cb_t on_destroy; + tuya_p2p_rtc_aes_encrypt_cb_t on_encrypt; + tuya_p2p_rtc_aes_decrypt_cb_t on_decrypt; + } aes; + struct { + tuya_p2p_rtc_upnp_alloc_port_cb_t on_alloc; // Get UPN mapped port information + tuya_p2p_rtc_upnp_release_port_cb_t on_release; // Release UPNP mapped port + tuya_p2p_rtc_upnp_bind_result_cb_t on_bind; // Feedback binding mapped port result to UPNP module + tuya_p2p_rtc_upnp_request_port_list_cb_t + on_request_port_list; // Get multi-level router mapped address and port information + } upnp; + + tuya_p2p_rtc_session_state_cb_t on_session_state; + tuya_p2p_rtc_session_get_address_cb_t on_get_address; +} tuya_p2p_rtc_cb_t; + +// p2p sdk initialization parameters +typedef struct tuya_p2p_rtc_options { + char local_id[TUYA_P2P_ID_LEN_MAX]; // Local id, device side fills device id, client side fills uid + tuya_p2p_rtc_cb_t cb; // Callback interface + uint32_t max_channel_number; // Maximum number of channels per connection + uint32_t max_session_number; // Allow simultaneous initiation or reception of several connections + uint32_t max_pre_session_number; // Allow simultaneous initiation or reception of several pre-connections + uint32_t send_buf_size[TUYA_P2P_CHANNEL_NUMBER_MAX]; // Send buffer size for each channel, in bytes + uint32_t recv_buf_size[TUYA_P2P_CHANNEL_NUMBER_MAX]; // Receive buffer size for each channel, in bytes + uint32_t video_bitrate_kbps; // Device side fills video bitrate, client side does not need to set + uint32_t preconnect_enable; // Whether to enable pre-connection, 1: enable, 0: disable + uint32_t fragement_len; // Data sending interface fragmentation length +} tuya_p2p_rtc_options_t; + +// Get p2p sdk version number +uint32_t tuya_p2p_rtc_get_version(); +// Get p2p sdk capability set +uint32_t tuya_p2p_rtc_get_skill(); +// p2p sdk initialization +int32_t tuya_p2p_rtc_init(tuya_p2p_rtc_options_t *opt); +int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason); +// p2p sdk deinitialization +int32_t tuya_p2p_rtc_deinit(); +int32_t tuya_p2p_rtc_reset(tuya_p2p_rtc_options_t *opt); +// Send signaling to p2p sdk, called when application layer receives p2p signaling +// remote_id: not used yet +// msg: signaling content, json format string +// msglen: signaling length +// return value: 0 +int32_t tuya_p2p_rtc_set_signaling(char *remote_id, char *msg, uint32_t msglen); +// Initiate pre-connection to the other party +// remote_id: id receiving mqtt signaling +// dev_id: device id +// return value: 0 +int32_t tuya_p2p_rtc_pre_connect(char *remote_id, char *dev_id); +// Close pre-connection +// remote_id: id receiving mqtt signaling +// return value: 0 +int32_t tuya_p2p_rtc_pre_connect_close(char *remote_id, int32_t reason); +// Initiate connection to the other party, atop interface is triggered by p2p library, called after connect +// remote_id: id receiving mqtt signaling +// dev_id: device id +// skill: device capability set, skill field obtained from cloud, json format string +// skill_len: skill length +// token: p2pConfig field obtained from cloud, json format string +// token_len: token length +// trace_id: full link log id, can be empty +// lan_mode: LAN priority mode +// timeout_ms: connection timeout, usually 15s +// return value: < 0 indicates failure, >=0 indicates successful connection, similar to socket fd +int32_t tuya_p2p_rtc_connect_v2(char *remote_id, char *dev_id, char *skill, uint32_t skill_len, char *token, + uint32_t token_len, char *trace_id, int lan_mode, int timeout_ms); +// Initiate connection to the other party +// remote_id: id of the other party, usually device id +// token: p2pConfig field obtained from cloud, json format string +// token_len: token length +// trace_id: full link log id, can be empty +// lan_mode: LAN priority mode +// timeout_ms: connection timeout, usually 15s +// return value: < 0 indicates failure, >=0 indicates successful connection, similar to socket fd +int32_t tuya_p2p_rtc_connect(char *remote_id, char *token, uint32_t token_len, char *trace_id, int lan_mode, + int timeout_ms); +// Cancel the connection being attempted, after calling this interface, tuya_p2p_rtc_connect interface returns failure +// immediately; Already established connections are not affected +int32_t tuya_p2p_rtc_connect_break(); +// Cancel the specified connection being attempted, after calling this interface, tuya_p2p_rtc_connect interface returns +// failure immediately; Already established connections are not affected trace_id: full link log id, specify the +// connection to cancel +int32_t tuya_p2p_rtc_connect_break_one(char *trace_id); +// http response +// api: http interface name +// code: http response status code +// result: http response content +// result_len: result length +int32_t tuya_p2p_rtc_set_http_result(char *api, uint32_t code, char *result, uint32_t result_len); +// http response +// api: http interface name +// code: http response status code +// content: on_http request content +// content_len: content length +// result: http response content +// result_len: result length +int32_t tuya_p2p_rtc_set_http_result_v2(char *api, uint32_t code, char *dev_id, char *content, uint32_t content_len, + char *result, uint32_t result_len); +// Accept a connection, similar to socket's accept interface +// return value: < 0 indicates failure, >= 0 indicates success +int32_t tuya_p2p_rtc_listen(); +// Interrupt listen, after calling this interface, tuya_p2p_rtc_listen returns failure immediately +int32_t tuya_p2p_rtc_listen_break(); +// Get connection related information, used by device side, to determine if the corresponding connection is p2p +// connection or webrtc connection +int32_t tuya_p2p_rtc_get_session_info(int32_t handle, tuya_p2p_rtc_session_info_t *info); +// Get session list +// Returns json format string, need to call tuya_p2p_rtc_free_session_list to release +char *tuya_p2p_rtc_get_session_list(); +// Release session list +// session_list: return value of tuya_p2p_rtc_get_session_list +void tuya_p2p_rtc_free_session_list(char *session_list); +// Check if connection is still alive +// handle: session handle +// return value: < 0 indicates session abnormal, 0 indicates session normal +int32_t tuya_p2p_rtc_check(int32_t handle); +// Close connection or suspend pre-connection +int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason); +// Send audio/video frame (webrtc specific) +int32_t tuya_p2p_rtc_send_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame); +// Send audio frame (webrtc specific) +int32_t tuya_p2p_rtc_recv_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame); +// Send data to the other party +// handle: connection handle +// channel_id: channel number +// buf: pointer to content to be sent +// len: length of data to be sent +// timeout_ms: interface blocking time, in milliseconds +// return value +// >=0: number of bytes sent successfully +// TUYA_P2P_ERROR_TIME_OUT: send timeout +// others: send failed, and connection has been disconnected +int32_t tuya_p2p_rtc_send_data(int32_t handle, uint32_t channel_id, char *buf, int32_t len, int32_t timeout_ms); +// Receive data +// handle: connection handle +// channel_id: channel number +// buf: receive buffer +// len: length of receive buffer +// timeout_ms: interface blocking time, in milliseconds +// return value +// 0: receive successful, *len indicates number of bytes received +// TUYA_P2P_ERROR_TIME_OUT: receive timeout +// others: receive failed, and connection has been disconnected +int32_t tuya_p2p_rtc_recv_data(int32_t handle, uint32_t channel_id, char *buf, int32_t *len, int32_t timeout_ms); +void tuya_p2p_rtc_notify_exit(); +// Check the current send/receive buffer status of a connection: +// handle: connection handle +// channel_id: channel number +// write_size: after function returns, updated to current bytes written to send buffer but not sent successfully +// read_size: after function returns, updated to current bytes received successfully but not read by application layer +// send_free_size: after function returns, updated to remaining space in send buffer +// return value: undefined +int32_t tuya_p2p_rtc_check_buffer(int32_t handle, uint32_t channel_id, uint32_t *write_size, uint32_t *read_size, + uint32_t *send_free_size); +// Notify p2p sdk that a device just came online +// Mainly used for low-power devices +int32_t tuya_p2p_rtc_set_remote_online(char *remote_id); +int32_t tuya_p2p_getwaitsnd(int32_t handle, uint32_t channel_id); +int32_t tuya_p2p_log_set_level(tuya_p2p_rtc_log_level_e level); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/base_ice/src/ikcp.c b/src/tuya_p2p/base_ice/src/ikcp.c new file mode 100755 index 000000000..e0b9c0919 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/ikcp.c @@ -0,0 +1,1385 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#include "ikcp.h" + +#include +#include +#include +#include +#include + +//===================================================================== +// KCP BASIC +//===================================================================== +const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto +const IUINT32 IKCP_RTO_MIN = 100; // normal min rto +const IUINT32 IKCP_RTO_DEF = 200; +const IUINT32 IKCP_RTO_MAX = 60000; +const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data +const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack +const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) +const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) +const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK +const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS +const IUINT32 IKCP_WND_SND = 32; +const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size +const IUINT32 IKCP_MTU_DEF = 1400; +const IUINT32 IKCP_ACK_FAST = 3; +const IUINT32 IKCP_INTERVAL = 100; +const IUINT32 IKCP_OVERHEAD = 24; +const IUINT32 IKCP_DEADLINK = 20; +const IUINT32 IKCP_THRESH_INIT = 2; +const IUINT32 IKCP_THRESH_MIN = 2; +const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size +const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window +const IUINT32 IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + +//--------------------------------------------------------------------- +// encode / decode +//--------------------------------------------------------------------- + +/* encode 8 bits unsigned int */ +static inline char *ikcp_encode8u(char *p, unsigned char c) +{ + *(unsigned char *)p++ = c; + return p; +} + +/* decode 8 bits unsigned int */ +static inline const char *ikcp_decode8u(const char *p, unsigned char *c) +{ + *c = *(unsigned char *)p++; + return p; +} + +/* encode 16 bits unsigned int (lsb) */ +static inline char *ikcp_encode16u(char *p, unsigned short w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char *)(p + 0) = (w & 255); + *(unsigned char *)(p + 1) = (w >> 8); +#else + memcpy(p, &w, 2); +#endif + p += 2; + return p; +} + +/* decode 16 bits unsigned int (lsb) */ +static inline const char *ikcp_decode16u(const char *p, unsigned short *w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(const unsigned char *)(p + 1); + *w = *(const unsigned char *)(p + 0) + (*w << 8); +#else + memcpy(w, p, 2); +#endif + p += 2; + return p; +} + +/* encode 32 bits unsigned int (lsb) */ +static inline char *ikcp_encode32u(char *p, IUINT32 l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char *)(p + 0) = (unsigned char)((l >> 0) & 0xff); + *(unsigned char *)(p + 1) = (unsigned char)((l >> 8) & 0xff); + *(unsigned char *)(p + 2) = (unsigned char)((l >> 16) & 0xff); + *(unsigned char *)(p + 3) = (unsigned char)((l >> 24) & 0xff); +#else + memcpy(p, &l, 4); +#endif + p += 4; + return p; +} + +/* decode 32 bits unsigned int (lsb) */ +static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(const unsigned char *)(p + 3); + *l = *(const unsigned char *)(p + 2) + (*l << 8); + *l = *(const unsigned char *)(p + 1) + (*l << 8); + *l = *(const unsigned char *)(p + 0) + (*l << 8); +#else + memcpy(l, p, 4); +#endif + p += 4; + return p; +} + +static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) +{ + return a <= b ? a : b; +} + +static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) +{ + return a >= b ? a : b; +} + +static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) +{ + return _imin_(_imax_(lower, middle), upper); +} + +static inline long _itimediff(IUINT32 later, IUINT32 earlier) +{ + return ((IINT32)(later - earlier)); +} + +//--------------------------------------------------------------------- +// manage segment +//--------------------------------------------------------------------- +typedef struct IKCPSEG IKCPSEG; + +static void *(*ikcp_malloc_hook)(size_t) = NULL; +static void (*ikcp_free_hook)(void *) = NULL; + +// internal malloc +static void *ikcp_malloc(size_t size) +{ + if (ikcp_malloc_hook) + return ikcp_malloc_hook(size); + return malloc(size); +} + +// internal free +static void ikcp_free(void *ptr) +{ + if (ikcp_free_hook) { + ikcp_free_hook(ptr); + } else { + free(ptr); + } +} + +// redefine allocator +void ikcp_allocator(void *(*new_malloc)(size_t), void (*new_free)(void *)) +{ + ikcp_malloc_hook = new_malloc; + ikcp_free_hook = new_free; +} + +// allocate a new kcp segment +static IKCPSEG *ikcp_segment_new(ikcpcb *kcp, int size) +{ + return (IKCPSEG *)ikcp_malloc(sizeof(IKCPSEG) + size); +} + +// delete a segment +static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) +{ + ikcp_free(seg); +} + +// write log +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) +{ + char buffer[1024]; + va_list argptr; + if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) + return; + va_start(argptr, fmt); + vsprintf(buffer, fmt, argptr); + va_end(argptr); + kcp->writelog(buffer, kcp, kcp->user); +} + +// check log mask +static int ikcp_canlog(const ikcpcb *kcp, int mask) +{ + if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) + return 0; + return 1; +} + +// output segment +static int ikcp_output(ikcpcb *kcp, const void *data, int size) +{ + assert(kcp); + assert(kcp->output); + if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { + ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); + } + if (size == 0) + return 0; + return kcp->output((const char *)data, size, kcp, kcp->user); +} + +// output queue +void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) +{ +#if 0 + const struct IQUEUEHEAD *p; + printf("<%s>: [", name); + for (p = head->next; p != head; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); + if (p->next != head) printf(","); + } + printf("]\n"); +#endif +} + +//--------------------------------------------------------------------- +// create a new kcpcb +//--------------------------------------------------------------------- +ikcpcb *ikcp_create(IUINT32 conv, void *user) +{ + ikcpcb *kcp = (ikcpcb *)ikcp_malloc(sizeof(struct IKCPCB)); + if (kcp == NULL) + return NULL; + kcp->conv = conv; + kcp->user = user; + kcp->snd_una = 0; + kcp->snd_nxt = 0; + kcp->rcv_nxt = 0; + kcp->ts_recent = 0; + kcp->ts_lastack = 0; + kcp->ts_probe = 0; + kcp->probe_wait = 0; + kcp->snd_wnd = IKCP_WND_SND; + kcp->rcv_wnd = IKCP_WND_RCV; + kcp->rmt_wnd = IKCP_WND_RCV; + kcp->cwnd = 0; + kcp->incr = 0; + kcp->probe = 0; + kcp->mtu = IKCP_MTU_DEF; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + kcp->stream = 0; + + kcp->buffer = (char *)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); + if (kcp->buffer == NULL) { + ikcp_free(kcp); + return NULL; + } + + iqueue_init(&kcp->snd_queue); + iqueue_init(&kcp->rcv_queue); + iqueue_init(&kcp->snd_buf); + iqueue_init(&kcp->rcv_buf); + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->state = 0; + kcp->acklist = NULL; + kcp->ackblock = 0; + kcp->ackcount = 0; + kcp->rx_srtt = 0; + kcp->rx_rttval = 0; + kcp->rx_rto = IKCP_RTO_DEF; + kcp->rx_minrto = IKCP_RTO_MIN; + kcp->current = 0; + kcp->interval = IKCP_INTERVAL; + kcp->ts_flush = IKCP_INTERVAL; + kcp->nodelay = 0; + kcp->updated = 0; + kcp->logmask = 0; + kcp->ssthresh = IKCP_THRESH_INIT; + kcp->fastresend = 0; + kcp->fastlimit = IKCP_FASTACK_LIMIT; + kcp->nocwnd = 0; + kcp->xmit = 0; + kcp->dead_link = IKCP_DEADLINK; + kcp->output = NULL; + kcp->writelog = NULL; + + return kcp; +} + +//--------------------------------------------------------------------- +// release a new kcpcb +//--------------------------------------------------------------------- +void ikcp_release(ikcpcb *kcp) +{ + assert(kcp); + if (kcp) { + IKCPSEG *seg; + while (!iqueue_is_empty(&kcp->snd_buf)) { + seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->snd_queue)) { + seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_queue)) { + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + if (kcp->buffer) { + ikcp_free(kcp->buffer); + } + if (kcp->acklist) { + ikcp_free(kcp->acklist); + } + + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->ackcount = 0; + kcp->buffer = NULL; + kcp->acklist = NULL; + ikcp_free(kcp); + } +} + +//--------------------------------------------------------------------- +// set output callback, which will be invoked by kcp +//--------------------------------------------------------------------- +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, ikcpcb *kcp, void *user)) +{ + kcp->output = output; +} + +//--------------------------------------------------------------------- +// user/upper level recv: returns size, returns below zero for EAGAIN +//--------------------------------------------------------------------- +int ikcp_recv(ikcpcb *kcp, char *buffer, int len) +{ + struct IQUEUEHEAD *p; + int ispeek = (len < 0) ? 1 : 0; + int peeksize; + int recover = 0; + IKCPSEG *seg; + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + if (len < 0) + len = -len; + + peeksize = ikcp_peeksize(kcp); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (kcp->nrcv_que >= kcp->rcv_wnd) + recover = 1; + + // merge fragment + for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue;) { + int fragment; + seg = iqueue_entry(p, IKCPSEG, node); + p = p->next; + + if (buffer) { + memcpy(buffer, seg->data, seg->len); + buffer += seg->len; + } + + len += seg->len; + fragment = seg->frg; + + if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { + ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); + } + + if (ispeek == 0) { + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + kcp->nrcv_que--; + } + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + + // fast recover + if (kcp->nrcv_que < kcp->rcv_wnd && recover) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + + return len; +} + +int ikcp_recv2(ikcpcb *kcp, char *buf, int len) +{ + assert(kcp); + IKCPSEG *seg_ret = NULL; + int ret = 0; + + if (iqueue_is_empty(&kcp->rcv_queue)) + return 0; + + // IKCP_RECVQUEUE_LOCK(kcp); + + seg_ret = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (len >= seg_ret->len) { + // Whether current window is 0 window, if it is 0 window, mark need to tell peer window update + if (kcp->nrcv_que >= kcp->rcv_wnd) { + kcp->probe |= IKCP_ASK_TELL; + } + + ret = seg_ret->len; + // No fragmentation exists, take it directly + iqueue_del(&seg_ret->node); + kcp->nrcv_que--; + // kcp->wait_rcv_bytes -= seg_ret->len; + // ikcp_log(kcp, IKCP_LOG_OUTPUT, "kcp recv kcp wait_rcv_bytes %d\n", kcp->wait_rcv_bytes); + // IKCP_RECVQUEUE_UNLOCK(kcp); + memcpy(buf, seg_ret->data + seg_ret->prepend, seg_ret->len); + // tuya_mbuf_free(seg_ret->user1); + } else { + /* This situation is rare, just do some troublesome handling */ + ret = len; + memcpy(buf, seg_ret->data + seg_ret->prepend, len); + /* Move the remaining part of buf forward */ + seg_ret->len -= len; + seg_ret->prepend += len; + + // kcp->wait_rcv_bytes -= len; + // ikcp_log(kcp, IKCP_LOG_OUTPUT, "kcp recv kcp2 wait_rcv_bytes %d\n", kcp->wait_rcv_bytes); + // IKCP_RECVQUEUE_UNLOCK(kcp); + } + + return ret; +} + +//--------------------------------------------------------------------- +// peek data size +//--------------------------------------------------------------------- +int ikcp_peeksize(const ikcpcb *kcp) +{ + struct IQUEUEHEAD *p; + IKCPSEG *seg; + int length = 0; + + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (seg->frg == 0) + return seg->len; + + if (kcp->nrcv_que < seg->frg + 1) + return -1; + + for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { + seg = iqueue_entry(p, IKCPSEG, node); + length += seg->len; + if (seg->frg == 0) + break; + } + + return length; +} + +//--------------------------------------------------------------------- +// user/upper level send, returns below zero for error +//--------------------------------------------------------------------- +int ikcp_send(ikcpcb *kcp, const char *buffer, int len) +{ + IKCPSEG *seg; + int count, i; + + assert(kcp->mss > 0); + if (len < 0) + return -1; + + // append to previous segment in streaming mode (if possible) + if (kcp->stream != 0) { + if (!iqueue_is_empty(&kcp->snd_queue)) { + IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); + if (old->len < kcp->mss) { + int capacity = kcp->mss - old->len; + int extend = (len < capacity) ? len : capacity; + seg = ikcp_segment_new(kcp, old->len + extend); + assert(seg); + if (seg == NULL) { + return -2; + } + iqueue_add_tail(&seg->node, &kcp->snd_queue); + memcpy(seg->data, old->data, old->len); + if (buffer) { + memcpy(seg->data + old->len, buffer, extend); + buffer += extend; + } + seg->len = old->len + extend; + seg->frg = 0; + len -= extend; + iqueue_del_init(&old->node); + ikcp_segment_delete(kcp, old); + } + } + if (len <= 0) { + return 0; + } + } + + if (len <= (int)kcp->mss) + count = 1; + else + count = (len + kcp->mss - 1) / kcp->mss; + + if (count >= (int)IKCP_WND_RCV) + return -2; + + if (count == 0) + count = 1; + + // fragment + for (i = 0; i < count; i++) { + int size = len > (int)kcp->mss ? (int)kcp->mss : len; + seg = ikcp_segment_new(kcp, size); + assert(seg); + if (seg == NULL) { + return -2; + } + if (buffer && len > 0) { + memcpy(seg->data, buffer, size); + } + seg->len = size; + seg->frg = (kcp->stream == 0) ? (count - i - 1) : 0; + iqueue_init(&seg->node); + iqueue_add_tail(&seg->node, &kcp->snd_queue); + kcp->nsnd_que++; + if (buffer) { + buffer += size; + } + len -= size; + } + + return 0; +} + +//--------------------------------------------------------------------- +// parse ack +//--------------------------------------------------------------------- +static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) +{ + IINT32 rto = 0; + if (kcp->rx_srtt == 0) { + kcp->rx_srtt = rtt; + kcp->rx_rttval = rtt / 2; + } else { + long delta = rtt - kcp->rx_srtt; + if (delta < 0) + delta = -delta; + kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; + kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; + if (kcp->rx_srtt < 1) + kcp->rx_srtt = 1; + } + rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); + kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); +} + +static void ikcp_shrink_buf(ikcpcb *kcp) +{ + struct IQUEUEHEAD *p = kcp->snd_buf.next; + if (p != &kcp->snd_buf) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + kcp->snd_una = seg->sn; + } else { + kcp->snd_una = kcp->snd_nxt; + } +} + +static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (sn == seg->sn) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + break; + } + if (_itimediff(sn, seg->sn) < 0) { + break; + } + } +} + +static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) +{ + struct IQUEUEHEAD *p, *next; + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(una, seg->sn) > 0) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + } else { + break; + } + } +} + +static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(sn, seg->sn) < 0) { + break; + } else if (sn != seg->sn) { +#ifndef IKCP_FASTACK_CONSERVE + seg->fastack++; +#else + if (_itimediff(ts, seg->ts) >= 0) + seg->fastack++; +#endif + } + } +} + +//--------------------------------------------------------------------- +// ack append +//--------------------------------------------------------------------- +static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + size_t newsize = kcp->ackcount + 1; + IUINT32 *ptr; + + if (newsize > kcp->ackblock) { + IUINT32 *acklist; + size_t newblock; + + for (newblock = 8; newblock < newsize; newblock <<= 1) + ; + acklist = (IUINT32 *)ikcp_malloc(newblock * sizeof(IUINT32) * 2); + + if (acklist == NULL) { + assert(acklist != NULL); + abort(); + } + + if (kcp->acklist != NULL) { + size_t x; + for (x = 0; x < kcp->ackcount; x++) { + acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; + acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; + } + ikcp_free(kcp->acklist); + } + + kcp->acklist = acklist; + kcp->ackblock = newblock; + } + + ptr = &kcp->acklist[kcp->ackcount * 2]; + ptr[0] = sn; + ptr[1] = ts; + kcp->ackcount++; +} + +static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) +{ + if (sn) + sn[0] = kcp->acklist[p * 2 + 0]; + if (ts) + ts[0] = kcp->acklist[p * 2 + 1]; +} + +//--------------------------------------------------------------------- +// parse data +//--------------------------------------------------------------------- +void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg, const char *data, int len) +{ + struct IQUEUEHEAD *p, *prev; + IUINT32 sn = newseg->sn; + int repeat = 0; + + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || _itimediff(sn, kcp->rcv_nxt) < 0) { + ikcp_segment_delete(kcp, newseg); + return; + } + + for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + prev = p->prev; + if (seg->sn == sn) { + repeat = 1; + break; + } + if (_itimediff(sn, seg->sn) > 0) { + break; + } + } + + if (repeat == 0) { + iqueue_init(&newseg->node); + iqueue_add(&newseg->node, p); + if (NULL != kcp->process_pkt) { + int after = kcp->process_pkt(kcp->user, len, data, newseg->data); + newseg->prepend = 0; + if (-1 != after) { + newseg->len = after; + } + } else { + newseg->prepend = 0; + memcpy(newseg->data, data, len); + } + kcp->nrcv_buf++; + } else { + ikcp_segment_delete(kcp, newseg); + } + +#if 0 + ikcp_qprint("rcvbuf", &kcp->rcv_buf); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + + // move available data from rcv_buf -> rcv_queue + while (!iqueue_is_empty(&kcp->rcv_buf)) { + IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + +#if 0 + ikcp_qprint("queue", &kcp->rcv_queue); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + +#if 1 +// printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); +// printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); +#endif +} + +//--------------------------------------------------------------------- +// input data +//--------------------------------------------------------------------- +int ikcp_input(ikcpcb *kcp, const char *data, long size) +{ + //printf("kcp->snd_una: %d\n", kcp->snd_una); + IUINT32 prev_una = kcp->snd_una; + IUINT32 maxack = 0, latest_ts = 0; + int flag = 0; + + if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { + ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); + } + + if (data == NULL || (int)size < (int)IKCP_OVERHEAD) + return -1; + + while (1) { + IUINT32 ts, sn, len, una, conv; + IUINT16 wnd; + IUINT8 cmd, frg; + IKCPSEG *seg; + + if (size < (int)IKCP_OVERHEAD) + break; + + data = ikcp_decode32u(data, &conv); + if (conv != kcp->conv) + return -1; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if ((long)size < (long)len || (int)len < 0) + return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + kcp->rmt_wnd = wnd; + ikcp_parse_una(kcp, una); + ikcp_shrink_buf(kcp); + + if (cmd == IKCP_CMD_ACK) { + if (_itimediff(kcp->current, ts) >= 0) { + ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); + } + ikcp_parse_ack(kcp, sn); + ikcp_shrink_buf(kcp); + if (flag == 0) { + flag = 1; + maxack = sn; + latest_ts = ts; + } else { + if (_itimediff(sn, maxack) > 0) { +#ifndef IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; +#else + if (_itimediff(ts, latest_ts) > 0) { + maxack = sn; + latest_ts = ts; + } +#endif + } + } + if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { + ikcp_log(kcp, IKCP_LOG_IN_ACK, "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, + (long)_itimediff(kcp->current, ts), (long)kcp->rx_rto); + } + } else if (cmd == IKCP_CMD_PUSH) { + if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { + ikcp_log(kcp, IKCP_LOG_IN_DATA, "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); + } + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { + ikcp_ack_push(kcp, sn, ts); + if (_itimediff(sn, kcp->rcv_nxt) >= 0) { + seg = ikcp_segment_new(kcp, len); + seg->conv = conv; + seg->cmd = cmd; + seg->frg = frg; + seg->wnd = wnd; + seg->ts = ts; + seg->sn = sn; + seg->una = una; + seg->len = len; + seg->prepend = 0; + + if (len > 0) { + memcpy(seg->data, data, len); + } + + ikcp_parse_data(kcp, seg, data, len); + } + } + } else if (cmd == IKCP_CMD_WASK) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { + ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); + } + } else if (cmd == IKCP_CMD_WINS) { + // do nothing + if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { + ikcp_log(kcp, IKCP_LOG_IN_WINS, "input wins: %lu", (unsigned long)(wnd)); + } + } else { + return -3; + } + + data += len; + size -= len; + } + + if (flag != 0) { + ikcp_parse_fastack(kcp, maxack, latest_ts); + } + + if (_itimediff(kcp->snd_una, prev_una) > 0) { + if (kcp->cwnd < kcp->rmt_wnd) { + IUINT32 mss = kcp->mss; + if (kcp->cwnd < kcp->ssthresh) { + kcp->cwnd++; + kcp->incr += mss; + } else { + if (kcp->incr < mss) + kcp->incr = mss; + kcp->incr += (mss * mss) / kcp->incr + (mss / 16); + if ((kcp->cwnd + 1) * mss <= kcp->incr) { +#if 1 + kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0) ? mss : 1); +#else + kcp->cwnd++; +#endif + } + } + if (kcp->cwnd > kcp->rmt_wnd) { + kcp->cwnd = kcp->rmt_wnd; + kcp->incr = kcp->rmt_wnd * mss; + } + } + } + + return 0; +} + +//--------------------------------------------------------------------- +// ikcp_encode_seg +//--------------------------------------------------------------------- +static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) +{ + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); + ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; +} + +static int ikcp_wnd_unused(const ikcpcb *kcp) +{ + if (kcp->nrcv_que < kcp->rcv_wnd) { + return kcp->rcv_wnd - kcp->nrcv_que; + } + return 0; +} + +//--------------------------------------------------------------------- +// ikcp_flush +//--------------------------------------------------------------------- +void ikcp_flush(ikcpcb *kcp) +{ + IUINT32 current = kcp->current; + char *buffer = kcp->buffer; + char *ptr = buffer; + int count, size, i; + IUINT32 resent, cwnd; + IUINT32 rtomin; + struct IQUEUEHEAD *p; + int change = 0; + int lost = 0; + IKCPSEG seg; + + // 'ikcp_update' haven't been called. + if (kcp->updated == 0) + return; + + seg.conv = kcp->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = ikcp_wnd_unused(kcp); + seg.una = kcp->rcv_nxt; + seg.len = 0; + seg.sn = 0; + seg.ts = 0; + seg.prepend = 0; + + // flush acknowledges + count = kcp->ackcount; + for (i = 0; i < count; i++) { + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->ackcount = 0; + + // probe window size (if remote window size equals zero) + if (kcp->rmt_wnd == 0) { + if (kcp->probe_wait == 0) { + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + } else { + if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { + if (kcp->probe_wait < IKCP_PROBE_INIT) + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->probe_wait += kcp->probe_wait / 2; + if (kcp->probe_wait > IKCP_PROBE_LIMIT) + kcp->probe_wait = IKCP_PROBE_LIMIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + kcp->probe |= IKCP_ASK_SEND; + } + } + } else { + kcp->ts_probe = 0; + kcp->probe_wait = 0; + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_SEND) { + seg.cmd = IKCP_CMD_WASK; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_TELL) { + seg.cmd = IKCP_CMD_WINS; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->probe = 0; + + // calculate window size + cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); + if (kcp->nocwnd == 0) + cwnd = _imin_(kcp->cwnd, cwnd); + + // move data from snd_queue to snd_buf + while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { + IKCPSEG *newseg; + if (iqueue_is_empty(&kcp->snd_queue)) + break; + + newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + + iqueue_del(&newseg->node); + iqueue_add_tail(&newseg->node, &kcp->snd_buf); + kcp->nsnd_que--; + kcp->nsnd_buf++; + + newseg->conv = kcp->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = kcp->snd_nxt++; + newseg->una = kcp->rcv_nxt; + newseg->resendts = current; + newseg->rto = kcp->rx_rto; + newseg->fastack = 0; + newseg->xmit = 0; + //printf("newseg->sn: %d\n", newseg->sn); + } + + // calculate resent + resent = (kcp->fastresend > 0) ? (IUINT32)kcp->fastresend : 0xffffffff; + rtomin = (kcp->nodelay == 0) ? (kcp->rx_rto >> 3) : 0; + + // flush data segments + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); + int needsend = 0; + if (segment->xmit == 0) { + needsend = 1; + segment->xmit++; + segment->rto = kcp->rx_rto; + segment->resendts = current + segment->rto + rtomin; + } else if (_itimediff(current, segment->resendts) >= 0) { + needsend = 1; + segment->xmit++; + kcp->xmit++; + if (kcp->nodelay == 0) { + segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto); + } else { + IINT32 step = (kcp->nodelay < 2) ? ((IINT32)(segment->rto)) : kcp->rx_rto; + segment->rto += step / 2; + } + segment->resendts = current + segment->rto; + lost = 1; + } else if (segment->fastack >= resent) { + if ((int)segment->xmit <= kcp->fastlimit || kcp->fastlimit <= 0) { + needsend = 1; + segment->xmit++; + segment->fastack = 0; + segment->resendts = current + segment->rto; + change++; + } + } + + if (needsend) { + int need; + segment->ts = current; + segment->wnd = seg.wnd; + segment->una = kcp->rcv_nxt; + + size = (int)(ptr - buffer); + need = IKCP_OVERHEAD + segment->len; + + if (size + need > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segment); + + if (segment->len > 0) { + memcpy(ptr, segment->data, segment->len); + ptr += segment->len; + } + + if (segment->xmit >= kcp->dead_link) { + kcp->state = (IUINT32)-1; + } + } + } + + // flash remain segments + size = (int)(ptr - buffer); + if (size > 0) { + ikcp_output(kcp, buffer, size); + } + + // update ssthresh + if (change) { + IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; + kcp->ssthresh = inflight / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = kcp->ssthresh + resent; + kcp->incr = kcp->cwnd * kcp->mss; + } + + if (lost) { + kcp->ssthresh = cwnd / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + + if (kcp->cwnd < 1) { + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } +} + +//--------------------------------------------------------------------- +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +//--------------------------------------------------------------------- +void ikcp_update(ikcpcb *kcp, IUINT32 current) +{ + IINT32 slap; + + kcp->current = current; + + if (kcp->updated == 0) { + kcp->updated = 1; + kcp->ts_flush = kcp->current; + } + + slap = _itimediff(kcp->current, kcp->ts_flush); + + if (slap >= 10000 || slap < -10000) { + kcp->ts_flush = kcp->current; + slap = 0; + } + + if (slap >= 0) { + kcp->ts_flush += kcp->interval; + if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { + kcp->ts_flush = kcp->current + kcp->interval; + } + ikcp_flush(kcp); + } +} + +//--------------------------------------------------------------------- +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +//--------------------------------------------------------------------- +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) +{ + IUINT32 ts_flush = kcp->ts_flush; + IINT32 tm_flush = 0x7fffffff; + IINT32 tm_packet = 0x7fffffff; + IUINT32 minimal = 0; + struct IQUEUEHEAD *p; + + if (kcp->updated == 0) { + return current; + } + + if (_itimediff(current, ts_flush) >= 10000 || _itimediff(current, ts_flush) < -10000) { + ts_flush = current; + } + + if (_itimediff(current, ts_flush) >= 0) { + return current; + } + + tm_flush = _itimediff(ts_flush, current); + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + IINT32 diff = _itimediff(seg->resendts, current); + if (diff <= 0) { + return current; + } + if (diff < tm_packet) + tm_packet = diff; + } + + minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= kcp->interval) + minimal = kcp->interval; + + return current + minimal; +} + +int ikcp_setmtu(ikcpcb *kcp, int mtu) +{ + char *buffer; + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + buffer = (char *)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); + if (buffer == NULL) + return -2; + kcp->mtu = mtu; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + ikcp_free(kcp->buffer); + kcp->buffer = buffer; + return 0; +} + +int ikcp_interval(ikcpcb *kcp, int interval) +{ + if (interval > 5000) + interval = 5000; + else if (interval < 10) + interval = 10; + kcp->interval = interval; + return 0; +} + +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) +{ + if (nodelay >= 0) { + kcp->nodelay = nodelay; + if (nodelay) { + kcp->rx_minrto = IKCP_RTO_NDL; + } else { + kcp->rx_minrto = IKCP_RTO_MIN; + } + } + if (interval >= 0) { + if (interval > 5000) + interval = 5000; + else if (interval < 10) + interval = 10; + kcp->interval = interval; + } + if (resend >= 0) { + kcp->fastresend = resend; + } + if (nc >= 0) { + kcp->nocwnd = nc; + } + return 0; +} + +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) +{ + if (kcp) { + if (sndwnd > 0) { + kcp->snd_wnd = sndwnd; + } + if (rcvwnd > 0) { // must >= max fragment size + kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); + } + } + return 0; +} + +int ikcp_waitsnd(const ikcpcb *kcp) +{ + return kcp->nsnd_buf + kcp->nsnd_que; +} + +// read conv +IUINT32 ikcp_getconv(const void *ptr) +{ + IUINT32 conv; + ikcp_decode32u((const char *)ptr, &conv); + return conv; +} + +// read cmd +IUINT8 ikcp_getcmd(const void *ptr) +{ + IUINT8 cmd; + // jump conv + ptr += 4; + ikcp_decode8u((const char *)ptr, &cmd); + return cmd; +} + +// read conv +IUINT32 ikcp_getsn(const void *ptr) +{ + IUINT32 sn; + // jump conv + ptr += 4; + // jump cmd + ptr += 1; + // jump frg + ptr += 1; + // jump wnd + ptr += 2; + // jump ts + ptr += 4; + ikcp_decode32u((const char *)ptr, &sn); + return sn; +} + +void ikcp_setprocesspkt(ikcpcb *kcp, int (*process_pkt)(void *user, int length, const char *input, char *output)) +{ + kcp->process_pkt = process_pkt; + return; +} diff --git a/src/tuya_p2p/base_ice/src/ikcp.h b/src/tuya_p2p/base_ice/src/ikcp.h new file mode 100755 index 000000000..abcff4a42 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/ikcp.h @@ -0,0 +1,406 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#ifndef __IKCP_H__ +#define __IKCP_H__ + +#include +#include +#include + +//===================================================================== +// 32BIT INTEGER DEFINITION +//===================================================================== +#ifndef __INTEGER_32_BITS__ +#define __INTEGER_32_BITS__ +#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || \ + defined(_M_IA64) || defined(_M_AMD64) +typedef unsigned int ISTDUINT32; +typedef int ISTDINT32; +#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || defined(__i386) || defined(_M_X86) +typedef unsigned long ISTDUINT32; +typedef long ISTDINT32; +#elif defined(__MACOS__) +typedef UInt32 ISTDUINT32; +typedef SInt32 ISTDINT32; +#elif defined(__APPLE__) && defined(__MACH__) +#include +typedef u_int32_t ISTDUINT32; +typedef int32_t ISTDINT32; +#elif defined(__BEOS__) +#include +typedef u_int32_t ISTDUINT32; +typedef int32_t ISTDINT32; +#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) +typedef unsigned __int32 ISTDUINT32; +typedef __int32 ISTDINT32; +#elif defined(__GNUC__) +#include +typedef uint32_t ISTDUINT32; +typedef int32_t ISTDINT32; +#else +typedef unsigned long ISTDUINT32; +typedef long ISTDINT32; +#endif +#endif + +//===================================================================== +// Integer Definition +//===================================================================== +#ifndef __IINT8_DEFINED +#define __IINT8_DEFINED +typedef char IINT8; +#endif + +#ifndef __IUINT8_DEFINED +#define __IUINT8_DEFINED +typedef unsigned char IUINT8; +#endif + +#ifndef __IUINT16_DEFINED +#define __IUINT16_DEFINED +typedef unsigned short IUINT16; +#endif + +#ifndef __IINT16_DEFINED +#define __IINT16_DEFINED +typedef short IINT16; +#endif + +#ifndef __IINT32_DEFINED +#define __IINT32_DEFINED +typedef ISTDINT32 IINT32; +#endif + +#ifndef __IUINT32_DEFINED +#define __IUINT32_DEFINED +typedef ISTDUINT32 IUINT32; +#endif + +#ifndef __IINT64_DEFINED +#define __IINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 IINT64; +#else +typedef long long IINT64; +#endif +#endif + +#ifndef __IUINT64_DEFINED +#define __IUINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 IUINT64; +#else +typedef unsigned long long IUINT64; +#endif +#endif + +#ifndef INLINE +#if defined(__GNUC__) + +#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) +#define INLINE __inline__ __attribute__((always_inline)) +#else +#define INLINE __inline__ +#endif + +#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) +#define INLINE __inline +#else +#define INLINE +#endif +#endif + +#if (!defined(__cplusplus)) && (!defined(inline)) +#define inline INLINE +#endif + +//===================================================================== +// QUEUE DEFINITION +//===================================================================== +#ifndef __IQUEUE_DEF__ +#define __IQUEUE_DEF__ + +struct IQUEUEHEAD { + struct IQUEUEHEAD *next, *prev; +}; + +typedef struct IQUEUEHEAD iqueue_head; + +//--------------------------------------------------------------------- +// queue init +//--------------------------------------------------------------------- +#define IQUEUE_HEAD_INIT(name) \ + { \ + &(name), &(name) \ + } +#define IQUEUE_HEAD(name) struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) + +#define IQUEUE_INIT(ptr) ((ptr)->next = (ptr), (ptr)->prev = (ptr)) + +#define IOFFSETOF(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER) + +#define ICONTAINEROF(ptr, type, member) ((type *)(((char *)((type *)ptr)) - IOFFSETOF(type, member))) + +#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) + +//--------------------------------------------------------------------- +// queue operation +//--------------------------------------------------------------------- +#define IQUEUE_ADD(node, head) \ + ((node)->prev = (head), (node)->next = (head)->next, (head)->next->prev = (node), (head)->next = (node)) + +#define IQUEUE_ADD_TAIL(node, head) \ + ((node)->prev = (head)->prev, (node)->next = (head), (head)->prev->next = (node), (head)->prev = (node)) + +#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) + +#define IQUEUE_DEL(entry) \ + ((entry)->next->prev = (entry)->prev, (entry)->prev->next = (entry)->next, (entry)->next = 0, (entry)->prev = 0) + +#define IQUEUE_DEL_INIT(entry) \ + do { \ + IQUEUE_DEL(entry); \ + IQUEUE_INIT(entry); \ + } while (0) + +#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) + +#define iqueue_init IQUEUE_INIT +#define iqueue_entry IQUEUE_ENTRY +#define iqueue_add IQUEUE_ADD +#define iqueue_add_tail IQUEUE_ADD_TAIL +#define iqueue_del IQUEUE_DEL +#define iqueue_del_init IQUEUE_DEL_INIT +#define iqueue_is_empty IQUEUE_IS_EMPTY + +#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ + for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); &((iterator)->MEMBER) != (head); \ + (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) + +#define iqueue_foreach(iterator, head, TYPE, MEMBER) IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) + +#define iqueue_foreach_entry(pos, head) for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) + +#define __iqueue_splice(list, head) \ + do { \ + iqueue_head *first = (list)->next, *last = (list)->prev; \ + iqueue_head *at = (head)->next; \ + (first)->prev = (head), (head)->next = (first); \ + (last)->next = (at), (at)->prev = (last); \ + } while (0) + +#define iqueue_splice(list, head) \ + do { \ + if (!iqueue_is_empty(list)) \ + __iqueue_splice(list, head); \ + } while (0) + +#define iqueue_splice_init(list, head) \ + do { \ + iqueue_splice(list, head); \ + iqueue_init(list); \ + } while (0) + +#ifdef _MSC_VER +#pragma warning(disable : 4311) +#pragma warning(disable : 4312) +#pragma warning(disable : 4996) +#endif + +#endif + +//--------------------------------------------------------------------- +// BYTE ORDER & ALIGNMENT +//--------------------------------------------------------------------- +#ifndef IWORDS_BIG_ENDIAN +#ifdef _BIG_ENDIAN_ +#if _BIG_ENDIAN_ +#define IWORDS_BIG_ENDIAN 1 +#endif +#endif +#ifndef IWORDS_BIG_ENDIAN +#if defined(__hppa__) || defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) || defined(__powerpc__) || defined(__mc68000__) || defined(__s390x__) || defined(__s390__) +#define IWORDS_BIG_ENDIAN 1 +#endif +#endif +#ifndef IWORDS_BIG_ENDIAN +#define IWORDS_BIG_ENDIAN 0 +#endif +#endif + +#ifndef IWORDS_MUST_ALIGN +#if defined(__i386__) || defined(__i386) || defined(_i386_) +#define IWORDS_MUST_ALIGN 0 +#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) +#define IWORDS_MUST_ALIGN 0 +#elif defined(__amd64) || defined(__amd64__) +#define IWORDS_MUST_ALIGN 0 +#else +#define IWORDS_MUST_ALIGN 1 +#endif +#endif + +//===================================================================== +// SEGMENT +//===================================================================== +struct IKCPSEG { + struct IQUEUEHEAD node; + IUINT32 conv; + IUINT32 cmd; + IUINT32 frg; + IUINT32 wnd; + IUINT32 ts; + IUINT32 sn; + IUINT32 una; + IUINT32 len; + IUINT32 resendts; + IUINT32 rto; + IUINT32 fastack; + IUINT32 xmit; + IUINT32 prepend; + char data[1]; +}; + +//--------------------------------------------------------------------- +// IKCPCB +//--------------------------------------------------------------------- +struct IKCPCB { + IUINT32 conv, mtu, mss, state; + IUINT32 snd_una, snd_nxt, rcv_nxt; + IUINT32 ts_recent, ts_lastack, ssthresh; + IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; + IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + IUINT32 current, interval, ts_flush, xmit; + IUINT32 nrcv_buf, nsnd_buf; + IUINT32 nrcv_que, nsnd_que; + IUINT32 nodelay, updated; + IUINT32 ts_probe, probe_wait; + IUINT32 dead_link, incr; + struct IQUEUEHEAD snd_queue; + struct IQUEUEHEAD rcv_queue; + struct IQUEUEHEAD snd_buf; + struct IQUEUEHEAD rcv_buf; + IUINT32 *acklist; + IUINT32 ackcount; + IUINT32 ackblock; + void *user; + char *buffer; + int fastresend; + int fastlimit; + int nocwnd, stream; + int logmask; + int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); + void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); + int (*process_pkt)(void *user, int length, const char *input, char *output); +}; + +typedef struct IKCPCB ikcpcb; + +#define IKCP_LOG_OUTPUT 1 +#define IKCP_LOG_INPUT 2 +#define IKCP_LOG_SEND 4 +#define IKCP_LOG_RECV 8 +#define IKCP_LOG_IN_DATA 16 +#define IKCP_LOG_IN_ACK 32 +#define IKCP_LOG_IN_PROBE 64 +#define IKCP_LOG_IN_WINS 128 +#define IKCP_LOG_OUT_DATA 256 +#define IKCP_LOG_OUT_ACK 512 +#define IKCP_LOG_OUT_PROBE 1024 +#define IKCP_LOG_OUT_WINS 2048 + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------- +// interface +//--------------------------------------------------------------------- + +// create a new kcp control object, 'conv' must equal in two endpoint +// from the same connection. 'user' will be passed to the output callback +// output callback can be setup like this: 'kcp->output = my_udp_output' +ikcpcb *ikcp_create(IUINT32 conv, void *user); + +// release kcp control object +void ikcp_release(ikcpcb *kcp); + +// set output callback, which will be invoked by kcp +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, ikcpcb *kcp, void *user)); + +// user/upper level recv: returns size, returns below zero for EAGAIN +int ikcp_recv(ikcpcb *kcp, char *buffer, int len); +int ikcp_recv2(ikcpcb *kcp, char *buffer, int len); + +// user/upper level send, returns below zero for error +int ikcp_send(ikcpcb *kcp, const char *buffer, int len); + +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +void ikcp_update(ikcpcb *kcp, IUINT32 current); + +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); + +// when you received a low level packet (eg. UDP packet), call it +int ikcp_input(ikcpcb *kcp, const char *data, long size); + +// flush pending data +void ikcp_flush(ikcpcb *kcp); + +// check the size of next message in the recv queue +int ikcp_peeksize(const ikcpcb *kcp); + +// change MTU size, default is 1400 +int ikcp_setmtu(ikcpcb *kcp, int mtu); + +// set maximum window size: sndwnd=32, rcvwnd=32 by default +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); + +// get how many packet is waiting to be sent +int ikcp_waitsnd(const ikcpcb *kcp); + +// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) +// nodelay: 0:disable(default), 1:enable +// interval: internal update timer interval in millisec, default is 100ms +// resend: 0:disable fast resend(default), 1:enable fast resend +// nc: 0:normal congestion control(default), 1:disable congestion control +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); + +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); + +// setup allocator +void ikcp_allocator(void *(*new_malloc)(size_t), void (*new_free)(void *)); + +// read conv +IUINT32 ikcp_getconv(const void *ptr); +// read cmd +IUINT8 ikcp_getcmd(const void *ptr); +// read conv +IUINT32 ikcp_getsn(const void *ptr); + +void ikcp_setprocesspkt(ikcpcb *kcp, int (*process_pkt)(void *user, int length, const char *input, char *output)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/base_ice/src/pj_ice.c b/src/tuya_p2p/base_ice/src/pj_ice.c new file mode 100755 index 000000000..ad6b09901 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_ice.c @@ -0,0 +1,581 @@ +#include "pj_ice.h" +#include "cJSON.h" + +typedef struct tagIceWorkerThreadParam { + pj_ice_session_t *pIceSession; + pj_ice_strans_cfg *pCfg; + bool bThreadQuitFlag; +} ICE_WORKER_THREAD_PARAM; + +typedef struct pj_ice_session { + pj_caching_pool cachePool; + pj_pool_t *pPool; + pj_thread_t *pThread; + pj_bool_t bThreadQuitFlag; + pj_ice_strans_cfg iceCfg; + pj_ice_strans *pIceSTransport; + pj_bool_t bLastCand; + unsigned int uComponentCount; + ICE_WORKER_THREAD_PARAM *pIceThreadParam; +} pj_ice_session_t; + +bool g_bInited = false; +#define KA_INTERVAL 300 +#define THIS_FILE "pj_ice.c" +#define INDENT " " + +#define PRINT(...) \ + printed = pj_ansi_snprintf(p, maxlen - (p - buffer), __VA_ARGS__); \ + if (printed <= 0 || printed >= (int)(maxlen - (p - buffer))) \ + return -PJ_ETOOSMALL; \ + p += printed + +void pj_print_error(const char *title, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + // PJ_LOG(1, (THIS_FILE, "%s: %s", title, errmsg)); + return; +} + +bool pj_thread_register2() +{ + pj_thread_desc desc; + pj_thread_t *thread = 0; + if (!pj_thread_is_registered()) { + return (pj_thread_register(NULL, desc, &thread) == PJ_SUCCESS ? true : false); + } + return false; +} + +bool is_ipv4(char *ip_str) +{ + pj_str_t ip = pj_str(ip_str); + pj_sockaddr addr; + if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &ip, &addr) != PJ_SUCCESS) { + // Not a valid IP address + return false; + } + return (addr.addr.sa_family == pj_AF_INET()); +} + +bool is_ipv6(char *ip_str) +{ + pj_str_t ip = pj_str(ip_str); + pj_sockaddr addr; + if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &ip, &addr) != PJ_SUCCESS) { + // Not a valid IP address + return false; + } + return (addr.addr.sa_family == pj_AF_INET6()); +} + +/* + * This function checks for events from both timer and ioqueue (for + * network events). It is invoked by the worker thread. + */ +bool pj_ice_session_handle_events(pj_ice_session_t *pIceSession, unsigned max_msec, unsigned *p_count) +{ + if (pIceSession == NULL) { + return false; + } + + enum { MAX_NET_EVENTS = 1 }; + pj_time_val max_timeout = {0, 0}; + pj_time_val timeout = {0, 0}; + unsigned count = 0, net_event_count = 0; + int c; + + pj_ice_strans_cfg *pIceStransCfg = &pIceSession->iceCfg; + max_timeout.msec = max_msec; + + /* Poll the timer to run it and also to retrieve the earliest entry. */ + timeout.sec = timeout.msec = 0; + c = pj_timer_heap_poll(pIceStransCfg->stun_cfg.timer_heap, &timeout); + if (c > 0) + count += c; + + /* timer_heap_poll should never ever returns negative value, or otherwise + * ioqueue_poll() will block forever! + */ + pj_assert(timeout.sec >= 0 && timeout.msec >= 0); + if (timeout.msec >= 1000) + timeout.msec = 999; + + /* compare the value with the timeout to wait from timer, and use the + * minimum value. + */ + if (PJ_TIME_VAL_GT(timeout, max_timeout)) + timeout = max_timeout; + + /* Poll ioqueue. + * Repeat polling the ioqueue while we have immediate events, because + * timer heap may process more than one events, so if we only process + * one network events at a time (such as when IOCP backend is used), + * the ioqueue may have trouble keeping up with the request rate. + * + * For example, for each send() request, one network event will be + * reported by ioqueue for the send() completion. If we don't poll + * the ioqueue often enough, the send() completion will not be + * reported in timely manner. + */ + do { + c = pj_ioqueue_poll(pIceStransCfg->stun_cfg.ioqueue, &timeout); + if (c < 0) { + pj_status_t err = pj_get_netos_error(); + if (err != PJ_SUCCESS) + printf("pj_handle_events error: %d\n", err); + pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout)); + if (p_count) + *p_count = count; + return false; + } else if (c == 0) { + break; + } else { + net_event_count += c; + timeout.sec = timeout.msec = 0; + } + } while (c > 0 && net_event_count < MAX_NET_EVENTS); + + count += net_event_count; + if (p_count) + *p_count = count; + + return true; +} + +/* + * This is the worker thread that polls event in the background. + */ +int ice_worker_thread(void *pParam) +{ + ICE_WORKER_THREAD_PARAM *pThis = (ICE_WORKER_THREAD_PARAM *)(pParam); + if (pThis == NULL) { + return -1; + } + while (!pThis->bThreadQuitFlag) { + pj_ice_session_handle_events(pThis->pIceSession, 10, NULL); + } + return 0; +} + +/* Utility to create a=candidate SDP attribute */ +int print_cand(char buffer[], unsigned maxlen, const pj_ice_sess_cand *cand) +{ + char ipaddr[PJ_INET6_ADDRSTRLEN]; + char *p = buffer; + int printed; + + PRINT("a=candidate:%.*s %u UDP %u %s %u typ ", (int)cand->foundation.slen, cand->foundation.ptr, + (unsigned)cand->comp_id, cand->prio, pj_sockaddr_print(&cand->addr, ipaddr, sizeof(ipaddr), 0), + (unsigned)pj_sockaddr_get_port(&cand->addr)); + + PRINT("%s\r\n", pj_ice_get_cand_type_name(cand->type)); + + if (p == buffer + maxlen) + return -PJ_ETOOSMALL; + + *p = '\0'; + + return (int)(p - buffer); +} + +/* Parse a=candidate line */ +int parse_cand(pj_pool_t *pool, const pj_str_t *orig_input, pj_ice_sess_cand *cand) +{ + pj_str_t token, delim, host; + int af; + pj_ssize_t found_idx; + pj_status_t status = PJNATH_EICEINCANDSDP; + + pj_bzero(cand, sizeof(*cand)); + + // PJ_UNUSED_ARG(obj_name); + + /* Foundation */ + delim = pj_str(" "); + found_idx = pj_strtok(orig_input, &delim, &token, 0); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE foundation in candidate")); + goto on_return; + } + if (pool) { + pj_strdup(pool, &cand->foundation, &token); + } else { + cand->foundation = token; + } + + /* Component ID */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE component ID in candidate")); + goto on_return; + } + cand->comp_id = (pj_uint8_t)pj_strtoul(&token); + + /* Transport */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE transport in candidate")); + goto on_return; + } + if (pj_stricmp2(&token, "UDP") != 0) { + // TRACE__((obj_name, "Expecting ICE UDP transport only in candidate")); + goto on_return; + } + + /* Priority */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE priority in candidate")); + goto on_return; + } + cand->prio = pj_strtoul(&token); + + /* Host */ + found_idx = pj_strtok(orig_input, &delim, &host, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE host in candidate")); + goto on_return; + } + /* Detect address family */ + if (pj_strchr(&host, ':')) + af = pj_AF_INET6(); + else + af = pj_AF_INET(); + /* Assign address */ + if (pj_sockaddr_init(af, &cand->addr, &host, 0)) { + // TRACE__((obj_name, "Invalid ICE candidate address")); + goto on_return; + } + + /* Port */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + host.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE port number in candidate")); + goto on_return; + } + pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)pj_strtoul(&token)); + + /* typ */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); + goto on_return; + } + if (pj_stricmp2(&token, "typ") != 0) { + // TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); + goto on_return; + } + + /* candidate type */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE candidate type in candidate")); + goto on_return; + } + + if (pj_stricmp2(&token, "host") == 0) { + cand->type = PJ_ICE_CAND_TYPE_HOST; + } else if (pj_stricmp2(&token, "srflx") == 0) { + cand->type = PJ_ICE_CAND_TYPE_SRFLX; + } else if (pj_stricmp2(&token, "relay") == 0) { + cand->type = PJ_ICE_CAND_TYPE_RELAYED; + } else if (pj_stricmp2(&token, "prflx") == 0) { + cand->type = PJ_ICE_CAND_TYPE_PRFLX; + } else { + printf("Invalid ICE candidate type %.*s in candidate", (int)token.slen, token.ptr); + goto on_return; + } + + return 0; + +on_return: + return -1; +} + +int pj_sdp_token_url_parse(const char *token_url, const char *type, char **addr, size_t *addr_len, uint16_t *port) +{ + if (token_url == NULL || type == NULL || addr == NULL || addr_len == NULL || port == NULL) { + printf("invalid param\n"); + return -1; + } + + char *paddr = (char*)token_url + strlen(type); + char *pport = NULL; + int i; + for (i = strlen(paddr); i > 0; i--) { + if (paddr[i] == ':') { + pport = paddr + i + 1; + break; + } + } + + if (pport == NULL) { + printf("invalid token url\n"); + return -1; + } + + *port = atoi(pport); + *addr_len = pport - paddr - 1; + if (paddr[0] == '[') { + paddr += 1; + *addr_len -= 2; + } + *addr = paddr; + return 0; +} + +bool pj_ice_session_create(pj_ice_session_cfg_t *pCfg, pj_ice_session_t **ppIceSession) +{ + pj_init(); + pjlib_util_init(); + pjnath_init(); + pj_log_set_level(0); + + pj_ice_session_t *pIceSession = malloc(sizeof(pj_ice_session_t)); + pIceSession->pPool = NULL; + pIceSession->pThread = NULL; + pIceSession->bThreadQuitFlag = false; + pIceSession->pIceSTransport = NULL; + pIceSession->bLastCand = false; + pIceSession->uComponentCount = 1; + pj_caching_pool_init(&pIceSession->cachePool, NULL, 0); + pj_ice_strans_cfg_default(&pIceSession->iceCfg); + pIceSession->iceCfg.stun_cfg.pf = &pIceSession->cachePool.factory; + pIceSession->pPool = pj_pool_create(&pIceSession->cachePool.factory, "ice_Pool", 512, 512, NULL); + pj_timer_heap_create(pIceSession->pPool, 100, &pIceSession->iceCfg.stun_cfg.timer_heap); + pj_ioqueue_create(pIceSession->pPool, 16, &pIceSession->iceCfg.stun_cfg.ioqueue); + + pj_ice_strans_cfg *pIceCfg = &pIceSession->iceCfg; + pIceCfg->opt.aggressive = PJ_FALSE; + pIceCfg->opt.trickle = PJ_ICE_SESS_TRICKLE_FULL; + ICE_WORKER_THREAD_PARAM *pIceThreadParam = malloc(sizeof(ICE_WORKER_THREAD_PARAM)); + pIceThreadParam->pIceSession = pIceSession; + pIceThreadParam->pCfg = pIceCfg; + pIceThreadParam->bThreadQuitFlag = false; + pIceSession->pIceThreadParam = pIceThreadParam; + // pj_thread_create(pIceSession->pPool, "ice_worker_thread", &ice_worker_thread, pIceThreadParam, 0, 0, + // &pIceSession->pThread); + // pj_str_t szDNSServers[2]; + // szDNSServers[0] = pj_str((char*)"8.8.8.8"); + // szDNSServers[1] = pj_str((char*)"144.144.144.144"); + // pj_dns_resolver_create(&pIceCfg->cachePool.factory, "resolver", 0, pIceCfg->iceCfg.stun_cfg.timer_heap, + // pIceCfg->iceCfg.stun_cfg.ioqueue, &pIceCfg->iceCfg.resolver); pj_dns_resolver_set_ns(pIceCfg->iceCfg.resolver, + // 1, szDNSServers, NULL); + *ppIceSession = pIceSession; + return true; +} + +bool pj_ice_session_destroy(pj_ice_session_t *pIceSession) +{ + pj_status_t status = PJ_SUCCESS; + g_bInited = false; + + pj_thread_register2(); + pIceSession->pIceThreadParam->bThreadQuitFlag = true; + if (pIceSession->pThread != NULL) { + pj_thread_join(pIceSession->pThread); + pj_thread_destroy(pIceSession->pThread); + pIceSession->pThread = NULL; + } + pj_pool_release(pIceSession->pPool); + free(pIceSession->pIceThreadParam); + pIceSession->pIceThreadParam = NULL; + + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + if (ice_st == NULL) { + PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first")); + return false; + } + + if (!pj_ice_strans_has_sess(ice_st)) { + PJ_LOG(1, (THIS_FILE, "Error: No ICE session, initialize first")); + return false; + } + + status = pj_ice_strans_stop_ice(ice_st); + if (status != PJ_SUCCESS) { + pj_print_error("error stopping session", status); + return false; + } else { + PJ_LOG(3, (THIS_FILE, "ICE session stopped")); + return true; + } +} + +bool pj_ice_session_init(pj_ice_session_t *pIceSession, pj_ice_session_cfg_t *pCfg) +{ + if (g_bInited) { + return true; + } else { + g_bInited = true; + } + + pj_ice_strans_cfg *pIceCfg = &pIceSession->iceCfg; + + // Get STUN server or TURN server information from cloud server + char *paddr = NULL; + size_t addrlen = 0; + uint16_t server_port = 0; + cJSON *el_root_token = cJSON_Parse(pCfg->server_tokens); + if (!cJSON_IsArray(el_root_token)) { + return -1; + } + cJSON *el_one_token; + cJSON_ArrayForEach(el_one_token, el_root_token) + { + if (!cJSON_IsObject(el_one_token)) { + continue; + } + cJSON *el_username = cJSON_GetObjectItemCaseSensitive(el_one_token, "username"); + cJSON *el_credential = cJSON_GetObjectItemCaseSensitive(el_one_token, "credential"); + cJSON *el_urls = cJSON_GetObjectItemCaseSensitive(el_one_token, "urls"); + if (!cJSON_IsString(el_urls)) { + continue; + } + char *p = el_urls->valuestring; + char *ptransport = strstr(p, "?transport="); + if (ptransport != NULL) { + char *ptransport_type = ptransport + strlen("?transport="); + if ((strncmp(ptransport_type, "tcp", strlen("tcp")) == 0) || + (strncmp(ptransport_type, "TCP", strlen("TCP")) == 0)) { + continue; + } + } + if (strncmp(p, "turn:", strlen("turn:")) == 0) { + if ((!cJSON_IsString(el_username)) || (!cJSON_IsString(el_credential))) + continue; + pj_str_t username = pj_str(el_username->valuestring); + pj_str_t credential = pj_str(el_credential->valuestring); + if (pj_sdp_token_url_parse(p, "turn:", &paddr, &addrlen, &server_port) == 0) { + pj_str_t pjstrServerHost; + pjstrServerHost.ptr = paddr; + pjstrServerHost.slen = addrlen; + printf("+ turn server: %.*s port:%d\n", (int)pjstrServerHost.slen, pjstrServerHost.ptr, server_port); + if (!is_ipv4(paddr) && !is_ipv6(paddr)) { + printf("- turn: %.*s is domain, ignore connect\n", (int)addrlen, paddr); + continue; + } + /*pIceCfg->turn.server = pj_str((char*)serverHost.base); + pIceCfg->turn.port = server_port;*/ + pIceCfg->turn_tp_cnt = 1; + pj_ice_strans_turn_cfg_default(&pIceCfg->turn_tp[0]); + pj_strdup_with_null(pIceSession->pPool, &pIceCfg->turn_tp[0].server, &pjstrServerHost); + pIceCfg->turn_tp[0].port = server_port; + pIceCfg->turn_tp[0].auth_cred.data.static_cred.username = pj_str(username.ptr); + pIceCfg->turn_tp[0].auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; + pIceCfg->turn_tp[0].auth_cred.data.static_cred.data = pj_str(credential.ptr); + } + + } else if (strncmp(p, "stun:", strlen("stun:")) == 0) { + if (pj_sdp_token_url_parse(p, "stun:", &paddr, &addrlen, &server_port) == 0) { + pj_str_t pjstrServerHost; + pjstrServerHost.ptr = paddr; + pjstrServerHost.slen = addrlen; + printf("+ stun server: %.*s port:%d\n", (int)pjstrServerHost.slen, pjstrServerHost.ptr, server_port); + if (!is_ipv4(paddr) && !is_ipv6(paddr)) { + printf("- stun: %.*s is domain, ignore connect\n", (int)pjstrServerHost.slen, pjstrServerHost.ptr); + continue; + } + pj_strdup_with_null(pIceSession->pPool, &pIceCfg->stun.server, &pjstrServerHost); + pIceCfg->stun.port = server_port; + pIceCfg->stun.cfg.ka_interval = KA_INTERVAL; + } + } else { + continue; + } + } // cJSON_ArrayForEach(el_one_token, el_root_token) + if (el_root_token != NULL) { + cJSON_Delete(el_root_token); + el_root_token = NULL; + } + + /* init the callback */ + pj_ice_strans_cb icecb; + pj_bzero(&icecb, sizeof(icecb)); + icecb.on_rx_data = pCfg->cb.ice_on_rx_data; + icecb.on_ice_complete = pCfg->cb.ice_on_ice_complete; + icecb.on_new_candidate = pCfg->cb.ice_on_new_candidate; + /* create the instance */ + pj_status_t status = pj_ice_strans_create("icedemo", &pIceSession->iceCfg, pIceSession->uComponentCount, + pCfg->user_data, &icecb, &pIceSession->pIceSTransport); + if (status != PJ_SUCCESS) { + return false; + } + + unsigned rolechar = pCfg->rolechar; + char *local_ufrag = pCfg->local_ufrag; + char *local_passwd = pCfg->local_passwd; + pj_ice_sess_role role = + (pj_tolower((pj_uint8_t)rolechar) == 'o' ? PJ_ICE_SESS_ROLE_CONTROLLING : PJ_ICE_SESS_ROLE_CONTROLLED); + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + if (ice_st == NULL) { + PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first")); + return false; + } + if (pj_ice_strans_has_sess(ice_st)) { + PJ_LOG(1, (THIS_FILE, "Error: Session already created")); + return false; + } + pj_str_t pjstrLocalUFrag = pj_str(local_ufrag); + pj_str_t pjstrLocalPasswd = pj_str(local_passwd); + status = pj_ice_strans_init_ice(ice_st, role, &pjstrLocalUFrag, &pjstrLocalPasswd); + if (status != PJ_SUCCESS) + pj_print_error("error creating session", status); + else + PJ_LOG(3, (THIS_FILE, "ICE session created")); + return true; +} + +bool pj_ice_session_add_remote_candidate(pj_ice_session_t *pIceSession, pj_str_t *rem_ufrag, pj_str_t *rem_passwd, + unsigned rcand_cnt, pj_ice_sess_cand rcand[], pj_bool_t rcand_end) +{ + pj_status_t status = PJ_FALSE; + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + if (ice_st == NULL) { + return false; + } + /* Update the checklist */ + status = pj_ice_strans_update_check_list(ice_st, rem_ufrag, rem_passwd, rcand_cnt, rcand, rcand_end); + if (status != PJ_SUCCESS) + return false; + /* Start ICE if both sides have sent their (initial) SDPs */ + if (!pj_ice_strans_sess_is_running(ice_st)) { + unsigned i = 0, comp_cnt = 0; + comp_cnt = pj_ice_strans_get_running_comp_cnt(ice_st); + // for (i = 0; i < comp_cnt; ++i) { + // if (tp_ice->last_send_cand_cnt[i] > 0) + // break; + // } + if (i != comp_cnt) { + pj_str_t rufrag; + pj_ice_strans_get_ufrag_pwd(ice_st, NULL, NULL, &rufrag, NULL); + if (rufrag.slen > 0) { + PJ_LOG(3, (THIS_FILE, "Trickle ICE starts connectivity check")); + status = pj_ice_strans_start_ice(ice_st, NULL, NULL, 0, NULL); + } + } + } + return true; +} + +bool pj_ice_session_sendto(pj_ice_session_t *pIceSession, void *pkt, uint32_t len) +{ + pj_thread_register2(); + + pj_status_t status = PJ_FALSE; + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + char szLCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + char szRCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + unsigned comp_id = 1; // Component starts with ID 1 + const pj_ice_sess_check *pIceSessCheck = pj_ice_strans_get_valid_pair(ice_st, comp_id); + pj_sockaddr_print(&pIceSessCheck->lcand->addr, szLCandAddr, sizeof(szLCandAddr), 3); + pj_sockaddr_print(&pIceSessCheck->rcand->addr, szRCandAddr, sizeof(szRCandAddr), 3); + status = pj_ice_strans_sendto2(ice_st, comp_id, pkt, len, &pIceSessCheck->rcand->addr, + pj_sockaddr_get_len(&pIceSessCheck->rcand->addr)); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + return false; + } + return true; +} diff --git a/src/tuya_p2p/base_ice/src/pj_ice.h b/src/tuya_p2p/base_ice/src/pj_ice.h new file mode 100755 index 000000000..171525df7 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_ice.h @@ -0,0 +1,40 @@ +#ifndef PJ_ICE_H_ +#define PJ_ICE_H_ +#include +#include +#include +#include +#include +#include + +typedef struct pj_ice_cb { + void (*ice_on_rx_data)(pj_ice_strans *ice_st, unsigned comp_id, void *buffer, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + void (*ice_on_new_candidate)(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t last); + void (*ice_on_ice_complete)(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); +} pj_ice_cb_t; + +typedef struct pj_ice_session_cfg { + pj_ice_cb_t cb; + unsigned rolechar; // Value: character 'o' represents Controlling role, other values represent Controlled role + char *local_ufrag; + char *local_passwd; + char server_tokens[2048]; + void *user_data; +} pj_ice_session_cfg_t; + +typedef struct pj_ice_session pj_ice_session_t; + +bool pj_thread_register2(); +int print_cand(char buffer[], unsigned maxlen, const pj_ice_sess_cand *cand); +int parse_cand(pj_pool_t *pool, const pj_str_t *orig_input, pj_ice_sess_cand *cand); + +bool pj_ice_session_create(pj_ice_session_cfg_t *pCfg, pj_ice_session_t **ppIceSession); +bool pj_ice_session_destroy(pj_ice_session_t *pIceSession); +bool pj_ice_session_init(pj_ice_session_t *pIceSession, pj_ice_session_cfg_t *pCfg); +bool pj_ice_session_add_remote_candidate(pj_ice_session_t *pIceSession, pj_str_t *rem_ufrag, pj_str_t *rem_passwd, + unsigned rcand_cnt, pj_ice_sess_cand rcand[], pj_bool_t rcand_end); +bool pj_ice_session_sendto(pj_ice_session_t *pIceSession, void *pkt, uint32_t len); +bool pj_ice_session_handle_events(pj_ice_session_t *pIceSession, unsigned max_msec, unsigned *p_count); + +#endif /* PJ_ICE_H_ */ \ No newline at end of file diff --git a/src/tuya_p2p/base_ice/src/pj_sdp.c b/src/tuya_p2p/base_ice/src/pj_sdp.c new file mode 100755 index 000000000..0046c8ab4 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sdp.c @@ -0,0 +1,48 @@ +#include "pj_sdp.h" + +#define SDP_TEMPLATE_PART_SESSION_HEADER \ + "v=0\r\n" \ + "o=- %lu 1 IN IP4 127.0.0.1\r\n" \ + "s=-\r\n" \ + "t=0 0\r\n" \ + "a=group:BUNDLE%s\r\n" \ + "a=msid-semantic: WMS %s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_HEADER "m=%s 9 %s%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT \ + "c=IN IP4 0.0.0.0\r\n" \ + "a=rtcp:9 IN IP4 0.0.0.0\r\n" \ + "a=ice-ufrag:%s\r\n" \ + "a=ice-pwd:%s\r\n" \ + "a=ice-options:trickle\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT_CANDIDATE "%s" + +#define SDP_TEMPLATE_PART_MEDIA_MID "a=mid:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP "a=rtpmap:%d %s %d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_KEY "a=aes-key:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_SSRC "a=ssrc:%u cname:%s\r\n" + +int pj_sdp_init(char *session_id, char *local_id, char *fingerprint, char *ufrag, char *password) +{ + return 0; +} + +int pj_sdp_deinit() +{ + return 0; +} + +int pj_sdp_set_aes_key(unsigned char *aes_key, uint32_t len) +{ + return 0; +} + +int pj_sdp_get_aes_key(unsigned char *aes_key, uint32_t len) +{ + return 0; +} diff --git a/src/tuya_p2p/base_ice/src/pj_sdp.h b/src/tuya_p2p/base_ice/src/pj_sdp.h new file mode 100755 index 000000000..1d5d4d986 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sdp.h @@ -0,0 +1,11 @@ +#ifndef PJ_SDP_H_ +#define PJ_SDP_H_ + +#include + +int pj_sdp_init(char *session_id, char *local_id, char *fingerprint, char *ufrag, char *password); +int pj_sdp_deinit(); +int pj_sdp_set_aes_key(unsigned char *aes_key, uint32_t len); +int pj_sdp_get_aes_key(unsigned char *aes_key, uint32_t len); + +#endif /* PJ_SDP_H_ */ diff --git a/src/tuya_p2p/base_ice/src/pj_sync_condition.c b/src/tuya_p2p/base_ice/src/pj_sync_condition.c new file mode 100755 index 000000000..706c6510f --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sync_condition.c @@ -0,0 +1,56 @@ +#include "pj_sync_condition.h" + +int sync_cond_init(sync_cond_t *pSyncCond) +{ + if (pthread_mutex_init(&pSyncCond->mutex, NULL) != 0) { + perror("mutex init failed"); + return -1; + } + + if (pthread_cond_init(&pSyncCond->cond, NULL) != 0) { + perror("cond init failed"); + pthread_mutex_destroy(&pSyncCond->mutex); + return -1; + } + + pSyncCond->condition_met = 0; + return 0; +} + +void sync_cond_notify(sync_cond_t *pSyncCond) +{ + pthread_mutex_lock(&pSyncCond->mutex); + + // Set condition to true + pSyncCond->condition_met = 1; + + // Notify waiting threads (you can choose one of the following methods) + pthread_cond_signal(&pSyncCond->cond); // Wake up at least one waiting thread + // pthread_cond_broadcast(&pSyncCond->cond); // Wake up all waiting threads + + pthread_mutex_unlock(&pSyncCond->mutex); +} + +// Wait condition function +void sync_cond_wait(sync_cond_t *pSyncCond) +{ + pthread_mutex_lock(&pSyncCond->mutex); + + while (pSyncCond->condition_met == 0) { + // Wait for condition variable, will automatically release mutex and reacquire on return + pthread_cond_wait(&pSyncCond->cond, &pSyncCond->mutex); + } + + // Reset condition flag (if needed) + pSyncCond->condition_met = 0; + + pthread_mutex_unlock(&pSyncCond->mutex); +} + +// Cleanup function +void sync_cond_clean(sync_cond_t *pSyncCond) +{ + pthread_mutex_destroy(&pSyncCond->mutex); + pthread_cond_destroy(&pSyncCond->cond); + return; +} \ No newline at end of file diff --git a/src/tuya_p2p/base_ice/src/pj_sync_condition.h b/src/tuya_p2p/base_ice/src/pj_sync_condition.h new file mode 100755 index 000000000..906f4b62f --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sync_condition.h @@ -0,0 +1,18 @@ +#ifndef __SYNC_CONDITION_H__ +#define __SYNC_CONDITION_H__ + +#include +#include + +typedef struct tagSyncCondition { + pthread_mutex_t mutex; + pthread_cond_t cond; + int condition_met; // Condition flag +} sync_cond_t; + +int sync_cond_init(sync_cond_t *pSyncCond); +void sync_cond_notify(sync_cond_t *pSyncCond); +void sync_cond_wait(sync_cond_t *pSyncCond); +void sync_cond_clean(sync_cond_t *pSyncCond); + +#endif /* __SYNC_CONDITION_H__ */ \ No newline at end of file diff --git a/src/tuya_p2p/base_ice/src/queue.h b/src/tuya_p2p/base_ice/src/queue.h new file mode 100755 index 000000000..2820efa27 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/queue.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2013, Ben Noordhuis + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUEUE_H_ +#define QUEUE_H_ + +#include + +typedef void *QUEUE[2]; + +/* Private macros. */ +#define QUEUE_NEXT(q) (*(QUEUE **)&((*(q))[0])) +#define QUEUE_PREV(q) (*(QUEUE **)&((*(q))[1])) +#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q))) +#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q))) + +/* Public macros. */ +#define QUEUE_DATA(ptr, type, field) ((type *)((char *)(ptr)-offsetof(type, field))) + +/* Important note: mutating the list while QUEUE_FOREACH is + * iterating over its elements results in undefined behavior. + */ +#define QUEUE_FOREACH(q, h) for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q)) + +#define QUEUE_EMPTY(q) ((const QUEUE *)(q) == (const QUEUE *)QUEUE_NEXT(q)) + +#define QUEUE_HEAD(q) (QUEUE_NEXT(q)) + +#define QUEUE_INIT(q) \ + do { \ + QUEUE_NEXT(q) = (q); \ + QUEUE_PREV(q) = (q); \ + } while (0) + +#define QUEUE_ADD(h, n) \ + do { \ + QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \ + QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV(h) = QUEUE_PREV(n); \ + QUEUE_PREV_NEXT(h) = (h); \ + } while (0) + +#define QUEUE_SPLIT(h, q, n) \ + do { \ + QUEUE_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(n) = (n); \ + QUEUE_NEXT(n) = (q); \ + QUEUE_PREV(h) = QUEUE_PREV(q); \ + QUEUE_PREV_NEXT(h) = (h); \ + QUEUE_PREV(q) = (n); \ + } while (0) + +#define QUEUE_MOVE(h, n) \ + do { \ + if (QUEUE_EMPTY(h)) \ + QUEUE_INIT(n); \ + else { \ + QUEUE *q = QUEUE_HEAD(h); \ + QUEUE_SPLIT(h, q, n); \ + } \ + } while (0) + +#define QUEUE_INSERT_HEAD(h, q) \ + do { \ + QUEUE_NEXT(q) = QUEUE_NEXT(h); \ + QUEUE_PREV(q) = (h); \ + QUEUE_NEXT_PREV(q) = (q); \ + QUEUE_NEXT(h) = (q); \ + } while (0) + +#define QUEUE_INSERT_TAIL(h, q) \ + do { \ + QUEUE_NEXT(q) = (h); \ + QUEUE_PREV(q) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(q) = (q); \ + QUEUE_PREV(h) = (q); \ + } while (0) + +#define QUEUE_INSERT_BEFORE(h, q) QUEUE_INSERT_TAIL(h, q) + +#define QUEUE_REMOVE(q) \ + do { \ + QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \ + QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \ + } while (0) + +#endif /* QUEUE_H_ */ diff --git a/src/tuya_p2p/base_ice/src/tuya_error.c b/src/tuya_p2p/base_ice/src/tuya_error.c new file mode 100755 index 000000000..dab66cf1a --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_error.c @@ -0,0 +1,63 @@ + +#include +#include "tuya_error.h" + +#define BUILD_ERR(code, msg) \ + { \ + code, msg " (" #code ")" \ + } +static const struct { + int code; + const char *msg; +} err_str[] = { + BUILD_ERR(TUYA_P2P_EINSTUNMSG, "Invalid STUN message"), + BUILD_ERR(TUYA_P2P_EINSTUNMSGLEN, "Invalid STUN message length"), + BUILD_ERR(TUYA_P2P_EINSTUNMSGTYPE, "Invalid or unexpected STUN message type"), + BUILD_ERR(TUYA_P2P_ESTUNTIMEDOUT, "STUN transaction has timed out"), + + BUILD_ERR(TUYA_P2P_ESTUNTOOMANYATTR, "Too many STUN attributes"), + BUILD_ERR(TUYA_P2P_ESTUNINATTRLEN, "Invalid STUN attribute length"), + BUILD_ERR(TUYA_P2P_ESTUNDUPATTR, "Found duplicate STUN attribute"), + BUILD_ERR(TUYA_P2P_ESTUNFINGERPRINT, "STUN FINGERPRINT verification failed"), + BUILD_ERR(TUYA_P2P_ESTUNMSGINTPOS, "Invalid STUN attribute after MESSAGE-INTEGRITY"), + BUILD_ERR(TUYA_P2P_ESTUNFINGERPOS, "Invalid STUN attribute after FINGERPRINT"), + BUILD_ERR(TUYA_P2P_ESTUNNOMAPPEDADDR, "STUN (XOR-)MAPPED-ADDRESS attribute not found"), + BUILD_ERR(TUYA_P2P_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + BUILD_ERR(TUYA_P2P_EINVAF, "Invalid address family value in STUN message"), + BUILD_ERR(TUYA_P2P_ESTUNINSERVER, "Invalid STUN server or server not configured"), + BUILD_ERR(TUYA_P2P_ESTUNDESTROYED, "STUN object has been destoyed"), + + BUILD_ERR(TUYA_P2P_ENOICE, "ICE session not available"), + BUILD_ERR(TUYA_P2P_EICEINPROGRESS, "ICE check is in progress"), + BUILD_ERR(TUYA_P2P_EICEFAILED, "ICE connectivity check has failed"), + BUILD_ERR(TUYA_P2P_EICEMISMATCH, "Default destination does not match any ICE candidates"), + BUILD_ERR(TUYA_P2P_EICEINCOMPID, "Invalid ICE component ID"), + BUILD_ERR(TUYA_P2P_EICEINCANDID, "Invalid ICE candidate ID"), + BUILD_ERR(TUYA_P2P_EICEINSRCADDR, "Source address mismatch"), + BUILD_ERR(TUYA_P2P_EICEMISSINGSDP, "Missing ICE SDP attribute"), + BUILD_ERR(TUYA_P2P_EICEINCANDSDP, "Invalid SDP candidate attribute"), + BUILD_ERR(TUYA_P2P_EICENOHOSTCAND, "No host candidate associated with srflx"), + BUILD_ERR(TUYA_P2P_EICENOMTIMEOUT, "Controlled timed-out waiting for Controlling to send nominated check"), + + BUILD_ERR(TUYA_P2P_ETURNINTP, "Invalid or unsupported TURN transport")}; + +void tuya_p2p_strerror(int32_t statcode, char *buf, size_t bufsize) +{ + if (buf == NULL || bufsize <= 0) { + return; + } + + if (statcode == TUYA_P2P_SUCCESS) { + snprintf(buf, bufsize, "Success"); + } else { + int i; + for (i = 0; i < sizeof(err_str) / sizeof(err_str[0]); ++i) { + if (err_str[i].code == statcode) { + snprintf(buf, bufsize, "%s", err_str[i].msg); + return; + } + } + } + snprintf(buf, bufsize, "Unknown tuya p2p error %d", statcode); + return; +} diff --git a/src/tuya_p2p/base_ice/src/tuya_error.h b/src/tuya_p2p/base_ice/src/tuya_error.h new file mode 100755 index 000000000..4864bb01a --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_error.h @@ -0,0 +1,82 @@ + + +#ifndef __TUYA_ERROR_H__ +#define __TUYA_ERROR_H__ + +#include +#include + +#define TUYA_P2P_SUCCESS 0 + +#define TUYA_P2P_ERRNO_START 1000 +#define TUYA_P2P_ERRNO_STUN_START 10000 +#define TUYA_P2P_ERRNO_RTP_START 120000 +#define TUYA_P2P_ERR_MSG_SIZE 80 +#define TUYA_P2P_STATUS_FROM_STUN_CODE(code) (code) + +#define TUYA_P2P_EBUG (TUYA_P2P_ERRNO_START + 0) /* 1000 */ +#define TUYA_P2P_EUNKNOWN (TUYA_P2P_ERRNO_START + 1) /* 1001 */ +#define TUYA_P2P_EPENDING (TUYA_P2P_ERRNO_START + 2) /* 1002 */ +#define TUYA_P2P_ETOOMANYCONN (TUYA_P2P_ERRNO_START + 3) /* 1003 */ +#define TUYA_P2P_EINVAL (TUYA_P2P_ERRNO_START + 4) /* 1004 */ +#define TUYA_P2P_ENAMETOOLONG (TUYA_P2P_ERRNO_START + 5) /* 1005 */ +#define TUYA_P2P_ENOTFOUND (TUYA_P2P_ERRNO_START + 6) /* 1006 */ +#define TUYA_P2P_ETOOMANY (TUYA_P2P_ERRNO_START + 10) /* 1010 */ +#define TUYA_P2P_EBUSY (TUYA_P2P_ERRNO_START + 11) /* 1011 */ +#define TUYA_P2P_ENOTSUP (TUYA_P2P_ERRNO_START + 12) /* 1012 */ +#define TUYA_P2P_EINVALIDOP (TUYA_P2P_ERRNO_START + 13) /* 1013 */ +#define TUYA_P2P_ETOOSMALL (TUYA_P2P_ERRNO_START + 19) /* 1019 */ +#define TUYA_P2P_EIGNORED (TUYA_P2P_ERRNO_START + 20) /* 1020 */ +#define TUYA_P2P_SOCKETCREATEFAIL (TUYA_P2P_ERRNO_START + 30) /* 1030 */ + +#define TUYA_P2P_INVALID_PARAM (TUYA_P2P_ERRNO_START + 31) /* 1031 */ +#define TUYA_P2P_MALLOC_FAILED (TUYA_P2P_ERRNO_START + 32) /* 1032 */ +#define TUYA_P2P_LOG_BUFFER_FULL (TUYA_P2P_ERRNO_START + 33) /* 1033 */ + +/************************************************************ + * STUN MESSAGING ERRORS + ***********************************************************/ + +#define TUYA_P2P_EINSTUNMSG (TUYA_P2P_ERRNO_STUN_START + 1) /* 10001 */ +#define TUYA_P2P_EINSTUNMSGLEN (TUYA_P2P_ERRNO_STUN_START + 2) /* 10002 */ +#define TUYA_P2P_EINSTUNMSGTYPE (TUYA_P2P_ERRNO_STUN_START + 3) /* 10003 */ +#define TUYA_P2P_ESTUNTIMEDOUT (TUYA_P2P_ERRNO_STUN_START + 4) /* 10004 */ +#define TUYA_P2P_ESTUNTOOMANYATTR (TUYA_P2P_ERRNO_STUN_START + 21) /* 10021 */ +#define TUYA_P2P_ESTUNINATTRLEN (TUYA_P2P_ERRNO_STUN_START + 22) /* 10022 */ +#define TUYA_P2P_ESTUNDUPATTR (TUYA_P2P_ERRNO_STUN_START + 23) /* 10023 */ +#define TUYA_P2P_ESTUNFINGERPRINT (TUYA_P2P_ERRNO_STUN_START + 30) /* 10030 */ +#define TUYA_P2P_ESTUNMSGINTPOS (TUYA_P2P_ERRNO_STUN_START + 31) /* 10031 */ +#define TUYA_P2P_ESTUNFINGERPOS (TUYA_P2P_ERRNO_STUN_START + 33) /* 10033 */ +#define TUYA_P2P_ESTUNNOMAPPEDADDR (TUYA_P2P_ERRNO_STUN_START + 40) /* 10040 */ +#define TUYA_P2P_ESTUNIPV6NOTSUPP (TUYA_P2P_ERRNO_STUN_START + 41) /* 10041 */ +#define TUYA_P2P_EINVAF (TUYA_P2P_ERRNO_STUN_START + 42) /* 10042 */ +#define TUYA_P2P_ESTUNINSERVER (TUYA_P2P_ERRNO_STUN_START + 50) /* 10050 */ + +/************************************************************ + * STUN SESSION/TRANSPORT ERROR CODES + ***********************************************************/ +#define TUYA_P2P_ESTUNDESTROYED (TUYA_P2P_ERRNO_STUN_START + 60) /* 10060 */ +#define TUYA_P2P_ENOICE (TUYA_P2P_ERRNO_STUN_START + 80) /* 10080 */ +#define TUYA_P2P_EICEINPROGRESS (TUYA_P2P_ERRNO_STUN_START + 81) /* 10081 */ +#define TUYA_P2P_EICEFAILED (TUYA_P2P_ERRNO_STUN_START + 82) /* 10082 */ +#define TUYA_P2P_EICEMISMATCH (TUYA_P2P_ERRNO_STUN_START + 83) /* 10083 */ +#define TUYA_P2P_EICEINCOMPID (TUYA_P2P_ERRNO_STUN_START + 86) /* 10086 */ +#define TUYA_P2P_EICEINCANDID (TUYA_P2P_ERRNO_STUN_START + 87) /* 10087 */ +#define TUYA_P2P_EICEINSRCADDR (TUYA_P2P_ERRNO_STUN_START + 88) /* 10088 */ +#define TUYA_P2P_EICEMISSINGSDP (TUYA_P2P_ERRNO_STUN_START + 90) /* 10090 */ +#define TUYA_P2P_EICEINCANDSDP (TUYA_P2P_ERRNO_STUN_START + 91) /* 10091 */ +#define TUYA_P2P_EICENOHOSTCAND (TUYA_P2P_ERRNO_STUN_START + 92) /* 10092 */ +#define TUYA_P2P_EICENOMTIMEOUT (TUYA_P2P_ERRNO_STUN_START + 93) /* 10093 */ + +/************************************************************ + * TURN ERROR CODES + ***********************************************************/ +#define TUYA_P2P_ETURNINTP (TUYA_P2P_ERRNO_STUN_START + 120) /* 10120 */ + +#define TUYA_P2P_RTP_EINPACK (TUYA_P2P_ERRNO_RTP_START + 121) /* 120121 */ +#define TUYA_P2P_RTP_EINVER (TUYA_P2P_ERRNO_RTP_START + 122) /* 120122 */ +#define TUYA_P2P_RTP_EINLEN (TUYA_P2P_ERRNO_RTP_START + 125) /* 120125 */ + +extern void tuya_p2p_strerror(int32_t statcode, char *buf, size_t bufsize); + +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_log.h b/src/tuya_p2p/base_ice/src/tuya_log.h new file mode 100755 index 000000000..2315c148d --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_log.h @@ -0,0 +1,20 @@ + + +#ifndef __TUYA_LOG_H__ +#define __TUYA_LOG_H__ + +#include +#include +#include +#include "tuya_media_service_rtc.h" + +#define tuya_p2p_log_trace(...) tuya_p2p_log_log(TUYA_P2P_LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_debug(...) tuya_p2p_log_log(TUYA_P2P_LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_info(...) tuya_p2p_log_log(TUYA_P2P_LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_warn(...) tuya_p2p_log_log(TUYA_P2P_LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_error(...) tuya_p2p_log_log(TUYA_P2P_LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_fatal(...) tuya_p2p_log_log(TUYA_P2P_LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) + +void tuya_p2p_log_log(int level, const char *file, int line, const char *fmt, ...); + +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c b/src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c new file mode 100755 index 000000000..77a7e3f8c --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c @@ -0,0 +1,2077 @@ +#include "tuya_media_service_rtc.h" +#include "tal_mutex.h" +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#ifndef __APPLE__ +#include +#endif +#include "ikcp.h" +#include "mbedtls/aes.h" +#include "mbedtls/md.h" +#include "tuya_log.h" +#include "tuya_misc.h" +#include "cJSON.h" +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) +#include "mbedtls/certs.h" +#endif +#include "mbedtls/ssl.h" +#include "mbedtls/timing.h" +#include "tuya_sdp.h" +#include "pj_ice.h" +#include "pj_sync_condition.h" +#include +#include "tal_log.h" + +#define IKCP_PACKET_HEADER_SIZE 24 +#define TUYA_P2P_SEND_BUFFER_SIZE_MAX (800 * 1024) +#define TUYA_P2P_SEND_BUFFER_SIZE_MIN (50 * 1024) +#define TUYA_P2P_RECV_BUFFER_SIZE_MAX (800 * 1024) +#define TUYA_P2P_RECV_BUFFER_SIZE_MIN (50 * 1024) +#define RTC_SESSION_RUN_INTERVAL_MS 5 +#define SRTP_MASTER_KEY_LENGTH 16 +#define SRTP_MASTER_SALT_LENGTH 14 +#define SRTP_MASTER_LENGTH (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH) +#define ENCRYPT_MD5_LEN 16 + +// CMD is transmitted using kcp's channel number field, and kcp uses little endian +#define RTC_CHANNEL_CMD (0x010000F3) +#define RTC_CMD_SIGNALING (0x0001) + +#define P2P_UPLOAD_LOG_MASK_OPEN 0x01 +#define P2P_UPLOAD_LOG_MASK_HANDSHAKE 0x02 +#define P2P_UPLOAD_LOG_MASK_CLOSE 0x04 +#define P2P_UPLOAD_LOG_MASK_ACTIVATE 0x08 + +#define RTC_TOKEN_REFRESH_INTERVAL_SECONDS 600 + +#define P2P_DEFAULT_FRAGEMENT_LEN 1300 + +typedef enum rtc_session_close_reason { + RTC_SESSION_CLOSE_REASON_OK = 0, + RTC_SESSION_CLOSE_REASON_ICE_FAILED = 1, + RTC_SESSION_CLOSE_REASON_DTLS_HANDSHAKE_FAILED = 2, + RTC_SESSION_CLOSE_REASON_LOCAL_CANCEL = 3, + RTC_SESSION_CLOSE_REASON_LOCAL_CLOSE = 4, + RTC_SESSION_CLOSE_REASON_REMOTE_CLOSE = 5, + RTC_SESSION_CLOSE_REASON_KEEPALIVE_TIMEOUT = 6, + RTC_SESSION_CLOSE_REASON_AUTH_FAILED = 7, + RTC_SESSION_CLOSE_REASON_MEMORY_ALLOC = 8, + RTC_SESSION_CLOSE_REASON_DTLS_HANDSHAKE_FAILED_FINGERPRINT = 9, + RTC_SESSION_CLOSE_REASON_ICE_UDP_TCP_ALL_FAILED = 10, + RTC_SESSION_CLOSE_REASON_RESET = 11, + RTC_SESSION_CLOSE_REASON_REFUSED = 12, + RTC_SESSION_CLOSE_REASON_PRE_CMD_TIMEOUT = 13, + RTC_SESSION_CLOSE_REASON_GET_TOKEN_TIMEOUT = 14, + RTC_SESSION_CLOSE_REASON_RESERVE_TIMEOUT = 15, + RTC_SESSION_CLOSE_REASON_PRECONNECT_UNSUPPORTED = 16, + RTC_SESSION_CLOSE_REASON_HTTP_FAILED = 17, + RTC_SESSION_CLOSE_REASON_PRE_MESS = 18, + RTC_SESSION_CLOSE_REASON_SECURITY_NEGOTIATE_FAIL = 19, + RTC_SESSION_CLOSE_REASON_INIT_MBEDTLS_MD_AND_AES = 20, + RTC_SESSION_CLOSE_REASON_DTLS_HANDSHAKE_TIMEOUT = 21, + RTC_SESSION_CLOSE_REASON_UNDEFINED = 99 +} rtc_session_close_reason_e; + +typedef struct tagTuyaBuf { + char *base; + size_t len; +} tuya_uv_buf_t; + +typedef struct tuya_p2p_rtc_dtls_cert { + unsigned char cert[8 * 1024]; + unsigned char pkey[8 * 1024]; + char fingerprint[1024]; + int cert_len; + int pkey_len; +} tuya_p2p_rtc_dtls_cert_t; + +typedef struct rtc_channel { + struct tuya_p2p_rtc_session *rtc; + // tuya_mbuf_queue_t *send_queue; + // tuya_mbuf_queue_t *recv_queue; + int has_receiver; + ikcpcb *kcp; + int channel_id; + uint32_t has_sent_to_tcp; + uint32_t highest_seq_tcp_has_sent; + int64_t write_bytes; + int64_t read_bytes; + int64_t send_bytes; + int64_t recv_bytes; + int64_t socket_send_bytes; + int64_t socket_recv_bytes; + int64_t first_send_time_ms; + int64_t first_write_time_ms; + int64_t first_read_time_ms; + int64_t first_read_try_time_ms; + int64_t first_data_time_ms; + + void *aes_ctx_enc; + void *aes_ctx_dec; +} rtc_channel_t; + +#if (MBEDTLS_VERSION_NUMBER > 0x03000000) +#define MBEDTLS_TLS_SRTP_MAX_KEY_MATERIAL_LENGTH 60 +typedef struct dtls_srtp_keys { + unsigned char master_secret[48]; + unsigned char randbytes[64]; + mbedtls_tls_prf_types tls_prf_type; +} dtls_srtp_keys; +#endif +typedef struct rtc_dtls { + int inited; + int remote_cert_verified; + tuya_p2p_rtc_dtls_cert_t cert; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt x509_crt; + mbedtls_pk_context pkey; + mbedtls_timing_delay_context timer; +#if (MBEDTLS_VERSION_NUMBER > 0x03000000) + dtls_srtp_keys dtls_srtp_keying; +#endif +} rtc_dtls_t; + +// typedef struct rtc_srtp { +// int inited; +// int srtp_profile; +// unsigned char remote_policy_key[SRTP_MASTER_LENGTH]; +// unsigned char local_policy_key[SRTP_MASTER_LENGTH]; +// srtp_policy_t remote_policy; +// srtp_policy_t local_policy_audio; +// srtp_policy_t local_policy_video; +// srtp_policy_t local_policy_video_rtx; +// srtp_ctx_t *srtp_sess_in; +// srtp_ctx_t *srtp_sess_out; +// } rtc_srtp_t; + +typedef struct rtc_transport { + struct { + uint32_t ice; + uint32_t udp; + uint32_t tcp; + } water_level; +} rtc_transport_t; + +typedef enum { + MSG_TYPE_SIGNALING, + MSG_TYPE_CONTROL, + MSG_TYPE_REPORT, + MSG_TYPE_CERT, + MSG_TYPE_HTTP, + MSG_TYPE_STATE, +} msg_type_e; + +typedef enum { PJ_ROLE_CALLER, PJ_ROLE_CALLEE } pj_role_e; + +typedef struct tuya_p2p_rtc_session_cfg { + // tuya_uv_loop_t *loop; + uint32_t offline_timeout_seconds; + uint32_t connect_limit_time_ms; + uint32_t lan_mode; + int p2p_skill; + int preconnect_enable; + int is_pre; + int is_webrtc; + pj_role_e role; + char local_id[64]; // Added by Langdon + char remote_id[64]; + char session_id[64]; + char connect_session[64]; + char connect_api[64]; + char dev_id[64]; + char node_id[64]; + char moto_id[64]; + char trace_id[256]; + char auth[128]; + char ice_ufrag[32]; + char ice_password[32]; + char aes_key[64]; + uint32_t channel_number; + int32_t stream_type; + int32_t is_replay; + char start_time[32]; + char end_time[32]; + char ice_server_tokens[2048]; + char udp_server_tokens[2048]; + char tcp_server_tokens[2048]; + // rtc_token_t *rtc_token; + // rtc_token_type_e token_type; + // tuya_p2p_rtc_security_level_e security_level; + int security_level; +} rtc_session_cfg_t; + +typedef struct tuya_p2p_rtc_session { + tuya_p2p_rtc_cb_t cb; // Callback interface + + int ref_cnt; + pthread_mutex_t ref_lock; + + sync_cond_t syncCondExit; + + rtc_sdp_t local_sdp; + rtc_sdp_t remote_sdp; + pjmedia_sdp_session *pLocalSdp; + pjmedia_sdp_session *pRemoteSdp; + pthread_mutex_t channel_lock; + rtc_channel_t *channels; + // tuya_uv_timer_t *te; + // rtc_state_entry_t state; + rtc_session_cfg_t cfg; + void *queue[2]; + + int active_handle; + int local_cmd_seq; + + // kcp channel + unsigned char aes_key[16]; + unsigned char iv[16]; + mbedtls_md_info_t *md_info; + mbedtls_md_context_t md_ctx; + + struct { + char recv_buf[4096]; + uint32_t recv_already; + } cmd_channel; + + pj_ice_session_t *pIce; + pthread_t tid; + bool bQuitKCPThread; +} tuya_p2p_rtc_session_t; + +tuya_p2p_rtc_options_t g_options; +static uint32_t g_uP2PSkill = TUYA_P2P_SDK_SKILL_BASIC /*TUYA_P2P_SDK_SKILL_NUMBER*/; +tuya_p2p_rtc_session_t *g_pRtcSession = NULL; +MUTEX_HANDLE g_p2p_session_mutex = NULL; +rtc_session_cfg_t cfg; +pj_ice_session_cfg_t iceSessionCfg; + +static const unsigned char KCP_CMD_PUSH = 81; // cmd: push data +static const unsigned char KCP_CMD_ACK = 82; // cmd: ack +static const unsigned char KCP_CMD_WASK = 83; // cmd: window probe (ask) +static const unsigned char KCP_CMD_WINS = 84; // cmd: window size (tell) + +sync_cond_t g_syncCond; + +#define KA_INTERVAL 300 +#define THIS_FILE "tuya_media_service_rtc2.c" + +void ice_on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); +void ice_on_new_candidate(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t last); +void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, void *buffer, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + +tuya_p2p_rtc_session_t *ctx_session_create(rtc_session_cfg_t *cfg, rtc_state_e state, int32_t *err_code); +void ctx_session_destroy(tuya_p2p_rtc_session_t *rtc); +void ctx_session_channel_set_send_time(struct rtc_channel *chan); +int ctx_session_channel_process_data(struct rtc_channel *chan, char *data, int len); +int ctx_session_channel_process_pkt(void *user, int length, const char *input, char *output); +int ctx_session_send_sdp(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg); // For example, send Answer SDP +int ctx_session_send_candidate(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg, char *cand_str); +int ctx_session_add_remote_candidate(tuya_p2p_rtc_session_t *rtc, rtc_sdp_t *remote_sdp, char *candidate); +int ctx_session_send_suspend_resp(tuya_p2p_rtc_session_t *rtc, int error); +int ctx_session_send_disconnect(tuya_p2p_rtc_session_t *rtc, int32_t close_reason_local, rtc_session_close_reason_e close_reason); +int ctx_session_send_signaling(tuya_p2p_rtc_session_t *rtc, char *signaling); +char *ctx_signaling_add_path(char *signaling, char *path); +int tuya_p2p_rtc_channels_init(tuya_p2p_rtc_session_t *rtc); +void tuya_p2p_rtc_channels_destroy(tuya_p2p_rtc_session_t *rtc); + +void rtc_process_kcp_data(tuya_p2p_rtc_session_t *rtc, const tuya_uv_buf_t *pkt); + +int rtc_init_mbedtls_md_and_aes(tuya_p2p_rtc_session_t *rtc); +int rtc_channel_aes_init(rtc_channel_t *chan); +int rtc_crypt_encrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output); +int rtc_crypt_decrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output); +int rtc_channel_aes_uninit(struct rtc_channel *chan); + +void *rtc_worker_thread(void *arg); + +void rtc_ref_cnt_add(tuya_p2p_rtc_session_t *rtc); +void rtc_ref_cnt_del(tuya_p2p_rtc_session_t *rtc); +int rtc_ref_cnt_get(tuya_p2p_rtc_session_t *rtc); + +int32_t tuya_p2p_rtc_init(tuya_p2p_rtc_options_t *opt) +{ + sync_cond_init(&g_syncCond); + memcpy(&g_options, opt, sizeof(tuya_p2p_rtc_options_t)); + g_options.preconnect_enable = false; // Disable the use of pre-connection + // g_pRtcSession->cb = opt->cb; + + // int i; + // for (i = 0; i < TUYA_P2P_CHANNEL_NUMBER_MAX; i++) { + // if (i < g_options.max_channel_number) { + // int send_buffer_size_min = TUYA_P2P_SEND_BUFFER_SIZE_MIN; + // int recv_buffer_size_min = TUYA_P2P_RECV_BUFFER_SIZE_MIN; + // switch (i) { + // case 1: + // case 3: + // send_buffer_size_min = 500 * 1024; + // recv_buffer_size_min = 500 * 1024; + // break; + // default: + // break; + // } + // ctx->opt.send_buf_size[i] = ctx->opt.send_buf_size[i] < send_buffer_size_min + // ? send_buffer_size_min + // : ctx->opt.send_buf_size[i]; + // ctx->opt.recv_buf_size[i] = ctx->opt.recv_buf_size[i] < recv_buffer_size_min + // ? recv_buffer_size_min + // : ctx->opt.recv_buf_size[i]; + // ctx->opt.send_buf_size[i] = ctx->opt.send_buf_size[i] > TUYA_P2P_SEND_BUFFER_SIZE_MAX + // ? TUYA_P2P_SEND_BUFFER_SIZE_MAX + // : ctx->opt.send_buf_size[i]; + // ctx->opt.recv_buf_size[i] = ctx->opt.recv_buf_size[i] > TUYA_P2P_RECV_BUFFER_SIZE_MAX + // ? TUYA_P2P_RECV_BUFFER_SIZE_MAX + // : ctx->opt.recv_buf_size[i]; + // } else { + // ctx->opt.send_buf_size[i] = 0; + // ctx->opt.recv_buf_size[i] = 0; + // } + // } + + // if (ctx->opt.video_bitrate_kbps < TUYA_P2P_VIDEO_BITRATE_MIN) { + // ctx->opt.video_bitrate_kbps = TUYA_P2P_VIDEO_BITRATE_MIN; + // } + // if (ctx->opt.video_bitrate_kbps > TUYA_P2P_VIDEO_BITRATE_MAX) { + // ctx->opt.video_bitrate_kbps = TUYA_P2P_VIDEO_BITRATE_MAX; + // } + + return 0; +} + +int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason) +{ + if (g_pRtcSession == NULL) { + return TUYA_P2P_ERROR_NOT_INITIALIZED; + } + tuya_p2p_log_info("rtc session %08x close\n", handle); + ctx_session_send_disconnect(g_pRtcSession, reason, RTC_SESSION_CLOSE_REASON_LOCAL_CLOSE); + tuya_p2p_log_info("rtc session %08x close over\n", handle); + return 0; +} + +static int tuya_p2p_process_signal_msg(char *msg, int msglen) +{ + (void)msglen; + cJSON *root = cJSON_Parse(msg); + if (root == NULL) { + printf("invalid webrtc signaling: not a json\n=========\n%s\n==========\n", msg); + return -1; + } + + // parse header + cJSON *el_header = cJSON_GetObjectItemCaseSensitive(root, "header"); + if (!cJSON_IsObject(el_header)) { + printf("invalid signaling: invalid json, no header field\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + cJSON *el_from = cJSON_GetObjectItemCaseSensitive(el_header, "from"); + cJSON *el_to = cJSON_GetObjectItemCaseSensitive(el_header, "to"); + cJSON *el_node_id = cJSON_GetObjectItemCaseSensitive(el_header, "sub_dev_id"); + cJSON *el_sessionid = cJSON_GetObjectItemCaseSensitive(el_header, "sessionid"); + cJSON *el_trace_id = cJSON_GetObjectItemCaseSensitive(el_header, "trace_id"); + cJSON *el_moto_id = cJSON_GetObjectItemCaseSensitive(el_header, "moto_id"); + cJSON *el_path = cJSON_GetObjectItemCaseSensitive(el_header, "path"); + cJSON *el_type = cJSON_GetObjectItemCaseSensitive(el_header, "type"); + cJSON *el_is_pre = cJSON_GetObjectItemCaseSensitive(el_header, "is_pre"); + cJSON *el_p2p_skill = cJSON_GetObjectItemCaseSensitive(el_header, "p2p_skill"); + cJSON *el_security_level = cJSON_GetObjectItemCaseSensitive(el_header, "security_level"); + if ((!cJSON_IsString(el_from)) || (!cJSON_IsString(el_to)) || (!cJSON_IsString(el_sessionid)) || + (!(cJSON_IsString(el_type)))) { + printf("invalid signaling: invalid header\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + char *remote_id = el_from->valuestring; + char *local_id = el_to->valuestring; + char *session_id = el_sessionid->valuestring; + char *trace_id = cJSON_IsString(el_trace_id) ? el_trace_id->valuestring : ""; + char *moto_id = cJSON_IsString(el_moto_id) ? el_moto_id->valuestring : ""; + char *str_path = cJSON_IsString(el_path) ? el_path->valuestring : ""; + char *node_id = cJSON_IsString(el_node_id) ? el_node_id->valuestring : ""; + char *type = el_type->valuestring; + int is_pre = cJSON_IsNumber(el_is_pre) ? el_is_pre->valueint : 0; + int p2p_skill = cJSON_IsNumber(el_p2p_skill) ? el_p2p_skill->valueint : 0; + + // parse msg + cJSON *el_msg = cJSON_GetObjectItemCaseSensitive(root, "msg"); + if (!cJSON_IsObject(el_msg)) { + printf("invalid signaling: invalid json, no msg field\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + cJSON *el_replay = cJSON_GetObjectItemCaseSensitive(el_msg, "replay"); + cJSON *el_sdp = cJSON_GetObjectItemCaseSensitive(el_msg, "sdp"); + cJSON *el_token = cJSON_GetObjectItemCaseSensitive(el_msg, "token"); + cJSON *el_udp_token = cJSON_GetObjectItemCaseSensitive(el_msg, "udp_token"); + cJSON *el_tcp_token = cJSON_GetObjectItemCaseSensitive(el_msg, "tcp_token"); + cJSON *el_candidate = cJSON_GetObjectItemCaseSensitive(el_msg, "candidate"); + cJSON *el_mode = cJSON_GetObjectItemCaseSensitive(el_msg, "mode"); + cJSON *el_auth = cJSON_GetObjectItemCaseSensitive(el_msg, "auth"); + if (el_auth == NULL) { + el_auth = cJSON_GetObjectItemCaseSensitive(el_msg, "Auth"); + } + cJSON *el_stream_type = cJSON_GetObjectItemCaseSensitive(el_msg, "stream_type"); + char *auth = cJSON_IsString(el_auth) ? el_auth->valuestring : ""; + int32_t stream_type = cJSON_IsNumber(el_stream_type) ? el_stream_type->valueint : -1; + cJSON *el_preconnect = cJSON_GetObjectItemCaseSensitive(el_msg, "preconnect"); + + printf("process signaling %s\n", type); + // create new session if necessary + // static pj_session_cfg_t cfg; + // tuya_p2p_rtc_session_t *rtc = ctx_session_get(ctx, remote_id, session_id); + if (g_pRtcSession == NULL && strcmp(type, "offer") == 0) { + memset(&cfg, 0, sizeof(cfg)); + + if (cJSON_IsString(el_mode) && strcmp(el_mode->valuestring, "webrtc") == 0) { + cfg.is_webrtc = 1; + } else { + // cfg.channel_number = ctx->opt.max_channel_number; // todo: set channel number + } + + if (!cJSON_IsArray(el_token) && !cJSON_IsObject(el_udp_token) && !cJSON_IsObject(el_tcp_token)) { + printf("invalid signaling: invalid json, no token field\n"); + return -1; + } else { + if (cJSON_IsArray(el_token)) { + char *token_str = cJSON_PrintUnformatted(el_token); + if (token_str != NULL) { + snprintf(cfg.ice_server_tokens, sizeof(cfg.ice_server_tokens), "%s", + token_str); // Parse ICE server information from cloud + cJSON_free(token_str); + } + } + if (cJSON_IsObject(el_udp_token)) { + char *token_str = cJSON_PrintUnformatted(el_udp_token); + if (token_str != NULL) { + snprintf(cfg.udp_server_tokens, sizeof(cfg.udp_server_tokens), "%s", token_str); + cJSON_free(token_str); + } + } + if (cJSON_IsObject(el_tcp_token)) { + char *token_str = cJSON_PrintUnformatted(el_tcp_token); + if (token_str != NULL) { + snprintf(cfg.tcp_server_tokens, sizeof(cfg.tcp_server_tokens), "%s", token_str); + cJSON_free(token_str); + } + } + } + + if (cJSON_IsNumber(el_security_level)) { + printf("security_level in offer: %d\n", el_security_level->valueint); + } else { + printf("no security_level in offer, use default L3\n"); + } + int peer_security_level = + cJSON_IsNumber(el_security_level) ? el_security_level->valueint : 3 /*TUYA_P2P_SECURITY_LEVEL_3*/; + // if (__check_security_level(peer_security_level) < 0) { + // ctx_session_send_disconnect_v2(ctx, + // cfg.moto_id, + // ctx->opt.local_id, + // remote_id, + // node_id, + // session_id, + // trace_id, + // 0, + // RTC_SESSION_CLOSE_REASON_SECURITY_NEGOTIATE_FAIL, + // path); + // return -1; + // } + + // create rtc session + // cfg.loop = &ctx->loop; + cfg.offline_timeout_seconds = 30; + cfg.role = PJ_ROLE_CALLEE; + // cfg.channel_number = ctx->opt.max_channel_number; + cfg.connect_limit_time_ms = 15000; + cfg.stream_type = stream_type >= 0 ? stream_type : 0; + cfg.is_pre = is_pre; + cfg.p2p_skill = 0 /*p2p_skill*/; + cfg.preconnect_enable = 0; + // if (cJSON_IsBool(el_preconnect)) { + // cfg.preconnect_enable = el_preconnect->valueint; + // } + cfg.security_level = peer_security_level; + snprintf(cfg.local_id, sizeof(cfg.local_id), "%s", local_id); + snprintf(cfg.remote_id, sizeof(cfg.remote_id), "%s", remote_id); + snprintf(cfg.session_id, sizeof(cfg.session_id), "%s", session_id); + snprintf(cfg.node_id, sizeof(cfg.node_id), "%s", node_id); + snprintf(cfg.trace_id, sizeof(cfg.trace_id), "%s", trace_id); + snprintf(cfg.moto_id, sizeof(cfg.moto_id), "%s", moto_id); + snprintf(cfg.auth, sizeof(cfg.auth), "%s", auth); + tuya_p2p_misc_rand_string(cfg.ice_ufrag, 5); + tuya_p2p_misc_rand_string(cfg.ice_password, 25); + + if (cJSON_IsObject(el_replay)) { + cJSON *el_is_replay = cJSON_GetObjectItemCaseSensitive(el_replay, "is_replay"); + cJSON *el_start_time = cJSON_GetObjectItemCaseSensitive(el_replay, "start_time"); + cJSON *el_end_time = cJSON_GetObjectItemCaseSensitive(el_replay, "end_time"); + if (cJSON_IsNumber(el_is_replay)) { + cfg.is_replay = el_is_replay->valueint; + } + if (cJSON_IsString(el_start_time)) { + snprintf(cfg.start_time, sizeof(cfg.start_time), "%s", el_start_time->valuestring); + } + if (cJSON_IsString(el_end_time)) { + snprintf(cfg.end_time, sizeof(cfg.end_time), "%s", el_end_time->valuestring); + } + } + + int32_t err_code = 0; + tal_mutex_create_init(&g_p2p_session_mutex); + g_pRtcSession = ctx_session_create(&cfg, RTC_STATE_P2P_CONNECT, &err_code); + memcpy(&g_pRtcSession->cb, &g_options.cb, sizeof(g_options.cb)); + + iceSessionCfg.cb.ice_on_rx_data = ice_on_rx_data; + iceSessionCfg.cb.ice_on_ice_complete = ice_on_ice_complete; + iceSessionCfg.cb.ice_on_new_candidate = ice_on_new_candidate; + iceSessionCfg.rolechar = 'o'; + iceSessionCfg.local_ufrag = cfg.ice_ufrag; + iceSessionCfg.local_passwd = cfg.ice_password; + iceSessionCfg.user_data = g_pRtcSession; + memcpy(iceSessionCfg.server_tokens, cfg.ice_server_tokens, sizeof(iceSessionCfg.server_tokens)); + pj_ice_session_create(&iceSessionCfg, &g_pRtcSession->pIce); + pj_ice_session_init(g_pRtcSession->pIce, &iceSessionCfg); + + pthread_create(&g_pRtcSession->tid, NULL, rtc_worker_thread, g_pRtcSession); + tuya_p2p_log_info("ctx_session_create\n"); + } + + if (g_pRtcSession == NULL) { + tuya_p2p_log_info("can not find rtc session\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + + if (strcmp(type, "candidate") == 0) { + if (!cJSON_IsString(el_candidate)) { + printf("invalid signaling: type: candidate\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + tal_mutex_lock(g_p2p_session_mutex); + ctx_session_add_remote_candidate(g_pRtcSession, &g_pRtcSession->remote_sdp, el_candidate->valuestring); + tal_mutex_unlock(g_p2p_session_mutex); + } else if (strcmp(type, "offer") == 0) { + if (!cJSON_IsString(el_sdp)) { + printf("invalid signaling: type: sdp\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + char *buf = el_sdp->valuestring; + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_sdp_decode(&g_pRtcSession->remote_sdp, buf); + tuya_p2p_rtc_sdp_negotiate(&g_pRtcSession->local_sdp, &g_pRtcSession->remote_sdp, type); + ctx_session_send_sdp(g_pRtcSession, &cfg); // Send Answer_SDP to peer + tal_mutex_unlock(g_p2p_session_mutex); + } else if ((strcmp(type, "answer") == 0)) { + if (!cJSON_IsString(el_sdp)) { + tuya_p2p_log_debug("invalid signaling: type: sdp\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + char *buf = el_sdp->valuestring; + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_sdp_decode(&g_pRtcSession->remote_sdp, buf); + tuya_p2p_rtc_sdp_negotiate(&g_pRtcSession->local_sdp, &g_pRtcSession->remote_sdp, type); + tal_mutex_unlock(g_p2p_session_mutex); + } else if (strcmp(type, "disconnect") == 0) { + cJSON *jclose_reason_local = cJSON_GetObjectItemCaseSensitive(el_msg, "close_reason_local"); + cJSON *jclose_reason = cJSON_GetObjectItemCaseSensitive(el_msg, "close_reason"); + int close_reason = + cJSON_IsNumber(jclose_reason) ? jclose_reason->valueint : 99 /*RTC_SESSION_CLOSE_REASON_UNDEFINED*/; + int close_reason_local = cJSON_IsNumber(jclose_reason_local) ? jclose_reason_local->valueint : 0; + //tal_mutex_lock(g_p2p_session_mutex); + ctx_session_destroy(g_pRtcSession); + g_pRtcSession = NULL; + //tal_mutex_unlock(g_p2p_session_mutex); + tal_mutex_release(g_p2p_session_mutex); + g_p2p_session_mutex = NULL; + } else if (strcmp(type, "activate") == 0) { + cJSON *el_handle = cJSON_GetObjectItemCaseSensitive(el_msg, "handle"); + cJSON *el_seq = cJSON_GetObjectItemCaseSensitive(el_msg, "seq"); + if (!cJSON_IsNumber(el_handle) || !cJSON_IsNumber(el_seq)) { + tuya_p2p_log_debug("invalid signaling: type: handle or seq\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + // int active_handle = el_handle->valueint; + // int seq = el_seq->valueint; + // if (rtc->remote_cmd_seq >= seq) { + // tuya_p2p_log_warn( + // "rtc session %08x got old %s: %d >= %d\n", rtc->handle, type, rtc->remote_cmd_seq, seq); + // return -1; + // } + // rtc->remote_cmd_seq = seq; + // if (rtc->active_state == RTC_PRE_ACTIVE) { + // if (rtc->active_handle == active_handle) { + // tuya_p2p_log_warn( + // "rtc session %08x got repeated activation:%d\n", rtc->handle, active_handle); + // ctx_session_send_activate_resp(ctx, rtc, TUYA_P2P_ERROR_SUCCESSFUL); + // } else { + // ctx_session_send_activate_resp(ctx, rtc, TUYA_P2P_ERROR_PRE_SESSION_ALREADY_ACTIVE); + // } + // return -1; + // } + // if (rtc->active_state != RTC_PRE_NOT_ACTIVE) { + // tuya_p2p_log_warn("rtc session %08x state %d\n", rtc->handle, rtc->active_state); + // ctx_session_send_activate_resp(ctx, rtc, TUYA_P2P_ERROR_PRE_SESSION_ALREADY_ACTIVE); + // return -1; + // } + // int error = TUYA_P2P_ERROR_SUCCESSFUL; + // if (rtc->cfg.is_pre == 0) { + // error = TUYA_P2P_ERROR_INVALID_PRE_SESSION; + // } else if (rtc->state.state != RTC_STATE_STREAM) { + // error = TUYA_P2P_ERROR_PRE_SESSION_NOT_CONNECTED; + // } else if (ctx_get_current_session_number(ctx) >= ctx->opt.max_session_number) { + // error = TUYA_P2P_ERROR_OUT_OF_SESSION; + // } + // if (error == TUYA_P2P_ERROR_SUCCESSFUL) { + // rtc->log.activate_time = tuya_p2p_misc_get_timestamp_ms(); + // rtc->log.activate_resp_time = 0; + // rtc->log.suspend_time = 0; + // rtc->log.suspend_resp_time = 0; + // rtc->connect_break_flag = 0; + // rtc->log.close_reason_local = 0; + // rtc->log.close_reason_remote = 0; + // rtc->active_state = RTC_PRE_ACTIVE; + // rtc->active_handle = active_handle; + // rtc->handle |= rtc->active_handle << 16; + // rtc->has_notified = 0; + // rtc->cfg.is_pre = 0; + // rtc->cfg.role = RTC_ROLE_CALLEE; + // tuya_p2p_rtc_channels_reset(rtc); + // ctx_session_on_state_change(rtc); + // ctx_new_session_activate(ctx, rtc, TUYA_P2P_ERROR_SUCCESSFUL); + // } + // ctx_session_send_activate_resp(ctx, rtc, error); + } else if (strcmp(type, "suspend") == 0) { + cJSON *el_handle = cJSON_GetObjectItemCaseSensitive(el_msg, "handle"); + cJSON *el_reason = cJSON_GetObjectItemCaseSensitive(el_msg, "reason"); + cJSON *el_seq = cJSON_GetObjectItemCaseSensitive(el_msg, "seq"); + if (!cJSON_IsNumber(el_handle) || !cJSON_IsNumber(el_reason) || !cJSON_IsNumber(el_seq)) { + tuya_p2p_log_debug("invalid signaling: type: handle or seq\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + int active_handle = el_handle->valueint; + int reason = el_reason->valueint; + int seq = el_seq->valueint; + // if (rtc->remote_cmd_seq >= seq) { + // tuya_p2p_log_warn("rtc session %08x got old %s: %d >= %d\n", rtc->handle, type, rtc->remote_cmd_seq, + // seq); return -1; + // } + // rtc->remote_cmd_seq = seq; + // uint32_t pre_session_number = ctx_get_pre_session_number(ctx); + // uint32_t pre_session_number_remote = ctx_get_pre_session_number_by_remote(ctx, rtc->cfg.remote_id); + // tuya_p2p_log_info("remote %s pre session number %d\n", rtc->cfg.remote_id, pre_session_number_remote); + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + rtc->active_handle = active_handle; + ctx_session_send_suspend_resp(rtc, TUYA_P2P_ERROR_SUCCESSFUL); + tal_mutex_unlock(g_p2p_session_mutex); + } + if (root != NULL) { + cJSON_Delete(root); + } + return 0; +} + +int32_t tuya_p2p_rtc_set_signaling(char *remote_id, char *msg, uint32_t msglen) +{ + cJSON *jsignaling = NULL; + cJSON *jheader = NULL; + cJSON *jsession_id = NULL; + cJSON *jremote_id = NULL; + cJSON *jtype = NULL; + cJSON *jpath = NULL; + char *path = "mqtt"; + jsignaling = cJSON_Parse(msg); + if (!cJSON_IsObject(jsignaling)) { + printf("set signaling: not a json(%.*s)\n", msglen, msg); + goto finish; + } + jheader = cJSON_GetObjectItemCaseSensitive(jsignaling, "header"); + if (!cJSON_IsObject(jsignaling)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jsession_id = cJSON_GetObjectItemCaseSensitive(jheader, "sessionid"); + if (!cJSON_IsString(jsession_id)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jremote_id = cJSON_GetObjectItemCaseSensitive(jheader, "from"); + if (!cJSON_IsString(jremote_id)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jtype = cJSON_GetObjectItemCaseSensitive(jheader, "type"); + if (!cJSON_IsString(jtype)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jpath = cJSON_GetObjectItemCaseSensitive(jheader, "path"); + if (cJSON_IsString(jpath)) { + path = jpath->valuestring; + } + if (strcmp(jtype->valuestring, "offer") == 0) { + // int ret = ctx_add_session(g_ctx, jsession_id->valuestring); + // if (ret < 0) { + // tuya_p2p_log_info("got repeated offer, drop\n"); + // char control_msg[1024] = {0}; + // snprintf(control_msg, + // sizeof(control_msg), + // "{\"cmd\":\"retransmit_signaling\",\"args\":{\"session_id\":\"%s\", " + // "\"remote_id\":\"%s\",\"path\":\"%s\"}}", + // jsession_id->valuestring, + // jremote_id->valuestring, + // path); + // ctx_input_control_msg(control_msg, strlen(control_msg)); + // goto finish; + // } + } + + // Regardless of whether the received type field is "offer" or "candidate", process with the following function + int ret = tuya_p2p_process_signal_msg(msg, msglen); + if (ret < 0) { + goto finish; + } + +finish: + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_add_remote_candidate(tuya_p2p_rtc_session_t *rtc, rtc_sdp_t *remote_sdp, char *candidate) +{ + pj_ice_session_t *pIceSession = rtc->pIce; + int iCandidateLen = strlen(candidate); + if (iCandidateLen == 0) { + return -1; + } + tuya_p2p_rtc_sdp_add_candidate(remote_sdp, candidate); + pj_str_t pjstrCand = pj_str(candidate); + if (*(candidate + iCandidateLen - 2) == '\r' && *(candidate + iCandidateLen - 1) == '\n') { + pjstrCand.slen -= 2; // Remove trailing \r\n character length + } + pj_ice_sess_cand cand; + if (parse_cand(NULL, &pjstrCand, &cand) != + 0 /*|| cand.type == PJ_ICE_CAND_TYPE_HOST || cand.type == PJ_ICE_CAND_TYPE_RELAYED*/) { + return -1; + } + pj_str_t pjstrUFrag = pj_str(remote_sdp->ufrag); + pj_str_t pjstrPasswd = pj_str(remote_sdp->password); + pj_ice_session_add_remote_candidate(pIceSession, &pjstrUFrag, &pjstrPasswd, 1, &cand, false); + return 0; +} + +int ctx_session_send_sdp(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg) +{ + char *type = (cfg->role == PJ_ROLE_CALLER ? "offer" : "answer"); + char sdp[4 * 1024] = {0}; + int ret = tuya_p2p_rtc_sdp_encode(&rtc->local_sdp, type, sdp, sizeof(sdp)); + if (ret < 0) { + return -1; + } + + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(cfg->local_id); + cJSON *jto = cJSON_CreateString(cfg->remote_id); + cJSON *jsession_id = cJSON_CreateString(cfg->session_id); + cJSON *jmoto_id = cJSON_CreateString(cfg->moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(cfg->trace_id); + cJSON *jis_pre = cJSON_CreateNumber(cfg->is_pre); + cJSON *jsecurity_level = cJSON_CreateNumber(cfg->security_level); + cJSON *jp2p_skill = cJSON_CreateNumber(g_uP2PSkill); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jmoto_id == NULL || jtype == NULL || jtrace_id == NULL || + jis_pre == NULL || jp2p_skill == NULL || jheader == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + cJSON_AddItemToObject(jheader, "is_pre", jis_pre); + cJSON_AddItemToObject(jheader, "p2p_skill", jp2p_skill); + cJSON_AddItemToObject(jheader, "security_level", jsecurity_level); + + cJSON *jsdp = cJSON_CreateString(sdp); + cJSON *jpreconnect = cJSON_CreateBool(cfg->preconnect_enable); + cJSON *jtoken = cJSON_Parse(cfg->ice_server_tokens); + cJSON *judp_token = cJSON_Parse(cfg->udp_server_tokens); + cJSON *jtcp_token = cJSON_Parse(cfg->tcp_server_tokens); + cJSON *jmsg = cJSON_CreateObject(); + if (jsdp == NULL || jmsg == NULL || jpreconnect == NULL) { + goto finish; + } + cJSON_AddItemToObject(jmsg, "sdp", jsdp); + // cJSON_AddItemToObject(jmsg, "preconnect", jpreconnect); + if (jtoken != NULL) { + cJSON_AddItemToObject(jmsg, "token", jtoken); + } + if (judp_token != NULL) { + cJSON_AddItemToObject(jmsg, "udp_token", judp_token); + } + if (jtcp_token != NULL) { + cJSON_AddItemToObject(jmsg, "tcp_token", jtcp_token); + } + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + // ctx_session_send_signaling(rtc, signaling, 0); + // ctx_session_backup_signaling(rtc, "outgoing", type, signaling); + tal_mutex_lock(g_p2p_session_mutex); + if (g_pRtcSession->cb.on_signaling != NULL) { + g_pRtcSession->cb.on_signaling(cfg->remote_id, signaling, strlen(signaling)); + printf("g_pRtcSession->cb.on_signaling success\n"); + } + tal_mutex_unlock(g_p2p_session_mutex); + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_candidate(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg, char *cand_str) +{ + char *type = "candidate"; + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(cfg->local_id); + cJSON *jto = cJSON_CreateString(cfg->remote_id); + cJSON *jsession_id = cJSON_CreateString(cfg->session_id); + cJSON *jmoto_id = cJSON_CreateString(cfg->moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(cfg->trace_id); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jmoto_id == NULL || jtype == NULL || jtrace_id == NULL || + jheader == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + + cJSON *jcand = cJSON_CreateString(cand_str); + cJSON *jmsg = cJSON_CreateObject(); + if (jcand == NULL || jmsg == NULL) { + goto finish; + } + cJSON_AddItemToObject(jmsg, "candidate", jcand); + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + // ctx_session_send_signaling(rtc, signaling, 0); + // ctx_session_backup_signaling(rtc, "outgoing", type, signaling); + tal_mutex_lock(g_p2p_session_mutex); + if (g_pRtcSession->cb.on_signaling != NULL) { + g_pRtcSession->cb.on_signaling(cfg->remote_id, signaling, strlen(signaling)); + } + tal_mutex_unlock(g_p2p_session_mutex); + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_suspend_resp(tuya_p2p_rtc_session_t *rtc, int error) +{ + char *type = "suspend_resp"; + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(rtc->cfg.local_id); + cJSON *jto = cJSON_CreateString(rtc->cfg.remote_id); + cJSON *jsession_id = cJSON_CreateString(rtc->cfg.session_id); + cJSON *jmoto_id = cJSON_CreateString(rtc->cfg.moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(rtc->cfg.trace_id); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jmoto_id == NULL || jtype == NULL || jtrace_id == NULL || + jheader == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + + cJSON *jhandle = cJSON_CreateNumber(rtc->active_handle); + cJSON *jerror = cJSON_CreateNumber(error); + cJSON *jseq = cJSON_CreateNumber(++rtc->local_cmd_seq); + cJSON *jmsg = cJSON_CreateObject(); + if (jhandle == NULL || jerror == NULL || jseq == NULL || jmsg == NULL) { + goto finish; + } + cJSON_AddItemToObject(jmsg, "handle", jhandle); + cJSON_AddItemToObject(jmsg, "error", jerror); + cJSON_AddItemToObject(jmsg, "seq", jseq); + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + // ctx_session_send_signaling(rtc, signaling, 0); + if (rtc->cb.on_signaling != NULL) { + rtc->cb.on_signaling(rtc->cfg.remote_id, signaling, strlen(signaling)); + printf("rtc->cb.on_signaling success\n"); + } + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_disconnect(tuya_p2p_rtc_session_t *rtc, int32_t close_reason_local, rtc_session_close_reason_e close_reason) +{ + char *type = "disconnect"; + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(rtc->cfg.local_id); + cJSON *jto = cJSON_CreateString(rtc->cfg.remote_id); + cJSON *jnode_id = cJSON_CreateString(rtc->cfg.node_id); + cJSON *jsession_id = cJSON_CreateString(rtc->cfg.session_id); + cJSON *jmoto_id = cJSON_CreateString(rtc->cfg.moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(rtc->cfg.trace_id); + cJSON *jclose_reason_local = cJSON_CreateNumber(close_reason_local); + cJSON *jclose_reason = cJSON_CreateNumber(close_reason); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jnode_id == NULL || jmoto_id == NULL || + jtype == NULL || jtrace_id == NULL || jheader == NULL || jclose_reason_local == NULL || + jclose_reason == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sub_dev_id", jnode_id); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + + cJSON *jmsg = cJSON_CreateObject(); + if (jmsg == NULL) { + goto finish; + } + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jmsg, "close_reason_local", jclose_reason_local); + cJSON_AddItemToObject(jmsg, "close_reason", jclose_reason); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + ctx_session_send_signaling(rtc, signaling); + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_signaling(tuya_p2p_rtc_session_t *rtc, char *signaling) +{ + if (g_pRtcSession->cb.on_signaling != NULL) { + g_pRtcSession->cb.on_signaling(rtc->cfg.remote_id, signaling, strlen(signaling)); + } + return 0; +} + +char *ctx_signaling_add_path(char *signaling, char *path) { + char *new_signaling = NULL; + cJSON *jsignaling = cJSON_Parse(signaling); + if (!cJSON_IsObject(jsignaling)) { + goto finish; + } + + cJSON *jheader = cJSON_GetObjectItemCaseSensitive(jsignaling, "header"); + if (!cJSON_IsObject(jheader)) { + goto finish; + } + + cJSON *jpath = cJSON_CreateString(path); + if (jpath == NULL) { + goto finish; + } + + cJSON_AddItemToObject(jheader, "path", jpath); + new_signaling = cJSON_PrintUnformatted(jsignaling); + +finish: + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return new_signaling; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +void ice_on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status) +{ + tuya_p2p_rtc_session_t *pRtcSession = (tuya_p2p_rtc_session_t *)pj_ice_strans_get_user_data(ice_st); + pj_ice_session_t *pIceSession = (pj_ice_session_t *)pRtcSession->pIce; + if (!pIceSession) + return; + + switch (op) { + case PJ_ICE_STRANS_OP_INIT: { + break; + } + case PJ_ICE_STRANS_OP_NEGOTIATION: { + if (status == PJ_SUCCESS) { + char szLCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + char szRCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + unsigned comp_id = 1; // Component starting ID number is 1 + const pj_ice_sess_check *pIceSessCheck = pj_ice_strans_get_valid_pair(ice_st, comp_id); + pj_sockaddr_print(&pIceSessCheck->lcand->addr, szLCandAddr, sizeof(szLCandAddr), 3); + pj_sockaddr_print(&pIceSessCheck->rcand->addr, szRCandAddr, sizeof(szRCandAddr), 3); + + rtc_init_mbedtls_md_and_aes(pRtcSession); + sync_cond_notify(&g_syncCond); + printf("ICE STrans negotiation success!\n"); + } else { + printf("ICE STrans negotiation fail!\n"); + } + break; + } + case PJ_ICE_STRANS_OP_KEEP_ALIVE: { + break; + } + default: { + pj_assert(!"Unknown op"); + break; + } + } // switch (op) + + return; +} + +void ice_on_new_candidate(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t last) +{ + tuya_p2p_rtc_session_t *pRtcSession = (tuya_p2p_rtc_session_t *)pj_ice_strans_get_user_data(ice_st); + pj_ice_session_t *pIceSession = (pj_ice_session_t *)pRtcSession->pIce; + if (!pIceSession) + return; + + // pIceSession->bLastCand = last; + + if (cand) { + char buf1[PJ_INET6_ADDRSTRLEN + 10] = {0}; + char buf2[PJ_INET6_ADDRSTRLEN + 10] = {0}; + PJ_LOG(4, (THIS_FILE, + "%p: discovered a new candidate " + "comp=%d, type=%s, addr=%s, baseaddr=%s, end=%d", + pIceSession, cand->comp_id, pj_ice_get_cand_type_name(cand->type), + pj_sockaddr_print(&cand->addr, buf1, sizeof(buf1), 3), + pj_sockaddr_print(&cand->base_addr, buf2, sizeof(buf2), 3), last)); + char szCand[1024] = {0}; + if (print_cand(szCand, sizeof(szCand), cand) < 0) { + return; + } + ctx_session_send_candidate(pRtcSession, &cfg, szCand); + } + // else if (pIceSession->pIceSTransport && last) { + // PJ_LOG(4, (THIS_FILE, "%p: end of candidate", pIceSession->pIceSTransport)); + // } + + return; +} + +void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, void *buffer, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + tuya_p2p_rtc_session_t *rtc = (tuya_p2p_rtc_session_t *)pj_ice_strans_get_user_data(ice_st); + if (rtc == NULL) { + return; + } + tuya_uv_buf_t pkt; + pkt.base = buffer; + pkt.len = size; + rtc_process_kcp_data(rtc, &pkt); + return; +} + +void rtc_process_kcp_data(tuya_p2p_rtc_session_t *rtc, const tuya_uv_buf_t *pkt) +{ + if (rtc == NULL || pkt == NULL) { + return; + } + uint32_t digest_len = 0; + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_3) { + digest_len = mbedtls_md_get_size(rtc->md_info); + } + if (pkt->len < IKCP_PACKET_HEADER_SIZE + digest_len) { + tuya_p2p_log_debug("recv invalid packet, len = %d\n", pkt->len); + return; + } + uint32_t channel_id = ikcp_getconv(pkt->base); + if (channel_id < 0 || channel_id > rtc->cfg.channel_number) { + tuya_p2p_log_warn("recv invalid kcp packet, channel id = %d(%d)\n", channel_id, rtc->cfg.channel_number); + return; + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + chan->socket_recv_bytes += (pkt->len); + + if (digest_len > 0) { + unsigned char digest[digest_len]; + int ret; + ret = mbedtls_md_hmac_starts(&rtc->md_ctx, rtc->aes_key, sizeof(rtc->aes_key)); + if (ret != 0) { + return; + } + ret = mbedtls_md_hmac_update(&rtc->md_ctx, (unsigned char *)pkt->base, pkt->len - digest_len); + if (ret != 0) { + return; + } + ret = mbedtls_md_hmac_finish(&rtc->md_ctx, digest); + if (ret != 0) { + return; + } + + if (memcmp(digest, pkt->base + pkt->len - digest_len, digest_len)) { + tuya_p2p_log_debug("invalid md code\n"); + return; + } + } + + ctx_session_channel_process_data(chan, pkt->base, pkt->len - digest_len); + + return; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +static int on_kcp_output(const char *buf, int len, ikcpcb *kcp, void *user_data) +{ + (void)kcp; + if (user_data == NULL) { + return 0; + } + rtc_channel_t *chan = (rtc_channel_t *)user_data; + tuya_p2p_rtc_session_t *rtc = chan->rtc; + + int md_size = 0; + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_3) { + int ret; + ret = mbedtls_md_hmac_starts(&rtc->md_ctx, rtc->aes_key, sizeof(rtc->aes_key)); + if (ret != 0) { + return 0; + } + ret = mbedtls_md_hmac_update(&rtc->md_ctx, (unsigned char *)buf, len); + if (ret != 0) { + return 0; + } + ret = mbedtls_md_hmac_finish(&rtc->md_ctx, (unsigned char *)buf + len); + if (ret != 0) { + return 0; + } + md_size = mbedtls_md_get_size(rtc->md_info); + } + + ctx_session_channel_set_send_time(chan); + uint32_t r = (rand() % 99) + 1; + uint32_t channel_id = ikcp_getconv(buf); + unsigned char cmd = ikcp_getcmd(buf); + uint32_t sn = ikcp_getsn(buf); + // tuya_p2p_log_trace("channel_id: %08x, sn: %d, cmd: %d\n", channel_id, sn, cmd); + + if (cmd != KCP_CMD_PUSH || channel_id != RTC_CHANNEL_CMD) { + pj_ice_session_sendto(rtc->pIce, (void *)buf, len + md_size); + } + + chan->socket_send_bytes += (len + md_size); + return len; +} + +void rtc_ref_cnt_add(tuya_p2p_rtc_session_t *rtc) { + pthread_mutex_lock(&rtc->ref_lock); + rtc->ref_cnt++; + pthread_mutex_unlock(&rtc->ref_lock); +} + +void rtc_ref_cnt_del(tuya_p2p_rtc_session_t *rtc) { + pthread_mutex_lock(&rtc->ref_lock); + rtc->ref_cnt--; + pthread_mutex_unlock(&rtc->ref_lock); +} + +int rtc_ref_cnt_get(tuya_p2p_rtc_session_t *rtc) { + int ref_cnt; + pthread_mutex_lock(&rtc->ref_lock); + ref_cnt = rtc->ref_cnt; + pthread_mutex_unlock(&rtc->ref_lock); + return ref_cnt; +} + +tuya_p2p_rtc_session_t *ctx_session_create(rtc_session_cfg_t *cfg, rtc_state_e state, int32_t *err_code) +{ + tuya_p2p_rtc_session_t *rtc = NULL; + rtc = (tuya_p2p_rtc_session_t *)malloc(sizeof(tuya_p2p_rtc_session_t)); + if (rtc == NULL) { + *err_code = TUYA_P2P_ERROR_OUT_OF_MEMORY; + goto finish; + } + memset(rtc, 0, sizeof(tuya_p2p_rtc_session_t)); + memcpy(&rtc->cfg, cfg, sizeof(rtc->cfg)); + // rtc->pool = NULL; + rtc->ref_cnt = 0; + pthread_mutex_init(&rtc->ref_lock, NULL); + pthread_mutex_init(&rtc->channel_lock, NULL); + rtc->cfg.channel_number = 3; + rtc->active_handle = 0; + rtc->local_cmd_seq = 0; + rtc->tid = -1; + rtc->bQuitKCPThread = false; + + sync_cond_init(&rtc->syncCondExit); + + if (tuya_p2p_rtc_channels_init(rtc) != 0) { + *err_code = TUYA_P2P_ERROR_CHANNEL_INIT_FAILED; + goto finish; + } + + int ret = 0; + tuya_p2p_rtc_dtls_role_e local_dtls_role = + DTLS_ROLE_CLIENT /*rtc->cfg.role == RTC_ROLE_CALLER ? DTLS_ROLE_BOTH : DTLS_ROLE_CLIENT*/; + tuya_p2p_rtc_dtls_role_e remote_dtls_role = + DTLS_ROLE_SERVER /*rtc->cfg.role == RTC_ROLE_CALLER ? DTLS_ROLE_BOTH : DTLS_ROLE_SERVER*/; + ret = tuya_p2p_rtc_sdp_init(&rtc->local_sdp, cfg->session_id, cfg->local_id, "", cfg->ice_ufrag, cfg->ice_password, + local_dtls_role); + if (ret < 0) { + *err_code = TUYA_P2P_ERROR_SDP_INIT_FAILED; + goto finish; + } + ret = tuya_p2p_rtc_sdp_init(&rtc->remote_sdp, "", "", "", NULL, NULL, remote_dtls_role); + if (ret < 0) { + *err_code = TUYA_P2P_ERROR_SDP_INIT_FAILED; + goto finish; + } + rtc_ref_cnt_add(rtc); + return rtc; + +finish: + if (rtc != NULL) { + ctx_session_destroy(rtc); + } + return NULL; +} + +void ctx_session_destroy(tuya_p2p_rtc_session_t *rtc) +{ + tal_mutex_lock(g_p2p_session_mutex); + if (rtc == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + return; + } + if (rtc->tid != -1) { + rtc->bQuitKCPThread = true; + pthread_join(rtc->tid, NULL); + rtc->tid = -1; + } + tuya_p2p_rtc_channels_destroy(rtc); + if (rtc->pIce != NULL) { + pj_ice_session_destroy(rtc->pIce); + rtc->pIce = NULL; + } + tal_mutex_unlock(g_p2p_session_mutex); + + sync_cond_wait(&rtc->syncCondExit); + sync_cond_clean(&rtc->syncCondExit); + + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_sdp_deinit(&rtc->local_sdp); + tuya_p2p_rtc_sdp_deinit(&rtc->remote_sdp); + mbedtls_md_free(&rtc->md_ctx); + pthread_mutex_destroy(&rtc->ref_lock); + pthread_mutex_destroy(&rtc->channel_lock); + free(rtc); + rtc = NULL; + tal_mutex_unlock(g_p2p_session_mutex); + return; +} + +void ctx_session_channel_set_data_time(struct rtc_channel *chan) +{ + if (chan->first_data_time_ms == 0) { + tuya_p2p_log_warn("channel %d get first data\n", chan->channel_id); + chan->first_data_time_ms = tuya_p2p_misc_get_timestamp_ms(); + } +} + +void ctx_session_channel_set_write_time(struct rtc_channel *chan) +{ + if (chan->first_write_time_ms == 0) { + tuya_p2p_log_warn("channel %d first write\n", chan->channel_id); + chan->first_write_time_ms = tuya_p2p_misc_get_timestamp_ms(); + } +} + +void ctx_session_channel_set_send_time(struct rtc_channel *chan) +{ + if (chan->first_send_time_ms == 0) { + tuya_p2p_log_warn("channel %d first send\n", chan->channel_id); + chan->first_send_time_ms = tuya_p2p_misc_get_timestamp_ms(); + } +} + +int ctx_session_channel_process_data(struct rtc_channel *chan, char *data, int len) +{ + ctx_session_channel_set_data_time(chan); + if (ikcp_input(chan->kcp, (const char *)data, len /*, tuya_p2p_misc_get_timestamp_ms()*/) > 0) { + } + return 0; +} + +int ctx_session_channel_process_pkt(void *user, int length, const char *input, char *output) +{ + char *encrypted = (char *)input; + char *decrypted = output; + int iv_size = 16; + int keylen = 16; + char *iv = encrypted; + int msg_size = length - iv_size; + rtc_channel_t *chan = (rtc_channel_t *)user; + tuya_p2p_rtc_session_t *rtc = chan->rtc; + int ret = -1; + if ((msg_size > 0) && ((msg_size % keylen) == 0)) { + ret = rtc_crypt_decrypt_aes_128_cbc(rtc, chan->aes_ctx_dec, msg_size, (unsigned char *)iv, + (const unsigned char *)(encrypted + iv_size), (unsigned char *)decrypted); + + // Subtract GCM signature length + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_4) { + if (msg_size <= 16) { + return -1; + } + msg_size -= 16; + } + + if (ret == 0) { + int padding_size = decrypted[msg_size - 1]; + if (padding_size <= 16) { + if (padding_size < msg_size) { + ret = msg_size - padding_size; + } + } + } + } + + return ret; +} + +int tuya_p2p_rtc_channels_init(tuya_p2p_rtc_session_t *rtc) +{ + uint32_t i = 0; + // tuya_mem_pool_t *pool = tuya_mem_pool_create(TUYA_MBUF_HUGE_SIZE, TUYA_MBUF_NUM_ONCE); + // if (NULL == pool) { + // goto finish; + // } + // rtc->pool = pool; + rtc->channels = (rtc_channel_t *)malloc((rtc->cfg.channel_number + 1) * sizeof(rtc_channel_t)); + if (rtc->channels == NULL) { + goto finish; + } + for (i = 0; i < rtc->cfg.channel_number + 1; i++) { + uint32_t channel_id; + uint32_t send_buf_size; + uint32_t recv_buf_size; + if (i == rtc->cfg.channel_number) { + channel_id = RTC_CHANNEL_CMD; + send_buf_size = 100 * 1024; + recv_buf_size = 100 * 1024; + } else { + channel_id = i; + send_buf_size = g_options.send_buf_size[i]; + recv_buf_size = g_options.recv_buf_size[i]; + // if (rtc->cfg.is_pre) { + // channel_id |= (rtc->active_handle << 16) & 0xFFFF0000; + // } + } + rtc_channel_t *chan = &rtc->channels[i]; + memset(chan, 0, sizeof(*chan)); + chan->rtc = rtc; + chan->channel_id = i; + // chan->send_queue = tuya_mbuf_queue_create(send_buf_size, pool); + // chan->recv_queue = tuya_mbuf_queue_create(recv_buf_size, pool); + chan->kcp = ikcp_create(channel_id, chan /*, chan->send_queue, chan->recv_queue*/); + if (chan->kcp == NULL /*|| chan->send_queue == NULL || chan->recv_queue == NULL*/) { + goto finish; + } + + ikcp_setoutput(chan->kcp, on_kcp_output); + ikcp_wndsize(chan->kcp, send_buf_size / 1600 /*TUYA_MBUF_HUGE_SIZE*/, + recv_buf_size / 1600 /*TUYA_MBUF_HUGE_SIZE*/); + ikcp_nodelay(chan->kcp, 0, 10, 20, 1); + ikcp_setmtu(chan->kcp, 1400); + ikcp_setprocesspkt(chan->kcp, ctx_session_channel_process_pkt); + // ikcp_setwritelog(chan->kcp, ctx_session_kcp_writelog); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_RTT | IKCP_LOG_INPUT | IKCP_LOG_OUTPUT); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_RECV); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_IN_DROP | IKCP_LOG_IN_DATA); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_OUT_DATA | IKCP_LOG_IN_ACK|IKCP_LOG_RATE); + // ikcp_setlogmask(chan->kcp, 0xffffffff); + } + return 0; +finish: + return -1; +} + +void tuya_p2p_rtc_channels_destroy(tuya_p2p_rtc_session_t *rtc) +{ + if (rtc->channels != NULL) { + uint32_t i; + for (i = 0; i < rtc->cfg.channel_number + 1; i++) { + rtc_channel_t *chan = &rtc->channels[i]; + if (chan->kcp != NULL) { + ikcp_release(chan->kcp); + chan->kcp = NULL; + } + rtc_channel_aes_uninit(chan); + // if (chan->send_queue != NULL) { + // tuya_mbuf_queue_destroy(chan->send_queue); + // } + // if (chan->recv_queue != NULL) { + // tuya_mbuf_queue_destroy(chan->recv_queue); + // } + // g_crypt_table[rtc->cfg.security_level].uninit(chan); + } + free(rtc->channels); + rtc->channels = NULL; + } + + // if (NULL != rtc->pool) { + // tuya_mem_pool_destroy(rtc->pool); + // rtc->pool = NULL; + // } + return; +} + +void *rtc_worker_thread(void *arg) +{ + // Execute KCP sending and receiving in the same thread + pj_thread_register2(); + tuya_p2p_rtc_session_t *rtc = (tuya_p2p_rtc_session_t *)arg; + while (!rtc->bQuitKCPThread) { + pj_ice_session_handle_events(rtc->pIce, 5, NULL); // Drive ICE state update and execute KCP receive operation + for (int i = 0; i < 3; ++i) //(rtc->cfg.channel_number + 1) + { + rtc_channel_t *channel = &rtc->channels[i]; + ikcp_update(channel->kcp, + tuya_p2p_misc_get_timestamp_ms()); // Drive KCP state update and execute KCP send operation + } + } + return NULL; +} + +int32_t tuya_p2p_rtc_listen() +{ + sync_cond_wait(&g_syncCond); + return 123456; // Temporarily return a random integer value, to be changed later +} + +int32_t tuya_p2p_rtc_dosend_data(tuya_p2p_rtc_session_t *rtc, uint32_t channel_id, char *buf, int32_t len, + int32_t timeout_ms) +{ + if (rtc == NULL) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } + int remain = len; + int already = 0; + int rc = 0; + uint64_t begin_time = 0; + + while (remain > 0) { + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels == NULL) { + pthread_mutex_unlock(&rtc->channel_lock); + rc = -1; + break; + } + if (timeout_ms > 0) { + if (begin_time == 0) { + begin_time = tuya_p2p_misc_get_timestamp_ms(); + } + if (tuya_p2p_misc_check_timeout(begin_time, timeout_ms)) { + pthread_mutex_unlock(&rtc->channel_lock); + tuya_p2p_log_error("tuya_p2p_misc_check_timeout"); + return TUYA_P2P_ERROR_TIME_OUT; + } + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + ctx_session_channel_set_write_time(chan); + // if (0 != tuya_mbuf_queue_get_status(chan->send_queue)) { + // //printf("rtc %08x channel %d over\n", rtc->handle, channel_id); + // pthread_mutex_unlock(&rtc->channel_lock); + // rc = -1; + // break; + // } + // if (tuya_mbuf_queue_get_free_size(chan->send_queue) <= 0) { + // if (timeout_ms == 0) { + // pthread_mutex_unlock(&rtc->channel_lock); + // break; + // } + // pthread_mutex_unlock(&rtc->channel_lock); + // usleep(5 * 1000); + // continue; + // } + + int fragement_len = 1200; + int current = (remain > fragement_len) ? (fragement_len) : remain; + char decrypted[1500]; + char *encrypted; + int iv_size = sizeof(rtc->iv); + int keylen = 16; + int sign_size = 0; + unsigned char padding_size = keylen; + int reserve_len_forkcp = sizeof(struct IKCPSEG) + IKCP_PACKET_HEADER_SIZE; + /* 60 bytes prepared for signature */ + int reserve_len = sizeof(struct IKCPSEG) + IKCP_PACKET_HEADER_SIZE + sizeof(rtc->iv) + 60; + int buflen; + + buflen = current; + memcpy(decrypted, buf + already, buflen); + + padding_size -= (buflen % keylen); + memset(&(decrypted[buflen]), padding_size, padding_size); + + buflen += padding_size; + + /* Reserved length for control header and kcp packet header needed for kcp chaining */ + // tuya_mbuf_t *mbuf_encrypted = tuya_mbuf_alloc(chan->send_queue, buflen + reserve_len, reserve_len_forkcp); + // if (NULL == mbuf_encrypted) { + // pthread_mutex_unlock(&rtc->channel_lock); + // /* This step will not fail unless out of memory, this step failure also means connection abnormal, + // because the packet just taken down has not been added to the kcp queue */ break; + // } else { + // encrypted = TUYA_MBUF_MTOD(mbuf_encrypted); + // } + encrypted = (char *)malloc(buflen + reserve_len); + char tmp_iv[16]; + tuya_p2p_misc_rand_hex(tmp_iv, sizeof(rtc->iv)); + memcpy(encrypted, tmp_iv, iv_size); + + int ret = rtc_crypt_encrypt_aes_128_cbc(rtc, chan->aes_ctx_enc, buflen, (unsigned char *)tmp_iv, + (const unsigned char *)decrypted, (unsigned char *)encrypted + iv_size); + + // GCM encryption automatically generates 16-byte signature + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_4) { + sign_size = 16; + } + + if (ret == 0) { + // ikcp_send_mbuf(chan->kcp, mbuf_encrypted, buflen + iv_size + sign_size); + ikcp_send(chan->kcp, encrypted, buflen + iv_size + sign_size); + remain -= current; + already += current; + chan->write_bytes += current; + free(encrypted); + } else { + pthread_mutex_unlock(&rtc->channel_lock); + tuya_p2p_log_error("aes encrypt failed, ret = %d\n", ret); + // tuya_mbuf_free(mbuf_encrypted); + free(encrypted); + rc = -1; + break; + } + pthread_mutex_unlock(&rtc->channel_lock); + } + + if (already > 0) { + tuya_p2p_log_debug("channel id %d, send rc = %d\n", channel_id, already); + return already; + } else if (rc < 0) { + // tuya_p2p_log_error("rtc %08x channel %d send rc = %d\n", rtc->handle, channel_id, + // TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT); + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } else { + // tuya_p2p_log_debug("send rc = %d\n", TUYA_P2P_ERROR_TIME_OUT); + return TUYA_P2P_ERROR_TIME_OUT; + } +} + +int32_t tuya_p2p_rtc_send_data(int32_t handle, uint32_t channel_id, char *buf, int32_t len, int32_t timeout_ms) +{ + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + if (rtc == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + tuya_p2p_log_error("rtc session %08x recv data: invalid session\n", handle); + return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + int32_t ret = tuya_p2p_rtc_dosend_data(rtc, channel_id, buf, len, timeout_ms); + tal_mutex_unlock(g_p2p_session_mutex); + return ret; +} + +int32_t tuya_p2p_rtc_dorecv_data(tuya_p2p_rtc_session_t *rtc, uint32_t channel_id, char *buf, int32_t *len, + int32_t timeout_ms) +{ + if (rtc == NULL) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } + int ret = 0; + uint64_t begin_time = 0; + int buflen = *len; + + *len = 0; + + while (1) { + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels == NULL) { + pthread_mutex_unlock(&rtc->channel_lock); + ret = -1; + break; + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + // ret = ikcp_recv_mbufwithdata(chan->kcp, buf, buflen); + if (0 != ret) { + chan->read_bytes += ret; + pthread_mutex_unlock(&rtc->channel_lock); + break; + } + pthread_mutex_unlock(&rtc->channel_lock); + if (timeout_ms >= 0) { + if (begin_time == 0) { + begin_time = tuya_p2p_misc_get_timestamp_ms(); + } + if (tuya_p2p_misc_check_timeout(begin_time, timeout_ms)) { + break; + } + } + usleep(10 * 1000); + } + + if (ret < 0) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } else if (ret == 0) { + return TUYA_P2P_ERROR_TIME_OUT; + } + + *len = ret; + + return 0; +} + +int32_t tuya_p2p_rtc_dorecv_data2(tuya_p2p_rtc_session_t *rtc, uint32_t channel_id, char *buf, int32_t *len, + int32_t timeout_ms) +{ + int ret = 0; + uint64_t begin_time = 0; + int buflen = *len; + *len = 0; + + while (1) { + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels == NULL) { + pthread_mutex_unlock(&rtc->channel_lock); + ret = -4; + break; + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + ret = ikcp_recv2(chan->kcp, buf, buflen); + if (ret > 0) { + chan->read_bytes += ret; + *len = ret; + pthread_mutex_unlock(&rtc->channel_lock); + break; + } + pthread_mutex_unlock(&rtc->channel_lock); + if (timeout_ms >= 0) { + if (begin_time == 0) { + begin_time = tuya_p2p_misc_get_timestamp_ms(); + } + if (tuya_p2p_misc_check_timeout(begin_time, timeout_ms)) { + break; + } + } + usleep(10 * 1000); + } + + if (ret == -1 || ret == -2) { + return TUYA_P2P_ERROR_TIME_OUT; + } else if (ret == -3 || ret == -4) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } + return 0; +} + +int32_t tuya_p2p_rtc_recv_data(int32_t handle, uint32_t channel_id, char *buf, int32_t *len, int32_t timeout_ms) +{ + int buflen = *len; + *len = 0; + + // if (!ctx_is_inited()) + // { + // tuya_p2p_log_error("rtc session %08x recv data: sdk not inited\n", handle); + // return TUYA_P2P_ERROR_NOT_INITIALIZED; + // } + // tuya_p2p_rtc_session_t *rtc = ctx_session_get_by_handle(g_ctx, handle); + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + if (rtc == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + tuya_p2p_log_error("rtc session %08x recv data: invalid session\n", handle); + return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + tal_mutex_unlock(g_p2p_session_mutex); + // int error = rtc_session_get_error(rtc); + // if (error != TUYA_P2P_ERROR_SUCCESSFUL) + // { + // ctx_session_release(g_ctx, rtc); + // return error; + // } + // if (rtc->cfg.is_webrtc) { + // ctx_session_release(g_ctx, rtc); + // tuya_p2p_log_error("rtc session %08x recv data: invalid session\n", handle); + // return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + // } + if (channel_id < 0 || channel_id >= rtc->cfg.channel_number) { + //tal_mutex_unlock(g_p2p_session_mutex); + tuya_p2p_log_error("rtc session %08x recv data: invalid channel number: %d/%d\n", handle, channel_id, + rtc->cfg.channel_number); + // ctx_session_release(g_ctx, rtc); + return TUYA_P2P_ERROR_INVALID_PARAMETER; + } + + *len = buflen; + int32_t ret = tuya_p2p_rtc_dorecv_data2(rtc, channel_id, buf, len, timeout_ms); + // rtc_channel_t *chan = &rtc->channels[channel_id]; + // ctx_session_channel_process_pkt(chan, *len, buf, buf); + + // ctx_session_release(g_ctx, rtc); + return ret; +} + +void tuya_p2p_rtc_notify_exit() +{ + //tal_mutex_lock(g_p2p_session_mutex); + sync_cond_notify(&g_pRtcSession->syncCondExit); + //tal_mutex_unlock(g_p2p_session_mutex); + return; +} + +int32_t tuya_p2p_rtc_check(int32_t handle) +{ + return 0; +} + +int32_t tuya_p2p_rtc_check_buffer(int32_t handle, uint32_t channel_id, uint32_t *write_size, uint32_t *read_size, + uint32_t *send_free_size) +{ + int ret = 0; + tal_mutex_lock(g_p2p_session_mutex); + if (g_pRtcSession == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels != NULL) { + rtc_channel_t *chan = &rtc->channels[channel_id]; + // if (write_size != NULL) { + // *write_size = tuya_mbuf_queue_get_used_size(chan->send_queue); + // } + // if (read_size != NULL) { + // *read_size = tuya_mbuf_queue_get_used_size(chan->recv_queue); + // } + if (send_free_size != NULL) { + *send_free_size = 160 * 1024 /*tuya_mbuf_queue_get_free_size(chan->send_queue)*/; + } + } else { + ret = TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + pthread_mutex_unlock(&rtc->channel_lock); + tal_mutex_unlock(g_p2p_session_mutex); + return ret; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +int rtc_init_mbedtls_md_and_aes(tuya_p2p_rtc_session_t *rtc) +{ + if (rtc->cfg.role == PJ_ROLE_CALLEE /*RTC_ROLE_CALLEE*/) { + int ret = tuya_p2p_rtc_sdp_get_aes_key(&rtc->remote_sdp, rtc->aes_key, sizeof(rtc->aes_key)); + if (ret < 0) { + return -1; + } + } + + // rtc_init_crypt(rtc); + for (int i = 0; i < rtc->cfg.channel_number + 1; i++) { + int ret = rtc_channel_aes_init(&rtc->channels[i]); + if (ret < 0) { + return -1; + } + } + + tuya_p2p_misc_rand_hex((char *)rtc->iv, sizeof(rtc->iv)); + + rtc->md_info = (mbedtls_md_info_t *)mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + if (rtc->md_info == NULL) { + return -1; + } + mbedtls_md_setup(&rtc->md_ctx, rtc->md_info, 1); + + return 0; +} + +int rtc_channel_aes_init(rtc_channel_t *chan) +{ + tuya_p2p_rtc_session_t *rtc = chan->rtc; + if (chan->aes_ctx_enc == NULL || chan->aes_ctx_dec == NULL) { + chan->aes_ctx_enc = (mbedtls_aes_context *)malloc(sizeof(mbedtls_aes_context)); + chan->aes_ctx_dec = (mbedtls_aes_context *)malloc(sizeof(mbedtls_aes_context)); + if (chan->aes_ctx_enc == NULL || chan->aes_ctx_dec == NULL) { + tuya_p2p_log_error("aes_ctx_enc or aes_ctx_dec is null\n"); + return -1; + } + memset(chan->aes_ctx_enc, 0, sizeof(mbedtls_aes_context)); + memset(chan->aes_ctx_dec, 0, sizeof(mbedtls_aes_context)); + mbedtls_aes_init(chan->aes_ctx_enc); + mbedtls_aes_init(chan->aes_ctx_dec); + int ret; + ret = mbedtls_aes_setkey_enc(chan->aes_ctx_enc, rtc->aes_key, sizeof(rtc->aes_key) * 8); + if (ret != 0) { + tuya_p2p_log_error("mbedtls_aes_setkey_enc failed\n"); + return -1; + } + ret = mbedtls_aes_setkey_dec(chan->aes_ctx_dec, rtc->aes_key, sizeof(rtc->aes_key) * 8); + if (ret != 0) { + tuya_p2p_log_error("mbedtls_aes_setkey_dec failed\n"); + return -1; + } + } + return 0; +} + +int rtc_crypt_encrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output) +{ + int ret = -1; + if (ctx != NULL) { + ret = mbedtls_aes_crypt_cbc(ctx, MBEDTLS_AES_ENCRYPT, length, iv, input, output); + } else { + tuya_p2p_log_error("aes_ctx_enc is null\n"); + } + return ret; +} + +int rtc_crypt_decrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output) +{ + int ret = -1; + if (ctx != NULL) { + ret = mbedtls_aes_crypt_cbc(ctx, MBEDTLS_AES_DECRYPT, length, iv, input, output); + } else { + tuya_p2p_log_error("aes_ctx_dec is null\n"); + } + return ret; +} + +int rtc_channel_aes_uninit(struct rtc_channel *chan) +{ + if (chan->aes_ctx_enc != NULL) { + mbedtls_aes_free(chan->aes_ctx_enc); + free(chan->aes_ctx_enc); + chan->aes_ctx_enc = NULL; + } + if (chan->aes_ctx_dec != NULL) { + mbedtls_aes_free(chan->aes_ctx_dec); + free(chan->aes_ctx_dec); + chan->aes_ctx_dec = NULL; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +uint32_t tuya_p2p_rtc_get_version() +{ + return (uint32_t)TUYA_P2P_SDK_VERSION_NUMBER; +} + +uint32_t tuya_p2p_rtc_get_skill() +{ + return g_uP2PSkill; +} + +int32_t tuya_p2p_rtc_deinit() +{ + if (g_pRtcSession == NULL) { + return TUYA_P2P_ERROR_NOT_INITIALIZED; + } + //tal_mutex_lock(g_p2p_session_mutex); + ctx_session_destroy(g_pRtcSession); + g_pRtcSession = NULL; + //tal_mutex_unlock(g_p2p_session_mutex); + tal_mutex_release(g_p2p_session_mutex); + g_p2p_session_mutex = NULL; + return 0; +} + +int32_t tuya_p2p_rtc_connect(char *remote_id, char *token, uint32_t token_len, char *trace_id, int lan_mode, + int timeout_ms) +{ + int ret = -1; + return ret; +} + +int32_t tuya_p2p_rtc_listen_break() +{ + return 0; +} + +int32_t tuya_p2p_rtc_get_session_info(int32_t handle, tuya_p2p_rtc_session_info_t *info) +{ + return 0; +} + +// int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason) +// { +// return 0; +// } + +int32_t tuya_p2p_rtc_send_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame) +{ + return 0; +} + +int32_t tuya_p2p_rtc_recv_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame) +{ + return 0; +} + +/////////////////////////////////////////////////////////////////////// +// logs + +static const char *level_names[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; + +void tuya_p2p_log_log(int level, const char *file, int line, const char *fmt, ...) +{ + // if (TUYA_P2P_LOG_DEBUG < tuya_p2p_log_get_level()) { + // return; + // } + + static char buf[8096] = {0}; + memset(buf, 0, sizeof(buf)); + va_list args; + va_start(args, fmt); + vsprintf(buf, fmt, args); + + cJSON *jlog = cJSON_CreateObject(); + cJSON *jt = cJSON_CreateNumber(tuya_p2p_misc_get_timestamp_ms()); + cJSON *jp = cJSON_CreateString(level_names[level]); + cJSON *jm = cJSON_CreateString(buf); + // cJSON *jf = cJSON_CreateString(file); + cJSON *jl = cJSON_CreateNumber(line); + + cJSON_AddItemToObject(jlog, "t", jt); + // cJSON_AddItemToObject(jlog, "f", jf); + cJSON_AddItemToObject(jlog, "l", jl); + cJSON_AddItemToObject(jlog, "p", jp); + cJSON_AddItemToObject(jlog, "m", jm); + + if (jlog != NULL) { + char *slog = cJSON_PrintUnformatted(jlog); + if (slog != NULL) { + // tuya_p2p_upload_log(level > TUYA_P2P_LOG_DEBUG ? TUYA_P2P_LOG_DEBUG : level, slog, strlen(slog)); + cJSON_free(slog); + } + + cJSON_Delete(jlog); + } + va_end(args); +} diff --git a/src/tuya_p2p/base_ice/src/tuya_misc.c b/src/tuya_p2p/base_ice/src/tuya_misc.c new file mode 100755 index 000000000..90c210b75 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_misc.c @@ -0,0 +1,417 @@ +#include "tuya_misc.h" +#include +#include +#include "tuya_log.h" +#include +#include +#include +#include +#include "mbedtls/pk.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/x509_crt.h" +#include "mbedtls/error.h" + +#define RTC_DEFAULT_TLS_CERT_ISSUER_NAME "CN=Cert,O=WebRTC,C=US" +#define RTC_DEFAULT_TLS_CERT_SUBJECT_NAME "CN=Cert,O=WebRTC,C=US" +#define RTC_DEFAULT_TLS_CERT_VERSION MBEDTLS_X509_CRT_VERSION_3 +#define RTC_DEFAULT_TLS_CERT_MD MBEDTLS_MD_SHA256 +#define RTC_DEFAULT_TLS_CERT_NOT_BEFORE "20180101000000" +#define RTC_DEFAULT_TLS_CERT_NOT_AFTER "20351231235959" + +uint64_t tuya_uv_hrtime2(void) +{ +#define NANOSEC ((uint64_t)1e9) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (((uint64_t)ts.tv_sec) * NANOSEC + ts.tv_nsec); +} + +uint64_t tuya_p2p_misc_get_current_time_ms() +{ + return tuya_uv_hrtime2() / 1000 / 1000; +} + +uint64_t tuya_p2p_misc_get_timestamp_ms() +{ + static uint64_t t = 0; + static uint64_t ms = 0; + if (ms == 0) { + t = time(NULL); + ms = tuya_p2p_misc_get_current_time_ms(); + } + uint64_t delta_ms = tuya_p2p_misc_get_current_time_ms() - ms; + return t * 1000 + delta_ms; +} + +int32_t tuya_p2p_misc_check_timeout(uint64_t tbegin, uint32_t timeout) +{ + uint64_t tnow = tuya_p2p_misc_get_timestamp_ms(); + if (tnow - tbegin >= timeout) { + return 1; + } else { + return 0; + } +} + +void tuya_p2p_misc_set_blocking(int fd, int blocking) +{ + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + tuya_p2p_log_error("get nonblock failed\n"); + return; + } + flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); + int ret = fcntl(fd, F_SETFL, flags); + if (ret < 0) { + tuya_p2p_log_error("set nonblock failed\n"); + } + return; +} + +char *tuya_p2p_misc_dump_buf(char *buf, int len) +{ + static char print_buf[8092]; + memset(print_buf, 0, sizeof(print_buf)); + int already = 0; + int remain = sizeof(print_buf) - 1; + int i; + for (i = 0; i < len; i++) { + int ret = snprintf(print_buf + already, remain, " %02hhx", buf[i]); + if (ret < 0) { + return print_buf; + } + already += ret; + remain -= ret; + } + return print_buf; +} + +void tuya_p2p_misc_rand_string(char *buf, uint32_t size) +{ + uint32_t i; + memset(buf, 0, size); + for (i = 0; i < size - 1; i++) { + uint8_t index = rand() % 62; // (62 = 26 + 26 + 10) + if (index < 10) { + buf[i] = '0' + index; + } else if (index < 36) { + buf[i] = 'A' + index - 10; + } else { + buf[i] = 'a' + index - 10 - 26; + } + } +} + +void tuya_p2p_misc_rand_string_dec(char *buf, uint32_t size) +{ + uint32_t i; + memset(buf, 0, size); + for (i = 0; i < size - 1; i++) { + uint8_t index = rand() % 10; // (62 = 26 + 26 + 10) + buf[i] = '0' + index; + } +} + +void tuya_p2p_misc_rand_hex(char *buf, uint32_t size) +{ + if (size == 0) { + return; + } + uint32_t i; + memset(buf, 0, size); + for (i = 0; i < size; i++) { + buf[i] = rand() % 0xff; + } +} + +unsigned char tuya_p2p_misc_hex_to_char(unsigned char hex) +{ + if (hex >= 0 && hex <= 9) { + return '0' + hex; + } else if (hex >= 10 && hex <= 15) { + return 'a' + (hex - 10); + } + return '0'; +} + +int tuya_p2p_misc_hex_to_string(char *dst_str, int dst_str_size, unsigned char *src_hex, int src_hex_size, char *sep) +{ + int i; + int already = 0; + for (i = 0; i < src_hex_size; i++) { + if (already + 2 > dst_str_size) { + return -1; + } + dst_str[already++] = tuya_p2p_misc_hex_to_char(src_hex[i] >> 4); + dst_str[already++] = tuya_p2p_misc_hex_to_char(src_hex[i] & 0x0F); + if (i != src_hex_size - 1 && sep) { + if (already + 1 > dst_str_size) { + return -1; + } + dst_str[already++] = *sep; + } + } + + if (already + 1 > dst_str_size) { + return -1; + } + dst_str[already++] = '\0'; + return 0; +} + +unsigned char tuya_p2p_misc_char_to_hex(unsigned char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } else if (c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } + return 0; +} + +char tuya_p2p_misc_char_to_lower(char c) +{ + if (c >= 'A' && c <= 'Z') { + c = 'a' + (c - 'A'); + } + return c; +} + +// case insensitive compare +int tuya_p2p_misc_strncicmp(char *a, char *b, int n) +{ + int i; + for (i = 0; i < n; i++) { + int d = tuya_p2p_misc_char_to_lower(a[i]) - tuya_p2p_misc_char_to_lower(b[i]); + if (d != 0) { + return d; + } + if (a[i] == 0) { + return 0; + } + } + return 0; +} + +int tuya_p2p_misc_generate_pkey(unsigned char *output_buf, size_t *len) +{ + if (output_buf == NULL || len == NULL) { + return -1; + } + + int rc = -1; + mbedtls_pk_context key; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_context entropy; + mbedtls_pk_init(&key); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + int ret; + + ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", -ret); + goto exit; + } + ret = mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_pk_setup returned -0x%04x", -ret); + goto exit; + } + ret = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(key), mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_ecp_gen_key returned -0x%04x", -ret); + goto exit; + } + + memset(output_buf, 0, *len); + ret = mbedtls_pk_write_key_pem(&key, output_buf, *len); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_pk_write_key_pem returned -0x%04x", -ret); + goto exit; + } + *len = strlen((char *)output_buf) + 1; + tuya_p2p_log_debug("pkey:\n%s\n", output_buf); + rc = 0; + +exit: + mbedtls_pk_free(&key); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + return rc; +} + +int mbedtls_test_rnd_zero_rand(void *rng_state, unsigned char *output, size_t len) +{ + if (rng_state != NULL) + rng_state = NULL; + + memset(output, 0, len); + + return (0); +} + +int tuya_p2p_misc_generate_cert(unsigned char *pkey, size_t pkey_len, unsigned char *output_buf, size_t *output_buf_len) +{ + int rc = -1; + mbedtls_pk_context *issuer_key = NULL; + mbedtls_pk_context *subject_key = NULL; + mbedtls_pk_context loaded_issuer_key; + mbedtls_x509write_cert crt; + mbedtls_mpi serial; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + int ret; + char buf[1024]; + memset(buf, 0, 1024); + + mbedtls_x509write_crt_init(&crt); + mbedtls_pk_init(&loaded_issuer_key); + mbedtls_mpi_init(&serial); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + + ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_ctr_drbg_seed returned %d - %s\n", ret, buf); + goto exit; + } + + char str_serial[20]; + tuya_p2p_misc_rand_string_dec(str_serial, sizeof(str_serial)); + ret = mbedtls_mpi_read_string(&serial, 10, str_serial); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_mpi_read_string " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + ret = mbedtls_pk_parse_key(&loaded_issuer_key, pkey, pkey_len, NULL, 0, mbedtls_test_rnd_zero_rand, NULL); +#else + ret = mbedtls_pk_parse_key(&loaded_issuer_key, pkey, pkey_len, NULL, 0); +#endif + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_pk_parse_keyfile " + "returned -x%02x - %s\n\n", + -ret, buf); + goto exit; + } + + issuer_key = &loaded_issuer_key; + subject_key = &loaded_issuer_key; + mbedtls_x509write_crt_set_subject_key(&crt, subject_key); + mbedtls_x509write_crt_set_issuer_key(&crt, issuer_key); + + ret = mbedtls_x509write_crt_set_subject_name(&crt, RTC_DEFAULT_TLS_CERT_SUBJECT_NAME); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_subject_name " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + ret = mbedtls_x509write_crt_set_issuer_name(&crt, RTC_DEFAULT_TLS_CERT_ISSUER_NAME); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_issuer_name " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + mbedtls_x509write_crt_set_version(&crt, RTC_DEFAULT_TLS_CERT_VERSION); + mbedtls_x509write_crt_set_md_alg(&crt, RTC_DEFAULT_TLS_CERT_MD); + + ret = mbedtls_x509write_crt_set_serial(&crt, &serial); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_serial " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + ret = mbedtls_x509write_crt_set_validity(&crt, RTC_DEFAULT_TLS_CERT_NOT_BEFORE, RTC_DEFAULT_TLS_CERT_NOT_AFTER); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_validity " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + memset(output_buf, 0, *output_buf_len); + ret = mbedtls_x509write_crt_pem(&crt, output_buf, *output_buf_len, mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret < 0) { + goto exit; + } + + *output_buf_len = strlen((char *)output_buf) + 1; + tuya_p2p_log_debug("cert:\n%s\n", output_buf); + rc = 0; + +exit: + mbedtls_x509write_crt_free(&crt); + mbedtls_pk_free(&loaded_issuer_key); + mbedtls_mpi_free(&serial); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + return rc; +} + +int tuya_p2p_misc_calculate_cert_fingerprint(char *md_type, unsigned char *cert, int cert_len, char *fingerprint, + int fingerprint_len) +{ + const mbedtls_md_info_t *md_info = NULL; + if (strcmp(md_type, "sha-1") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + } else if (strcmp(md_type, "sha-224") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA224); + } else if (strcmp(md_type, "sha-256") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + } else if (strcmp(md_type, "sha-384") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + } else if (strcmp(md_type, "sha-512") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + } + + if (md_info == NULL) { + tuya_p2p_log_error("calculate cert fingerprint: invalid md type\n"); + //*flags |= MBEDTLS_X509_BADCERT_OTHER; + // tuya_p2p_log_error("on dtls verify cert failed: invalid md type\n"); + return -1; + } + + mbedtls_x509_crt x509_crt; + mbedtls_x509_crt_init(&x509_crt); + int ret = mbedtls_x509_crt_parse(&x509_crt, cert, cert_len); + if (ret != 0) { + tuya_p2p_log_error("calculate cert fingerprint: parse crt\n"); + return -1; + } + + unsigned char md[1024]; + mbedtls_md(md_info, x509_crt.raw.p, x509_crt.raw.len, (unsigned char *)md); + mbedtls_x509_crt_free(&x509_crt); + + snprintf(fingerprint, fingerprint_len, "%s ", md_type); + int already = strlen(fingerprint); + + char sep = ':'; + ret = tuya_p2p_misc_hex_to_string(fingerprint + already, fingerprint_len - already, md, + mbedtls_md_get_size(md_info), &sep); + if (ret < 0) { + tuya_p2p_log_error("calculate cert fingerprint: hex to string\n"); + return -1; + } + + return 0; +} diff --git a/src/tuya_p2p/base_ice/src/tuya_misc.h b/src/tuya_p2p/base_ice/src/tuya_misc.h new file mode 100755 index 000000000..a5b9a563c --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_misc.h @@ -0,0 +1,47 @@ +#ifndef __TUYA_MISC_H__ +#define __TUYA_MISC_H__ + +#include +#include +#include +#define BC_MSG_SIZE_MAX (200 * 1024) +#define TUYA_P2P_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#define TUYA_P2P_SDK_VERSION_NUMBER (0xF4030478) +#define TUYA_P2P_SDK_VERSION "tuya_p2p_sdk_v3.4.120" + +#define TUYA_P2P_SDK_SKILL_BASIC (1) +#define TUYA_P2P_SDK_SKILL_UDP_TCP_RELAY (2) +#define TUYA_P2P_SDK_SKILL_PRECONNECT (32) +#define TUYA_P2P_SDK_SKILL_MULTIMEDIA (64) +#define TUYA_P2P_SDK_SKILL_IPV6 (TUYA_P2P_SDK_SKILL_BASIC << 9) +#define TUYA_P2P_SDK_SKILL_NUMBER \ + (TUYA_P2P_SDK_SKILL_BASIC | TUYA_P2P_SDK_SKILL_UDP_TCP_RELAY | TUYA_P2P_SDK_SKILL_PRECONNECT | \ + TUYA_P2P_SDK_SKILL_MULTIMEDIA | TUYA_P2P_SDK_SKILL_IPV6) + +enum { AI_LOOPBACK = 1, AI_LINKLOCAL = 2, AI_DISABLE = 3, AI_NORMAL = 4 }; + +#ifndef TUYA_MIN +#define TUYA_MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif +#ifndef TUYA_MAX +#define TUYA_MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +uint64_t tuya_p2p_misc_get_timestamp_ms(); +int32_t tuya_p2p_misc_check_timeout(uint64_t tbegin, uint32_t timeout); +void tuya_p2p_misc_rand_string(char *buf, uint32_t size); +void tuya_p2p_misc_rand_hex(char *buf, uint32_t size); +int tuya_p2p_misc_strncicmp(char *a, char *b, int n); + +unsigned char tuya_p2p_misc_hex_to_char(unsigned char hex); +unsigned char tuya_p2p_misc_char_to_hex(unsigned char c); +void tuya_p2p_misc_set_blocking(int fd, int blocking); +char *tuya_p2p_misc_dump_buf(char *buf, int len); +int tuya_p2p_misc_generate_pkey(unsigned char *output_buf, size_t *len); +int tuya_p2p_misc_generate_cert(unsigned char *pkey, size_t pkey_len, unsigned char *output_buf, + size_t *output_buf_len); +int tuya_p2p_misc_calculate_cert_fingerprint(char *md_type, unsigned char *cert, int cert_len, char *fingerprint, + int fingerprint_len); +int mbedtls_test_rnd_zero_rand(void *rng_state, unsigned char *output, size_t len); +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_rtp.h b/src/tuya_p2p/base_ice/src/tuya_rtp.h new file mode 100755 index 000000000..bcba881e1 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_rtp.h @@ -0,0 +1,151 @@ +#ifndef __TUYA_RTP_H__ +#define __TUYA_RTP_H__ + +#include +#include + +#pragma pack(1) + +typedef enum { + RTX_MODE_DISABLED, + RTX_MODE_REPLAY, + RTX_MODE_SSRC_MULTIPLEX, + RTX_MODE_SESSION_MULTIPLEX +} tuya_p2p_rtp_rtx_mode_e; + +typedef struct tuya_p2p_rtp_hdr { +#if defined(TUYA_P2P_IS_BIG_ENDIAN) && (TUYA_P2P_IS_BIG_ENDIAN != 0) + uint16_t v : 2; /**< packet type/version */ + uint16_t p : 1; /**< padding flag */ + uint16_t x : 1; /**< extension flag */ + uint16_t cc : 4; /**< CSRC count */ + uint16_t m : 1; /**< marker bit */ + uint16_t pt : 7; /**< payload type */ +#else + uint16_t cc : 4; /**< CSRC count */ + uint16_t x : 1; /**< header extension flag */ + uint16_t p : 1; /**< padding flag */ + uint16_t v : 2; /**< packet type/version */ + uint16_t pt : 7; /**< payload type */ + uint16_t m : 1; /**< marker bit */ +#endif + uint16_t seq; /**< sequence number */ + uint32_t ts; /**< timestamp */ + uint32_t ssrc; /**< synchronization source */ +} tuya_p2p_rtp_hdr_t; +#pragma pack() + +typedef struct tuya_p2p_rtp_ext_hdr { + uint16_t profile_data; /**< Profile data. */ + uint16_t length; /**< Length. */ +} tuya_p2p_rtp_ext_hdr_t; + +typedef struct tuya_p2p_rtp_dec_hdr { + /* RTP extension header output decode */ + tuya_p2p_rtp_ext_hdr_t *ext_hdr; + uint32_t *ext; + unsigned ext_len; +} tuya_p2p_rtp_dec_hdr_t; + +typedef struct tuya_p2p_rtp_pkt { + tuya_p2p_rtp_hdr_t **hdr; + tuya_p2p_rtp_dec_hdr_t ext_hdr; + unsigned payload_len; + void *payload; +} tuya_p2p_rtp_pkt_t; + +typedef struct tuya_p2p_rtp_seq_session { + uint16_t max_seq; /**< Highest sequence number heard */ + uint32_t cycles; /**< Shifted count of seq number cycles */ + uint32_t base_seq; /**< Base seq number */ + uint32_t bad_seq; /**< Last 'bad' seq number + 1 */ + uint32_t probation; /**< Sequ. packets till source is valid */ +} tuya_p2p_rtp_seq_session_t; + +typedef struct tuya_p2p_rtp_session { + tuya_p2p_rtp_hdr_t out_hdr; /**< Saved hdr for outgoing pkts. */ + tuya_p2p_rtp_seq_session_t seq_ctrl; /**< Sequence number management. */ + uint16_t out_pt; /**< Default outgoing payload type. */ + uint32_t out_extseq; /**< Outgoing extended seq #. */ + uint32_t peer_ssrc; /**< Peer SSRC. */ + uint32_t received; /**< Number of received packets. */ +} tuya_p2p_rtp_session_t; + +typedef struct tuya_p2p_rtp_status { + union { + struct flag { + int bad : 1; /**< General flag to indicate that sequence is + bad, and application should not process + this packet. More information will be given + in other flags. */ + int badpt : 1; /**< Bad payload type. */ + int badssrc : 1; /**< Bad SSRC */ + int dup : 1; /**< Indicates duplicate packet */ + int outorder : 1; /**< Indicates out of order packet */ + int probation : 1; /**< Indicates that session is in probation + until more packets are received. */ + int restart : 1; /**< Indicates that sequence number has made + a large jump, and internal base sequence + number has been adjusted. */ + } flag; /**< Status flags. */ + + uint16_t value; /**< Status value, to conveniently address all + flags. */ + + } status; /**< Status information union. */ + + uint16_t diff; /**< Sequence number difference from previous + packet. Normally the value should be 1. + Value greater than one may indicate packet + loss. If packet with lower sequence is + received, the value will be set to zero. + If base sequence has been restarted, the + value will be one. */ +} tuya_p2p_rtp_status_t; + +typedef struct tuya_p2p_rtp_session_setting { + uint8_t flags; /**< Bitmask flags to specify whether such + field is set. Bitmask contents are: + (bit #0 is LSB) + bit #0: default payload type + bit #1: sender SSRC + bit #2: sequence + bit #3: timestamp */ + int default_pt; /**< Default payload type. */ + uint32_t sender_ssrc; /**< Sender SSRC. */ + uint16_t seq; /**< Sequence. */ + uint32_t ts; /**< Timestamp. */ +} tuya_p2p_rtp_session_setting; + +void tuya_p2p_rtp_dump_rtp_hdr(tuya_p2p_rtp_hdr_t *rtp_hdr); +void tuya_p2p_rtp_dump_rtp(tuya_p2p_rtp_pkt_t *pkt); + +int32_t tuya_p2p_rtp_session_init(tuya_p2p_rtp_session_t *ses, int default_pt, uint32_t sender_ssrc); + +int32_t tuya_p2p_rtp_session_init2(tuya_p2p_rtp_session_t *ses, tuya_p2p_rtp_session_setting settings); + +int32_t tuya_p2p_rtp_encode_rtp(tuya_p2p_rtp_session_t *ses, int pt, int m, int payload_len, int ts_len, + const void **rtphdr, int *hdrlen); + +int32_t tuya_p2p_rtp_decode_rtp(tuya_p2p_rtp_session_t *ses, const void *pkt, int pkt_len, + const tuya_p2p_rtp_hdr_t **hdr, const void **payload, unsigned *payloadlen); + +int32_t tuya_p2p_rtp_decode_rtp2(tuya_p2p_rtp_session_t *ses, const void *pkt, int pkt_len, + const tuya_p2p_rtp_hdr_t **hdr, tuya_p2p_rtp_dec_hdr_t *dec_hdr, const void **payload, + unsigned *payloadlen); + +void tuya_p2p_rtp_session_update(tuya_p2p_rtp_session_t *ses, const tuya_p2p_rtp_hdr_t *hdr, + tuya_p2p_rtp_status_t *seq_st); + +void tuya_p2p_rtp_session_update2(tuya_p2p_rtp_session_t *ses, const tuya_p2p_rtp_hdr_t *hdr, + tuya_p2p_rtp_status_t *seq_st, bool check_pt); + +void tuya_p2p_rtp_seq_init(tuya_p2p_rtp_seq_session_t *seq_ctrl, uint16_t seq); + +void tuya_p2p_rtp_seq_update(tuya_p2p_rtp_seq_session_t *seq_ctrl, uint16_t seq, tuya_p2p_rtp_status_t *seq_status); + +uint32_t tuya_p2p_rtp_get_timestamp(void *rtphdr); +int32_t tuya_p2p_rtp_set_timestamp(void *rtphdr, uint32_t ts); +uint16_t tuya_p2p_rtp_get_seq(void *rtphdr); + +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_sdp.c b/src/tuya_p2p/base_ice/src/tuya_sdp.c new file mode 100755 index 000000000..929bff4eb --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_sdp.c @@ -0,0 +1,1290 @@ +#include "tuya_sdp.h" +#include "tuya_misc.h" +#include "tuya_log.h" +#include + +#define SDP_TEMPLATE_PART_SESSION_HEADER \ + "v=0\r\n" \ + "o=- %lu 1 IN IP4 127.0.0.1\r\n" \ + "s=-\r\n" \ + "t=0 0\r\n" \ + "a=group:BUNDLE%s\r\n" \ + "a=msid-semantic: WMS %s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_HEADER "m=%s 9 %s%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT \ + "c=IN IP4 0.0.0.0\r\n" \ + "a=rtcp:9 IN IP4 0.0.0.0\r\n" \ + "a=ice-ufrag:%s\r\n" \ + "a=ice-pwd:%s\r\n" \ + "a=ice-options:trickle\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT_CANDIDATE "%s" + +#define SDP_TEMPLATE_PART_MEDIA_DTLS \ + "a=fingerprint:%s\r\n" \ + "a=setup:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_MID "a=mid:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_DIRECTION "a=%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_MSID "a=msid:%s %s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_RTCP_MUX "a=rtcp-mux\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_SSRC "a=ssrc:%u cname:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_SSRC_GROUP \ + "a=ssrc-group:FID %u %u\r\n" \ + "a=ssrc:%u cname:%s\r\n" \ + "a=ssrc:%u cname:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP "a=rtpmap:%d %s %d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_AUDIO_RTPMAP "a=rtpmap:%d %s/%d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPMAP "a=rtpmap:%d %s/%d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_FIR "a=rtcp-fb:%d ccm fir\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_NACK "a=rtcp-fb:%d nack\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_PLI "a=rtcp-fb:%d nack pli\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_ATTR \ + "a=fmtp:%d level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_RTX \ + "a=rtpmap:%d rtx/%d\r\n" \ + "a=fmtp:%d apt=%d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_KEY "a=aes-key:%s\r\n" + +rtc_audio_codec_t default_audio_rtpmaps[] = {{{NULL, NULL}, "PCMU", 0, 0, 8000, 1}}; + +static rtc_audio_codec_t *tuya_p2p_rtc_sdp_find_default_audio_codec(int pt) +{ + uint32_t i; + for (i = 0; i < sizeof(default_audio_rtpmaps) / sizeof(rtc_audio_codec_t); i++) { + rtc_audio_codec_t *codec = &default_audio_rtpmaps[i]; + if (codec->pt == pt) { + return codec; + } + } + return NULL; +} + +static char *tuya_p2p_rtc_media_direction_str(tuya_p2p_rtc_media_direction_e direction) +{ + if (direction == MEDIA_DIRECTION_NONE) { + return "none"; + } else if (direction == MEDIA_DIRECTION_SENDONLY) { + return "sendonly"; + } else if (direction == MEDIA_DIRECTION_RECVONLY) { + return "recvonly"; + } else if (direction == MEDIA_DIRECTION_SENDRECV) { + return "sendrecv"; + } + return ""; +} +#if 0 +static tuya_p2p_rtc_media_direction_e tuya_p2p_rtc_media_direction_number(char *direction){ + if(strcmp(direction, "sendonly") == 0){ + return MEDIA_DIRECTION_SENDONLY; + }else if(strcmp(direction, "recvonly") == 0){ + return MEDIA_DIRECTION_RECVONLY; + }else if(strcmp(direction, "sendrecv") == 0){ + return MEDIA_DIRECTION_SENDRECV; + } + return MEDIA_DIRECTION_NONE; +} +#endif +static int tuya_p2p_rtc_sdp_encode_session(rtc_sdp_t *sdp, char *buf, int size) +{ + int ret; + int index = 0; + char bundle[64] = {0}; + QUEUE *q; + QUEUE_FOREACH(q, &sdp->media_info_list.queue) + { + media_info_t *info = QUEUE_DATA(q, media_info_t, queue); + ret = snprintf(bundle + index, sizeof(bundle) - index, " %s", info->mid); + if (ret < 0 || ret >= (int)sizeof(bundle) - index) { + return -1; + } + index += ret; + } + + ret = snprintf(buf, size, SDP_TEMPLATE_PART_SESSION_HEADER, (unsigned long)time(NULL), bundle, sdp->wms_id); + if (ret < 0 || ret >= size) { + return -1; + } + return ret; +} + +static int tuya_p2p_rtc_sdp_encode_media_audio(rtc_sdp_t *sdp, char *type, char *mid, char *buf, int size) +{ + int ret; + int index = 0; + int already = 0; + int remain = size; + + char pt[128] = {0}; + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + ret = snprintf(pt + index, sizeof(pt) - index, " %d", codec->pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } else { + ret = snprintf(pt, sizeof(pt), " %d", sdp->audio.negotiated_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt)) { + return -1; + } + } + + // m line + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_HEADER, "audio", "UDP/TLS/RTP/SAVPF", pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // ice + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_TRANSPORT, sdp->ufrag, sdp->password); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // dtls + char dtls_role[32] = {0}; + if (sdp->dtls_role == DTLS_ROLE_CLIENT) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "active"); + } else if (sdp->dtls_role == DTLS_ROLE_SERVER) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "passive"); + } else { + snprintf(dtls_role, sizeof(dtls_role), "%s", "actpass"); + } + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DTLS, sdp->fingerprint, dtls_role); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // mid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MID, mid); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // media direction + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DIRECTION, + tuya_p2p_rtc_media_direction_str(sdp->audio.direction)); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // msid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MSID, sdp->wms_id, sdp->audio.track_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp-mux + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTCP_MUX); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtpmap + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_AUDIO_RTPMAP, codec->pt, codec->name, + codec->sample_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } else { + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_AUDIO_RTPMAP, sdp->audio.negotiated_codec.pt, + sdp->audio.negotiated_codec.name, sdp->audio.negotiated_codec.sample_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + + // ssrc + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC, sdp->audio.negotiated_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + return already; +} + +static int tuya_p2p_rtc_sdp_encode_media_video(rtc_sdp_t *sdp, char *type, char *mid, char *buf, int size) +{ + int ret; + int index = 0; + int already = 0; + int remain = size; + + char pt[128] = {0}; + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + ret = snprintf(pt + index, sizeof(pt) - index, " %d", codec->pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } else { + ret = snprintf(pt + index, sizeof(pt) - index, " %d", sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + + if (sdp->video.rtx_codec.pt != -1) { + ret = snprintf(pt + index, sizeof(pt) - index, " %d", sdp->video.rtx_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } + + // m line + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_HEADER, "video", "UDP/TLS/RTP/SAVPF", pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // ice + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_TRANSPORT, sdp->ufrag, sdp->password); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // dtls + char dtls_role[32] = {0}; + if (sdp->dtls_role == DTLS_ROLE_CLIENT) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "active"); + } else if (sdp->dtls_role == DTLS_ROLE_SERVER) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "passive"); + } else { + snprintf(dtls_role, sizeof(dtls_role), "%s", "actpass"); + } + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DTLS, sdp->fingerprint, dtls_role); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // mid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MID, mid); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // media direction + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DIRECTION, + tuya_p2p_rtc_media_direction_str(sdp->video.direction)); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // msid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MSID, sdp->wms_id, sdp->video.track_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp-mux + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTCP_MUX); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + if (strcmp(codec->name, "rtx")) { + // rtpmap + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPMAP, codec->pt, codec->name, + codec->clock_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp ccm fir + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_FIR, codec->pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp nack + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_NACK, codec->pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp pli + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_PLI, codec->pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // attr + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_ATTR, codec->pt, + codec->profile_level_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } else { + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTX, codec->pt, codec->clock_rate, + codec->pt, codec->original_pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } + } else { + // rtpmap + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPMAP, sdp->video.negotiated_codec.pt, + sdp->video.negotiated_codec.name, sdp->video.negotiated_codec.clock_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp ccm fir + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_FIR, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp nack + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_NACK, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp pli + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_PLI, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // attr + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_ATTR, sdp->video.negotiated_codec.pt, + sdp->video.negotiated_codec.profile_level_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + if (sdp->video.rtx_mode == RTX_MODE_SSRC_MULTIPLEX) { + // rtx + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTX, sdp->video.rtx_codec.pt, + sdp->video.rtx_codec.clock_rate, sdp->video.rtx_codec.pt, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } + + if (sdp->video.rtx_mode == RTX_MODE_SSRC_MULTIPLEX) { + // ssrc group + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC_GROUP, sdp->video.negotiated_codec.ssrc, + sdp->video.rtx_codec.ssrc, sdp->video.negotiated_codec.ssrc, sdp->cname, + sdp->video.rtx_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } else { + // ssrc + ret = + snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC, sdp->video.negotiated_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + + return already; +} + +static int tuya_p2p_rtc_sdp_encode_media_tuya(rtc_sdp_t *sdp, char *type, char *mid, char *buf, int size) +{ + (void)sdp; + (void)type; + int ret; + int index = 0; + int already = 0; + int remain = size; + + char pt[128] = {0}; + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->tuya.codec_list) + { + rtc_tuya_codec_t *codec = QUEUE_DATA(q, rtc_tuya_codec_t, queue); + ret = snprintf(pt + index, sizeof(pt) - index, " %d", codec->pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } else { + ret = snprintf(pt, sizeof(pt), " %d", sdp->tuya.negotiated_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt)) { + return -1; + } + } + + // m line + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_HEADER, "application", "tuya", pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // ice + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_TRANSPORT, sdp->ufrag, sdp->password); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // aes key + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_APPLICATION_KEY, sdp->aes_key); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // mid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MID, mid); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtpmap + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->tuya.codec_list) + { + rtc_tuya_codec_t *codec = QUEUE_DATA(q, rtc_tuya_codec_t, queue); + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP, codec->pt, codec->name, + codec->channel_number); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } else { + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP, sdp->tuya.negotiated_codec.pt, + sdp->tuya.negotiated_codec.name, sdp->tuya.negotiated_codec.channel_number); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + + // ssrc + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC, sdp->video.negotiated_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + return already; +} + +static int tuya_p2p_rtc_sdp_set_original_pt(rtc_sdp_t *sdp, int pt, int original_pt) +{ + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + if (codec->pt == pt) { + codec->original_pt = original_pt; + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_init(rtc_sdp_t *sdp, char *session_id, char *local_id, char *fingerprint, char *ufrag, + char *password, tuya_p2p_rtc_dtls_role_e dtls_role) +{ + memset(sdp, 0, sizeof(*sdp)); + snprintf(sdp->wms_id, sizeof(sdp->wms_id), "%s", session_id); + snprintf(sdp->cname, sizeof(sdp->cname), "%s", local_id); + snprintf(sdp->fingerprint, sizeof(sdp->fingerprint), "%s", fingerprint); + sdp->dtls_role = dtls_role; + if (ufrag != NULL && password != NULL) { + snprintf(sdp->ufrag, sizeof(sdp->ufrag), "%s", ufrag); + snprintf(sdp->password, sizeof(sdp->password), "%s", password); + } + + QUEUE_INIT(&sdp->media_info_list.queue); + QUEUE_INIT(&sdp->candidates.queue); + + // audio + tuya_p2p_misc_rand_string(sdp->audio.track_id, 32); + sdp->audio.rtx_mode = RTX_MODE_DISABLED; + sdp->audio.direction = MEDIA_DIRECTION_SENDRECV; + sdp->audio.rtx_codec.pt = -1; + QUEUE_INIT(&sdp->audio.codec_list); + + // video + tuya_p2p_misc_rand_string(sdp->video.track_id, 32); + sdp->video.rtx_mode = RTX_MODE_REPLAY; + sdp->video.direction = MEDIA_DIRECTION_SENDONLY; + sdp->video.rtx_codec.pt = -1; + QUEUE_INIT(&sdp->video.codec_list); + + // tuya + QUEUE_INIT(&sdp->tuya.codec_list); + sdp->inited = 1; + return 0; +} + +int tuya_p2p_rtc_sdp_deinit(rtc_sdp_t *sdp) +{ + if (sdp->inited == 0) { + return 0; + } + + while (!QUEUE_EMPTY(&sdp->candidates.queue)) { + QUEUE *q = QUEUE_HEAD(&sdp->candidates.queue); + QUEUE_REMOVE(q); + rtc_cand_t *cand = QUEUE_DATA(q, rtc_cand_t, queue); + free(cand); + } + while (!QUEUE_EMPTY(&sdp->media_info_list.queue)) { + QUEUE *q = QUEUE_HEAD(&sdp->media_info_list.queue); + QUEUE_REMOVE(q); + media_info_t *info = QUEUE_DATA(q, media_info_t, queue); + free(info); + } + while (!QUEUE_EMPTY(&sdp->audio.codec_list)) { + QUEUE *q = QUEUE_HEAD(&sdp->audio.codec_list); + QUEUE_REMOVE(q); + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + free(codec); + } + while (!QUEUE_EMPTY(&sdp->video.codec_list)) { + QUEUE *q = QUEUE_HEAD(&sdp->video.codec_list); + QUEUE_REMOVE(q); + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + free(codec); + } + while (!QUEUE_EMPTY(&sdp->tuya.codec_list)) { + QUEUE *q = QUEUE_HEAD(&sdp->tuya.codec_list); + QUEUE_REMOVE(q); + rtc_tuya_codec_t *codec = QUEUE_DATA(q, rtc_tuya_codec_t, queue); + free(codec); + } + return 0; +} + +int tuya_p2p_rtc_sdp_set_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len) +{ + if (len * 2 >= sizeof(sdp->aes_key)) { + return -1; + } + + memset(sdp->aes_key, 0, sizeof(sdp->aes_key)); + uint32_t i; + for (i = 0; i < len; i++) { + sdp->aes_key[2 * i] = tuya_p2p_misc_hex_to_char((aes_key[i] & 0xf0) >> 4); + sdp->aes_key[2 * i + 1] = tuya_p2p_misc_hex_to_char(aes_key[i] & 0x0f); + } + return 0; +} + +int tuya_p2p_rtc_sdp_get_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len) +{ + if (len * 2 >= sizeof(sdp->aes_key)) { + return -1; + } + uint32_t i; + for (i = 0; i < len; i++) { + aes_key[i] = tuya_p2p_misc_char_to_hex(sdp->aes_key[2 * i]) << 4; + aes_key[i] |= tuya_p2p_misc_char_to_hex(sdp->aes_key[2 * i + 1]); + } + return 0; +} + +int tuya_p2p_rtc_sdp_set_dtls_cert_fingerprint(rtc_sdp_t *sdp, char *fingerprint) +{ + snprintf(sdp->fingerprint, sizeof(sdp->fingerprint), "%s", fingerprint); + return 0; +} + +int tuya_p2p_rtc_sdp_add_media(rtc_sdp_t *sdp, char *mid, char *type) +{ + media_info_t *m = (media_info_t *)malloc(sizeof(media_info_t)); + if (m == NULL) { + return -1; + } + memset(m, 0, sizeof(*m)); + snprintf(m->mid, sizeof(m->mid), "%s", mid); + snprintf(m->type, sizeof(m->type), "%s", type); + QUEUE_INSERT_TAIL(&sdp->media_info_list.queue, &m->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_set_media_type(rtc_sdp_t *sdp, char *mid, char *type) +{ + QUEUE *q; + QUEUE_FOREACH(q, &sdp->media_info_list.queue) + { + media_info_t *m = QUEUE_DATA(q, media_info_t, queue); + if (strcmp(m->mid, mid) == 0) { + snprintf(m->type, sizeof(m->type), "%s", type); + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_add_audio_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int sample_rate, + int channel_number) +{ + if (pt != 0) { + return -1; + } + // printf("add audio codec: %s %d %u %d %d\n", name, pt, ssrc, sample_rate, channel_number); + rtc_audio_codec_t *codec = (rtc_audio_codec_t *)malloc(sizeof(rtc_audio_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + + if (name == NULL) { + rtc_audio_codec_t *default_codec; + default_codec = tuya_p2p_rtc_sdp_find_default_audio_codec(pt); + if (default_codec == NULL) { + if (codec != NULL) { + free(codec); + } + return -1; + } + snprintf(codec->name, sizeof(codec->name), "%s", default_codec->name); + codec->pt = default_codec->pt; + codec->ssrc = default_codec->ssrc; + codec->sample_rate = default_codec->sample_rate; + codec->channel_number = default_codec->channel_number; + } else { + snprintf(codec->name, sizeof(codec->name), "%s", name); + codec->pt = pt; + codec->ssrc = ssrc; + codec->sample_rate = sample_rate; + codec->channel_number = channel_number; + } + + QUEUE_INSERT_TAIL(&sdp->audio.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_video_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int clock_rate, + char *profile_level_id) +{ + rtc_video_codec_t *codec = (rtc_video_codec_t *)malloc(sizeof(rtc_video_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + + snprintf(codec->name, sizeof(codec->name), "%s", name); + codec->original_pt = -1; + codec->pt = pt; + codec->ssrc = ssrc; + codec->clock_rate = clock_rate; + snprintf(codec->profile_level_id, sizeof(codec->profile_level_id), "%s", profile_level_id); + + QUEUE_INSERT_TAIL(&sdp->video.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_video_rtx_codec(rtc_sdp_t *sdp, int origin_pt, int pt, uint32_t ssrc, int clock_rate) +{ + rtc_video_codec_t *codec = (rtc_video_codec_t *)malloc(sizeof(rtc_video_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + + snprintf(codec->name, sizeof(codec->name), "%s", "rtx"); + codec->original_pt = origin_pt; + codec->pt = pt; + codec->ssrc = ssrc; + codec->clock_rate = clock_rate; + + QUEUE_INSERT_TAIL(&sdp->video.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_tuya_codec(rtc_sdp_t *sdp, char *name, int pt, int channel_number) +{ + rtc_tuya_codec_t *codec = (rtc_tuya_codec_t *)malloc(sizeof(rtc_tuya_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + snprintf(codec->name, sizeof(codec->name), "%s", name); + codec->pt = pt; + codec->channel_number = channel_number; + + QUEUE_INSERT_TAIL(&sdp->tuya.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_candidate(rtc_sdp_t *sdp, char *candidate) +{ + QUEUE *q; + QUEUE_FOREACH(q, &sdp->candidates.queue) + { + rtc_cand_t *cand = QUEUE_DATA(q, rtc_cand_t, queue); + if (strcmp(cand->str, candidate) == 0) { + return 0; + } + } + + rtc_cand_t *cand = (rtc_cand_t *)malloc(sizeof(rtc_cand_t)); + if (cand == NULL) { + return -1; + } + memset(cand, 0, sizeof(*cand)); + snprintf(cand->str, sizeof(cand->str), "%s", candidate); + cand->time_ms = tuya_p2p_misc_get_timestamp_ms(); + QUEUE_INSERT_TAIL(&sdp->candidates.queue, &cand->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_update_audio_codec(rtc_sdp_t *sdp, int pt, char *name, char *sample_rate, char *channel_number) +{ + tuya_p2p_log_debug("update audio codec: pt = %d, codec = %s, %s, %s\n", pt, name, sample_rate, channel_number); + QUEUE *q; + QUEUE_FOREACH(q, &sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + if (codec->pt == pt) { + if (name != NULL) { + snprintf(codec->name, sizeof(codec->name), "%s", name); + } + if (sample_rate != NULL) { + codec->sample_rate = atoi(sample_rate); + } + if (channel_number != NULL) { + codec->channel_number = atoi(channel_number); + } else { + codec->channel_number = 1; + } + break; + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_update_video_codec(rtc_sdp_t *sdp, int pt, char *name, char *clock_rate) +{ + tuya_p2p_log_debug("update video codec: pt = %d, codec = %s, %s\n", pt, name, clock_rate); + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + if (codec->pt == pt) { + if (name != NULL) { + snprintf(codec->name, sizeof(codec->name), "%s", name); + } + if (clock_rate != NULL) { + codec->clock_rate = atoi(clock_rate); + } + break; + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_encode(rtc_sdp_t *sdp, char *type, char *buf, int size) +{ + int ret; + int already = 0; + int remain = size; + ret = tuya_p2p_rtc_sdp_encode_session(sdp, buf + already, remain); + if (ret < 0 || ret >= remain) { + return -1; + } + remain -= ret; + already += ret; + + QUEUE *q; + QUEUE_FOREACH(q, &sdp->media_info_list.queue) + { + media_info_t *info = QUEUE_DATA(q, media_info_t, queue); + if (strcmp(info->type, "audio") == 0) { + ret = tuya_p2p_rtc_sdp_encode_media_audio(sdp, type, info->mid, buf + already, remain); + } else if (strcmp(info->type, "video") == 0) { + ret = tuya_p2p_rtc_sdp_encode_media_video(sdp, type, info->mid, buf + already, remain); + } else if (strcmp(info->type, "tuya") == 0) { + ret = tuya_p2p_rtc_sdp_encode_media_tuya(sdp, type, info->mid, buf + already, remain); + } else { + ret = 0; + } + if (ret < 0 || ret >= remain) { + return -1; + } + remain -= ret; + already += ret; + } + + return already; +} + +int tuya_p2p_rtc_sdp_decode(rtc_sdp_t *sdp, char *buf) +{ + char *lasts = NULL; + char *p = strtok_r(buf, "\r\n", &lasts); + if (p == NULL) { + return 0; + } + char m = '0'; + while (1) { + p = strtok_r(NULL, "\r\n", &lasts); + if (p == NULL) { + break; + } + if (strncmp(p, "a=msid-semantic:", strlen("a=msid-semantic:")) == 0) { + p += strlen("a=msid-semantic:"); + char wms_name[65] = {0}; + char wms_id[65] = {0}; + int cnt = sscanf(p, "%s %s", wms_name, wms_id); + if (cnt != 2 || strcmp(wms_name, "WMS") != 0) { + continue; + } + snprintf(sdp->wms_id, sizeof(sdp->wms_id), "%s", wms_id); + continue; + } + if (strncmp(p, "a=msid:", strlen("a=msid:")) == 0) { + p += strlen("a=msid:"); + char msid[65] = {0}; + char track_id[65] = {0}; + int cnt = sscanf(p, "%s %s", msid, track_id); + if (cnt != 2 || strcmp(msid, sdp->wms_id) != 0) { + continue; + } + if (m == 'a') { + snprintf(sdp->audio.track_id, sizeof(sdp->audio.track_id), "%s", track_id); + } else if (m == 'v') { + snprintf(sdp->video.track_id, sizeof(sdp->video.track_id), "%s", track_id); + } + continue; + } + if (strncmp(p, "a=group:BUNDLE", strlen("a=group:BUNDLE")) == 0) { + p += strlen("a=group:BUNDLE"); + char m1[65] = {0}; + char m2[65] = {0}; + char m3[65] = {0}; + int cnt = sscanf(p, "%s %s %s", m1, m2, m3); + if (cnt >= 1) { + tuya_p2p_rtc_sdp_add_media(sdp, m1, ""); + } + if (cnt >= 2) { + tuya_p2p_rtc_sdp_add_media(sdp, m2, ""); + } + if (cnt >= 3) { + tuya_p2p_rtc_sdp_add_media(sdp, m3, ""); + } + + continue; + } + if (strncmp(p, "m=audio", strlen("m=audio")) == 0) { + m = 'a'; + char tmp[128] = {0}; + char *ptmp = strstr(p, "SAVPF"); + if (ptmp != NULL) { + snprintf(tmp, sizeof(tmp), "%s", ptmp + strlen("SAVPF") + 1); + } + char *tmplasts = NULL; + char *pt = strtok_r(tmp, " ", &tmplasts); + while (pt != NULL) { + tuya_p2p_rtc_sdp_add_audio_codec(sdp, NULL, atoi(pt), 0, 0, 0); + pt = strtok_r(NULL, " ", &tmplasts); + } + continue; + } + if (strncmp(p, "m=video", strlen("m=video")) == 0) { + m = 'v'; + char tmp[128] = {0}; + char *ptmp = strstr(p, "SAVPF"); + if (ptmp != NULL) { + snprintf(tmp, sizeof(tmp), "%s", ptmp + strlen("SAVPF") + 1); + } + char *tmplasts = NULL; + char *pt = strtok_r(tmp, " ", &tmplasts); + while (pt != NULL) { + tuya_p2p_rtc_sdp_add_video_codec(sdp, "", atoi(pt), 0, 0, ""); + pt = strtok_r(NULL, " ", &tmplasts); + } + continue; + } + if (strncmp(p, "m=application", strlen("m=application")) == 0) { + m = 't'; + char tmp[128] = {0}; + char *ptmp = strstr(p, "tuya"); + if (ptmp != NULL) { + snprintf(tmp, sizeof(tmp), "%s", ptmp + strlen("tuya") + 1); + } + char *tmplasts = NULL; + char *pt = strtok_r(tmp, " ", &tmplasts); + while (pt != NULL) { + tuya_p2p_rtc_sdp_add_tuya_codec(sdp, "", atoi(pt), 0); + pt = strtok_r(NULL, " ", &tmplasts); + } + continue; + } + if (strncmp(p, "a=rtpmap:", strlen("a=rtpmap:")) == 0) { + p += strlen("a=rtpmap:"); + int pt = atoi(p); + + char *p1 = strstr(p, " "); + char *p2 = NULL; + char *p3 = NULL; + if (p1 != NULL) { + p1 += 1; + + p2 = strstr(p1, "/"); + if (p2 != NULL) { + *p2 = '\0'; + p2 += 1; + + p3 = strstr(p2, "/"); + if (p3 != NULL) { + *p3 = '\0'; + p3 += 1; + } + } + } + + if (m == 'a') { + tuya_p2p_rtc_sdp_update_audio_codec(sdp, pt, p1, p2, p3); + } else if (m == 'v') { + tuya_p2p_rtc_sdp_update_video_codec(sdp, pt, p1, p2); + } else if (m == 't') { + } else { + tuya_p2p_log_warn("got invalid rtpmap, m = %c\n", m); + } + continue; + } + if (strncmp(p, "a=fmtp:", strlen("a=fmtp:")) == 0) { + p += strlen("a=fmtp:"); + char str_pt[32] = {0}; + char str_attr[256] = {0}; + int cnt = sscanf(p, "%s %s", str_pt, str_attr); + if (cnt == 2) { + if (strncmp(str_attr, "apt=", strlen("apt=")) == 0) { + int pt = atoi(str_pt); + int original_pt = atoi(str_attr + strlen("apt=")); + tuya_p2p_rtc_sdp_set_original_pt(sdp, pt, original_pt); + } + } + } + + if (strncmp(p, "a=ssrc-group:FID", strlen("a=ssrc-group:FID")) == 0) { + char *p1 = p + strlen("a=ssrc-group:FID"); + char ssrc[65] = {0}; + char ssrc_rtx[65] = {0}; + int cnt = sscanf(p1, "%s %s", ssrc, ssrc_rtx); + if (cnt == 2) { + if (m == 'a') { + sdp->audio.negotiated_codec.ssrc = strtoul(ssrc, NULL, 10); + sdp->audio.rtx_codec.ssrc = strtoul(ssrc_rtx, NULL, 10); + } else if (m == 'v') { + sdp->video.negotiated_codec.ssrc = strtoul(ssrc, NULL, 10); + sdp->video.rtx_codec.ssrc = strtoul(ssrc_rtx, NULL, 10); + } else { + // do nothing + } + } + continue; + } + if (strncmp(p, "a=ssrc:", strlen("a=ssrc:")) == 0) { + char *p1 = p + strlen("a=ssrc:"); + char *p2 = strstr(p1, " "); + if (p2 != NULL) { + *p2 = '\0'; + p2 += 1; + + if (strncmp(p2, "cname:", strlen("cname:")) == 0) { + p2 = p2 + strlen("cname:"); + } else { + p2 = NULL; + } + } + + if (p1 != NULL) { + if (m == 'a') { + if (sdp->audio.negotiated_codec.ssrc == 0) { + sdp->audio.negotiated_codec.ssrc = strtoul(p1, NULL, 10); + } else if (sdp->audio.rtx_codec.ssrc == 0) { + sdp->audio.rtx_codec.ssrc = strtoul(p1, NULL, 10); + } + } else if (m == 'v') { + if (sdp->video.negotiated_codec.ssrc == 0) { + sdp->video.negotiated_codec.ssrc = strtoul(p1, NULL, 10); + } else if (sdp->video.rtx_codec.ssrc == 0) { + sdp->video.rtx_codec.ssrc = strtoul(p1, NULL, 10); + } + } else { + // do nothing + } + } + if (p2 != NULL) { + snprintf(sdp->cname, sizeof(sdp->cname), "%s", p2); + } + continue; + } + + if (strncmp(p, "a=mid:", strlen("a=mid:")) == 0) { + char *p1 = p + strlen("a=mid:"); + if (p1 != NULL) { + if (m == 'a') { + tuya_p2p_rtc_sdp_set_media_type(sdp, p1, "audio"); + } else if (m == 'v') { + tuya_p2p_rtc_sdp_set_media_type(sdp, p1, "video"); + } else { + tuya_p2p_rtc_sdp_set_media_type(sdp, p1, "tuya"); + } + } + continue; + } + + if (strncmp(p, "a=ice-ufrag:", strlen("a=ice-ufrag:")) == 0) { + snprintf(sdp->ufrag, sizeof(sdp->ufrag), "%s", p + strlen("a=ice-ufrag:")); + continue; + } + if (strncmp(p, "a=ice-pwd:", strlen("a=ice-pwd:")) == 0) { + snprintf(sdp->password, sizeof(sdp->password), "%s", p + strlen("a=ice-pwd:")); + continue; + } + if (strncmp(p, "a=fingerprint:", strlen("a=fingerprint:")) == 0) { + snprintf(sdp->fingerprint, sizeof(sdp->fingerprint), "%s", p + strlen("a=fingerprint:")); + continue; + } + if (strncmp(p, "a=aes-key:", strlen("a=aes-key:")) == 0) { + snprintf((char *)sdp->aes_key, sizeof(sdp->aes_key), "%s", p + strlen("a=aes-key:")); + continue; + } + if (strncmp(p, "a=candidate:", strlen("a=candidate:")) == 0) { + tuya_p2p_rtc_sdp_add_candidate(sdp, p); + continue; + } + } + + if (!QUEUE_EMPTY(&sdp->candidates.queue)) { + tuya_p2p_rtc_sdp_add_candidate(sdp, ""); + } + + return 0; +} + +int tuya_p2p_rtc_sdp_negotiate(rtc_sdp_t *local_sdp, rtc_sdp_t *remote_sdp, char *type) +{ + QUEUE *q; + if (strcmp(type, "offer") == 0) { + memcpy(local_sdp->aes_key, remote_sdp->aes_key, sizeof(local_sdp->aes_key)); + + QUEUE_FOREACH(q, &remote_sdp->media_info_list.queue) + { + media_info_t *m = QUEUE_DATA(q, media_info_t, queue); + tuya_p2p_rtc_sdp_add_media(local_sdp, m->mid, m->type); + } + } + + // audio + // printf("negotiate codec\n"); + QUEUE_FOREACH(q, &local_sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + // printf("local audio pt: %d\n", codec->pt); + QUEUE *tmp_q; + QUEUE_FOREACH(tmp_q, &remote_sdp->audio.codec_list) + { + rtc_audio_codec_t *tmp_codec = QUEUE_DATA(tmp_q, rtc_audio_codec_t, queue); + // printf("nego audio: %d(%s:%d:%d:%u) - %d(%s:%d:%d:%u)\n", + // codec->pt, codec->name, codec->sample_rate, codec->channel_number, codec->ssrc, + // tmp_codec->pt, tmp_codec->name, tmp_codec->sample_rate, tmp_codec->channel_number, codec->ssrc); + if ((strncmp(codec->name, tmp_codec->name, sizeof(codec->name)) == 0) && + (codec->sample_rate == tmp_codec->sample_rate) && + (codec->channel_number == tmp_codec->channel_number)) { + local_sdp->audio.negotiated_codec.channel_number = codec->channel_number; + local_sdp->audio.negotiated_codec.sample_rate = codec->sample_rate; + local_sdp->audio.negotiated_codec.pt = tmp_codec->pt; + local_sdp->audio.negotiated_codec.ssrc = codec->ssrc; + snprintf(local_sdp->audio.negotiated_codec.name, sizeof(local_sdp->audio.negotiated_codec.name), "%s", + codec->name); + remote_sdp->audio.negotiated_codec.channel_number = tmp_codec->channel_number; + remote_sdp->audio.negotiated_codec.sample_rate = tmp_codec->sample_rate; + remote_sdp->audio.negotiated_codec.pt = tmp_codec->pt; + // remote_sdp->audio.negotiated_codec.ssrc = tmp_codec->ssrc; + snprintf(remote_sdp->audio.negotiated_codec.name, sizeof(remote_sdp->audio.negotiated_codec.name), "%s", + tmp_codec->name); + goto finish_audio; + } + } + } + +finish_audio: + + // video + QUEUE_FOREACH(q, &local_sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + // printf("local video pt: %d\n", codec->pt); + QUEUE *tmp_q; + QUEUE_FOREACH(tmp_q, &remote_sdp->video.codec_list) + { + rtc_video_codec_t *tmp_codec = QUEUE_DATA(tmp_q, rtc_video_codec_t, queue); + if ((strncmp(codec->name, tmp_codec->name, sizeof(codec->name)) == 0) && + (codec->clock_rate == tmp_codec->clock_rate)) { + local_sdp->video.negotiated_codec.clock_rate = codec->clock_rate; + local_sdp->video.negotiated_codec.pt = tmp_codec->pt; + local_sdp->video.negotiated_codec.ssrc = codec->ssrc; + local_sdp->video.negotiated_codec.original_pt = -1; + snprintf(local_sdp->video.negotiated_codec.profile_level_id, + sizeof(local_sdp->video.negotiated_codec.profile_level_id), "%s", codec->profile_level_id); + snprintf(local_sdp->video.negotiated_codec.name, sizeof(local_sdp->video.negotiated_codec.name), "%s", + codec->name); + remote_sdp->video.negotiated_codec.clock_rate = tmp_codec->clock_rate; + remote_sdp->video.negotiated_codec.pt = tmp_codec->pt; + // remote_sdp->video.negotiated_codec.ssrc = tmp_codec->ssrc; + remote_sdp->video.negotiated_codec.original_pt = -1; + snprintf(remote_sdp->video.negotiated_codec.profile_level_id, + sizeof(remote_sdp->video.negotiated_codec.profile_level_id), "%s", + tmp_codec->profile_level_id); + snprintf(remote_sdp->video.negotiated_codec.name, sizeof(remote_sdp->video.negotiated_codec.name), "%s", + tmp_codec->name); + goto finish_video; + } + } + } + +finish_video: + + // video rtx codec + QUEUE_FOREACH(q, &remote_sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + // printf("remote video codec: %s %d %d (negotiated_codec pt = %d)\n", + // codec->name, codec->pt, codec->original_pt, local_sdp->video.negotiated_codec.pt); + if (strcmp(codec->name, "rtx") == 0 && codec->original_pt == remote_sdp->video.negotiated_codec.pt) { + remote_sdp->video.rtx_codec.pt = codec->pt; + remote_sdp->video.rtx_codec.ssrc = codec->ssrc; + remote_sdp->video.rtx_mode = RTX_MODE_SSRC_MULTIPLEX; + } + } + QUEUE_FOREACH(q, &local_sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + // printf("local video codec: %s %d %d (negotiated_codec pt = %d)\n", + // codec->name, codec->pt, codec->original_pt, local_sdp->video.negotiated_codec.pt); + if (strcmp(codec->name, "rtx") == 0) { + local_sdp->video.rtx_codec.ssrc = codec->ssrc; + local_sdp->video.rtx_codec.clock_rate = codec->clock_rate; + } + } + local_sdp->video.rtx_codec.pt = remote_sdp->video.rtx_codec.pt; + local_sdp->video.rtx_mode = remote_sdp->video.rtx_mode; + + // tuya media (hardcode) + local_sdp->tuya.negotiated_codec.channel_number = 3; + snprintf(local_sdp->tuya.negotiated_codec.name, sizeof(local_sdp->tuya.negotiated_codec.name), "AES/KCP"); + local_sdp->tuya.negotiated_codec.pt = 6001; + remote_sdp->tuya.negotiated_codec.channel_number = 3; + snprintf(remote_sdp->tuya.negotiated_codec.name, sizeof(remote_sdp->tuya.negotiated_codec.name), "AES/KCP"); + remote_sdp->tuya.negotiated_codec.pt = 6001; + + return 0; +} diff --git a/src/tuya_p2p/base_ice/src/tuya_sdp.h b/src/tuya_p2p/base_ice/src/tuya_sdp.h new file mode 100755 index 000000000..73d937d41 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_sdp.h @@ -0,0 +1,105 @@ +#include "queue.h" +#include "tuya_rtp.h" +#include + +typedef enum tuya_p2p_rtc_media_direction { + MEDIA_DIRECTION_NONE = 0, + MEDIA_DIRECTION_SENDONLY = 1, + MEDIA_DIRECTION_RECVONLY = 2, + MEDIA_DIRECTION_SENDRECV = 3 +} tuya_p2p_rtc_media_direction_e; + +typedef enum tuya_p2p_rtc_dtls_role { + DTLS_ROLE_BOTH = 0, + DTLS_ROLE_CLIENT = 1, + DTLS_ROLE_SERVER = 2, +} tuya_p2p_rtc_dtls_role_e; + +typedef struct rtc_audio_codec { + QUEUE queue; + char name[32]; + int pt; + uint32_t ssrc; + int sample_rate; + int channel_number; +} rtc_audio_codec_t; + +typedef struct rtc_video_codec { + QUEUE queue; + char name[32]; + int pt; + int original_pt; + uint32_t ssrc; + int clock_rate; + char profile_level_id[65]; +} rtc_video_codec_t; + +typedef struct rtc_tuya_codec { + QUEUE queue; + char name[32]; + int pt; + int channel_number; +} rtc_tuya_codec_t; + +typedef struct { + QUEUE queue; + char str[256]; + uint64_t time_ms; +} rtc_cand_t; + +typedef struct media_info { + QUEUE queue; + char type[8]; + char mid[65]; +} media_info_t; + +typedef struct rtc_sdp { + int inited; + char wms_id[65]; + char cname[65]; + unsigned char aes_key[48]; + char fingerprint[256]; + char ufrag[128]; + char password[128]; + rtc_cand_t candidates; + media_info_t media_info_list; + tuya_p2p_rtc_dtls_role_e dtls_role; + struct { + char track_id[65]; + tuya_p2p_rtp_rtx_mode_e rtx_mode; + tuya_p2p_rtc_media_direction_e direction; + QUEUE codec_list; + rtc_audio_codec_t negotiated_codec; + rtc_audio_codec_t rtx_codec; + } audio; + struct { + char track_id[65]; + tuya_p2p_rtp_rtx_mode_e rtx_mode; + tuya_p2p_rtc_media_direction_e direction; + QUEUE codec_list; + rtc_video_codec_t negotiated_codec; + rtc_video_codec_t rtx_codec; + } video; + struct { + QUEUE codec_list; + rtc_tuya_codec_t negotiated_codec; + } tuya; +} rtc_sdp_t; + +int tuya_p2p_rtc_sdp_init(rtc_sdp_t *sdp, char *session_id, char *local_id, char *fingerprint, char *ufrag, + char *password, tuya_p2p_rtc_dtls_role_e dtls_role); +int tuya_p2p_rtc_sdp_deinit(rtc_sdp_t *sdp); +int tuya_p2p_rtc_sdp_add_media(rtc_sdp_t *sdp, char *mid, char *type); +int tuya_p2p_rtc_sdp_add_audio_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int sample_rate, + int channel_number); +int tuya_p2p_rtc_sdp_add_video_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int clock_rate, + char *profile_level_id); +int tuya_p2p_rtc_sdp_add_video_rtx_codec(rtc_sdp_t *sdp, int origin_pt, int pt, uint32_t ssrc, int clock_rate); +int tuya_p2p_rtc_sdp_add_tuya_codec(rtc_sdp_t *sdp, char *name, int pt, int channel_number); +int tuya_p2p_rtc_sdp_encode(rtc_sdp_t *sdp, char *type, char *buf, int size); +int tuya_p2p_rtc_sdp_decode(rtc_sdp_t *sdp, char *buf); +int tuya_p2p_rtc_sdp_negotiate(rtc_sdp_t *local_sdp, rtc_sdp_t *remote_sdp, char *type); +int tuya_p2p_rtc_sdp_add_candidate(rtc_sdp_t *sdp, char *candidate); +int tuya_p2p_rtc_sdp_set_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len); +int tuya_p2p_rtc_sdp_get_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len); +int tuya_p2p_rtc_sdp_set_dtls_cert_fingerprint(rtc_sdp_t *sdp, char *fingerprint); diff --git a/src/tuya_p2p/lib_rtp/CMakeLists.txt b/src/tuya_p2p/lib_rtp/CMakeLists.txt new file mode 100755 index 000000000..b2be2df89 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/CMakeLists.txt @@ -0,0 +1,56 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS + "${MODULE_PATH}/payload/*.c" + "${MODULE_PATH}/rtpext/*.c" + "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/lib_rtp/include/rtcp-header.h b/src/tuya_p2p/lib_rtp/include/rtcp-header.h new file mode 100755 index 000000000..dac9e7583 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtcp-header.h @@ -0,0 +1,398 @@ +#ifndef _rtcp_header_h_ +#define _rtcp_header_h_ + +#include + +#define RTCP_V(v) ((v >> 30) & 0x03) // rtcp version +#define RTCP_P(v) ((v >> 29) & 0x01) // rtcp padding +#define RTCP_RC(v) ((v >> 24) & 0x1F) // rtcp reception report count +#define RTCP_PT(v) ((v >> 16) & 0xFF) // rtcp packet type +#define RTCP_LEN(v) (v & 0xFFFF) // rtcp packet length + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +enum rtcp_type_t { + RTCP_FIR = 192, // RFC2032, Reserved (Historic-FIR) + RTCP_NACK = 193, // RFC2032, Reserved (Historic-NACK) + RTCP_SMPTETC = 194, // RFC5484 + RTCP_IJ = 195, // RFC5450 + + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204, + + RTCP_RTPFB = 205, // RFC4585 + RTCP_PSFB = 206, // RFC4585 + RTCP_XR = 207, // RFC3611 + RTCP_AVB = 208, + RTCP_RSI = 209, // RFC5760 + RTCP_TOKEN = 210, // RFC6284 + RTCP_IDMS = 211, // RFC7272 + RTCP_RGRS = 212, // RFC8861 + + RTCP_LIMIT = 223, // RFC5761 RTCP packet types in the ranges 1-191 and 224-254 SHOULD only be used when other values + // have been exhausted. +}; + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5 +enum rtcp_sdes_type_t { + RTCP_SDES_END = 0, // RFC3550 + RTCP_SDES_CNAME = 1, // RFC3550 + RTCP_SDES_NAME = 2, // RFC3550 + RTCP_SDES_EMAIL = 3, // RFC3550 + RTCP_SDES_PHONE = 4, // RFC3550 + RTCP_SDES_LOC = 5, // RFC3550 + RTCP_SDES_TOOL = 6, // RFC3550 + RTCP_SDES_NOTE = 7, // RFC3550 + RTCP_SDES_PRIVATE = 8, // RFC3550 + RTCP_SDES_CADDR = 9, // H.323 callable address + RTCP_SDES_APSI = 10, // RFC6776 + RTCP_SDES_RGRP = 11, // RFC8861 + RTCP_SDES_RID = 12, // RFC8852 + RTCP_SDES_RRID = 13, // RFC8852 + RTCP_SDES_CCID = 14, // RFC8849 + RTCP_SDES_MID = 15, // RFC8843 +}; + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-8 +enum rtcp_rtpfb_type_t { + RTCP_RTPFB_NACK = 1, // RFC4585, Generic NACK + RTCP_RTPFB_TMMBR = 3, // RFC5104, Temporary Maximum Media Stream Bit Rate Request (TMMBR) + RTCP_RTPFB_TMMBN = 4, // RFC5104, Temporary Maximum Media Stream Bit Rate Notification (TMMBN) + RTCP_RTPFB_SRREQ = 5, // RFC6051, RTCP Rapid Resynchronisation Request(RTCP-SR-REQ) + RTCP_RTPFB_RAMS = 6, // RFC6285, Rapid Acquisition of Multicast Sessions + RTCP_RTPFB_TLLEI = 7, // RFC6642, Transport-Layer Third-Party Loss Early Indication + RTCP_RTPFB_ECN = 8, // RFC6679, RTCP ECN Feedback + RTCP_RTPFB_PS = 9, // RFC7728, Media Pause/Resume + RTCP_RTPFB_DBI = 10, // 3GPP TS 26.114 v16.3.0, Delay Budget Information (DBI) + RTCP_RTPFB_CCFB = 11, // RFC8888, RTP Congestion Control Feedback + RTCP_RTPFB_TCC01 = 15, // draft-holmer-rmcat-transport-wide-cc-extensions-01 + + RTCP_RTPFB_EXT = 31, +}; + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-9 +enum rtcp_psfb_type_t { + RTCP_PSFB_PLI = 1, // RFC4585, Picture Loss Indication (PLI) + RTCP_PSFB_SLI = 2, // RFC4585, Slice Loss Indication (SLI) + RTCP_PSFB_RPSI = 3, // RFC4585, Reference Picture Selection Indication (RPSI) + RTCP_PSFB_FIR = 4, // RFC5104, Full Intra Request (FIR) + RTCP_PSFB_TSTR = 5, // RFC5104, Temporal-Spatial Trade-off Request + RTCP_PSFB_TSTN = 6, // RFC5104, Temporal-Spatial Trade-off Notification + RTCP_PSFB_VBCM = 7, // RFC5104, Video Back Channel Message + RTCP_PSFB_PSLEI = 8, // RFC6642, Payload-Specific Third-Party Loss Early Indication + RTCP_PSFB_ROI = 9, // 3GPP TS 26.114 v16.3.0, Video region-of-interest (ROI) + RTCP_PSFB_LRR = 10, // RFC-ietf-avtext-lrr-07, Layer Refresh Request Command + RTCP_PSFB_AFB = 15, // RFC4585, Application layer FB (AFB) message + RTCP_PSFB_REMB = 15, // https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03#section-2.2 + + RTCP_PSFB_EXT = 31, +}; + +// https://www.iana.org/assignments/rtcp-xr-block-types/rtcp-xr-block-types.xhtml +enum rtcp_xr_type_t { + RTCP_XR_LRLE = 1, // RFC3611, Loss RLE Report Block + RTCP_XR_DRLE = 2, // RFC3611, Duplicate RLE Report Block + RTCP_XR_PRT = 3, // RFC3611, Packet Receipt Times Report Block + RTCP_XR_RRT = 4, // RFC3611, Receiver Reference Time Report Block + RTCP_XR_DLRR = 5, // RFC3611, DLRR Report Block + RTCP_XR_SS = 6, // RFC3611, Statistics Summary Report Block + RTCP_XR_VM = 7, // RFC3611, VoIP Metrics Report Block + RTCP_XR_RTCP = 8, // RFC5093, RTCP XR + + RTCP_XR_IDMS = 12, // RFC7272, IDMS Report Block + RTCP_XR_ECN = 13, // RFC6679, ECN Summary Report + + RTCP_XR_DISRLE = 25, // RFC7097, DRLE (Discard RLE Report) + RTCP_XR_BDR = 26, // RFC7243, BDR (Bytes Discarded Report) + RTCP_XR_RFISD = 27, // RFC7244, RFISD (RTP Flows Initial Synchronization Delay) + RTCP_XR_RFSO = 28, // RFC7244, RFSO (RTP Flows Synchronization Offset Metrics Block) +}; + +typedef struct _rtcp_header_t { + uint32_t v : 2; // version + uint32_t p : 1; // padding + uint32_t rc : 5; // reception report count + uint32_t pt : 8; // packet type + uint32_t length : 16; /* pkt len in words, w/o this word */ +} rtcp_header_t; + +typedef struct _rtcp_sr_t // sender report +{ + uint32_t ssrc; + uint32_t ntpmsw; // ntp timestamp MSW(in second) + uint32_t ntplsw; // ntp timestamp LSW(in picosecond) + uint32_t rtpts; // rtp timestamp + uint32_t spc; // sender packet count + uint32_t soc; // sender octet count +} rtcp_sr_t; + +typedef struct _rtcp_rr_t // receiver report +{ + uint32_t ssrc; +} rtcp_rr_t; + +typedef struct _rtcp_rb_t // report block +{ + uint32_t ssrc; + uint32_t fraction : 8; // fraction lost + uint32_t cumulative : 24; // cumulative number of packets lost + uint32_t exthsn; // extended highest sequence number received + uint32_t jitter; // interarrival jitter + uint32_t lsr; // last SR + uint32_t dlsr; // delay since last SR +} rtcp_rb_t; + +// source description RTCP packet +typedef struct _rtcp_sdes_item_t { + uint8_t pt; // chunk type + uint8_t len; + uint8_t *data; +} rtcp_sdes_item_t; + +typedef struct _rtcp_bye_t { + const void *reason; + int bytes; // reason length +} rtcp_bye_t; + +typedef struct _rtcp_app_t { + uint8_t subtype; + char name[4]; + void *data; + int bytes; // data length +} rtcp_app_t; + +// Slice Loss Indication (SLI) +typedef struct _rtcp_sli_t { + uint32_t first : 13; // The macroblock (MB) address of the first lost macroblock. + uint32_t number : 13; // The number of lost macroblocks, in scan order + uint32_t picture_id : 6; +} rtcp_sli_t; + +// Full Intra Request (FIR) +typedef struct _rtcp_fir_t { + uint32_t ssrc; + uint32_t sn : 8; // Command sequence number + uint32_t reserved : 19; + uint32_t index : 5; // for TSTR +} rtcp_fir_t; + +// Video Back Channel Message (VBCM) +typedef struct _rtcp_vbcm_t { + uint32_t ssrc; + uint32_t sn : 8; // Command sequence number + uint32_t reserved : 1; + uint32_t pt : 7; + uint32_t len : 16; + void *payload; +} rtcp_vbcm_t; + +// Layer Refresh Request +typedef struct _rtcp_lrr_t { + uint32_t ssrc; + + uint32_t sn : 8; // Command sequence number + uint32_t c : 1; + uint32_t payload : 7; + uint32_t reserved : 16; // Reserved + + uint32_t res1 : 5; // reserved + uint32_t ttid : 3; // Target Temporal Layer ID (TTID) (3 bits) + uint32_t tlid : 8; // Target Layer ID (TLID) (8 bits) + uint32_t res2 : 5; // reserved + uint32_t ctid : 3; // Current Temporal Layer ID (CTID) (3 bits) + uint32_t clid : 8; // Current Layer ID (CLID) (8 bits) +} rtcp_lrr_t; + +// Generic NACK +typedef struct _rtcp_nack_t { + uint16_t pid; // Packet ID (PID) + uint16_t blp; // bitmask of following lost packets (BLP) +} rtcp_nack_t; + +// Temporary Maximum Media Stream Bit Rate Request (TMMBR) +typedef struct _rtcp_tmmbr_t { + uint32_t ssrc; + uint32_t exp : 6; // MxTBR Exp (6 bits) + uint32_t mantissa : 17; // MxTBR Mantissa (17 bits) + uint32_t overhead : 9; // Measured Overhead (9 bits) + + // maximum total media bit rate(MxTBR) + // MxTBR = mantissa * 2^exp +} rtcp_tmmbr_t; + +// RTP/AVPF Transport-Layer ECN Feedback Packet(ECN) +typedef struct _rtcp_ecn_t { + uint32_t ext_highest_seq; // 32-bit extended highest sequence number received + uint32_t ect[2]; // The 32-bit cumulative number of RTP packets received from this SSRC. + uint16_t ect_ce_counter; + uint16_t not_ect_counter; + uint16_t lost_packets_counter; + uint16_t duplication_counter; +} rtcp_ecn_t; + +typedef struct _rtcp_remb_t { + uint32_t ssrc; + uint32_t exp : 6; // BR Exp (6 bits) + uint32_t mantissa : 18; // BR Mantissa (18 bits) in bps + + // maximum total media bit rate(MxTBR) + // MxTBR = mantissa * 2^exp +} rtcp_remb_t; + +// RTP Congestion Control Feedback +typedef struct _rtcp_ccfb_t { + uint32_t seq : 16; + uint32_t received : 1; + uint32_t ecn : 2; // 00 if not received or if ECN is not used + // uint32_t ato : 13; + + int16_t ato; // ms * 1024, Arrival time offset (ATO, 13 bits) & (TCC-01 16 bits) +} rtcp_ccfb_t; + +// DLRR Report Block +typedef struct _rtcp_dlrr_t { + uint32_t ssrc; + uint32_t lrr; + uint32_t dlrr; +} rtcp_dlrr_t; + +typedef struct _rtcp_rtpfb_t { + uint32_t media; // media ssrc + + union { + // RTCP_RTPFB | (RTCP_RTPFB_NACK << 8) + // RTCP_RTPFB | (RTCP_RTPFB_TLLEI << 8) + struct { + rtcp_nack_t *nack; + int count; + } nack; + + // RTCP_RTPFB | (RTCP_RTPFB_TMMBR << 8) + // RTCP_RTPFB | (RTCP_RTPFB_TMMBN << 8) + struct { + rtcp_tmmbr_t *tmmbr; + int count; + } tmmbr; + + // RTCP_RTPFB | (RTCP_RTPFB_CCFB << 8) + // RTCP_RTPFB | (RTCP_RTPFB_TCC01 << 8) + struct { + rtcp_ccfb_t *ccfb; + int count; + + int32_t timestamp; + uint32_t begin : 16; + uint32_t cc : 8; // TCC01 only + + uint32_t ssrc; // ccfb only + } tcc01; + + // RTCP_RTPFB | (RTCP_RTPFB_PS << 8) + struct { + uint32_t target; + uint32_t cmd : 8; + uint32_t len : 8; + uint32_t id : 16; + void *payload; + } ps; + + // RTCP_RTPFB | (RTCP_RTPFB_ECN << 8) + rtcp_ecn_t ecn; + + // RTCP_RTPFB | (RTCP_RTPFB_DBI << 8) + struct { + uint32_t delay : 16; + uint32_t s : 1; + uint32_t q : 1; + } dbi; + } u; +} rtcp_rtpfb_t; + +typedef struct _rtcp_psfb_t { + uint32_t media; // media ssrc + + union { + // RTCP_PSFB | (RTCP_PSFB_PLI << 8) + + // RTCP_PSFB | (RTCP_PSFB_SLI << 8) + struct { + rtcp_sli_t *sli; + int count; + } sli; + + // RTCP_PSFB | (RTCP_PSFB_FIR << 8) + // RTCP_PSFB | (RTCP_PSFB_TSTR << 8) + // RTCP_PSFB | (RTCP_PSFB_TSTN << 8) + struct { + rtcp_fir_t *fir; + int count; + } fir; + + // RTCP_PSFB | (RTCP_PSFB_VBCM << 8) + struct { + uint8_t pt; + uint32_t len; // length in bit + void *payload; + } rpsi; + + // RTCP_PSFB | (RTCP_PSFB_VBCM << 8) + rtcp_vbcm_t vbcm; + + // RTCP_PSFB | (RTCP_PSFB_PSLEI << 8) + struct { + uint32_t *ssrc; + int count; + } pslei; + + // RTCP_PSFB | (RTCP_PSFB_LRR << 8) + struct { + rtcp_lrr_t *lrr; + int count; + } lrr; + + // RTCP_PSFB | (RTCP_PSFB_AFB << 8) + struct { + rtcp_remb_t *remb; + int count; + } afb; + } u; +} rtcp_psfb_t; + +typedef struct _rtcp_xr_t { + union { + // RTCP_XR | (RTCP_XR_LRLE << 8) + // RTCP_XR | (RTCP_XR_DRLE << 8) + struct { + uint32_t source; // SSRC of source + uint32_t begin : 16; // begin_seq + uint32_t end : 16; // end_seq + uint8_t *chunk; + int count; + } rle; // lrle/drle + + // RTCP_XR | (RTCP_XR_PRT << 8) + struct { + uint32_t source; // SSRC of source + uint32_t begin : 16; // begin_seq + uint32_t end : 16; // end_seq + uint32_t *timestamp; + int count; + } prt; + + // RTCP_XR | (RTCP_XR_RRT << 8) + uint64_t rrt; + + // RTCP_XR | (RTCP_XR_DLRR << 8) + struct { + rtcp_dlrr_t *dlrr; + int count; + } dlrr; + + // RTCP_XR | (RTCP_XR_ECN << 8) + rtcp_ecn_t ecn; + } u; +} rtcp_xr_t; + +#endif /* !_rtcp_header_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-demuxer.h b/src/tuya_p2p/lib_rtp/include/rtp-demuxer.h new file mode 100755 index 000000000..8dce85462 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-demuxer.h @@ -0,0 +1,44 @@ +#ifndef _rtp_demuxer_h_ +#define _rtp_demuxer_h_ + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct rtp_demuxer_t; + +/// @param[in] param rtp_demuxer_create input param +/// @param[in] packet rtp payload data +/// @param[in] bytes rtp payload length in byte +/// @param[in] timestamp rtp timestamp(relation at sample rate) +/// @param[in] flags rtp packet flags, RTP_PAYLOAD_FLAG_PACKET_xxx, see more @rtp-payload.h +/// @return 0-ok, other-error +typedef int (*rtp_demuxer_onpacket)(void *param, const void *packet, int bytes, uint32_t timestamp, int flags); + +/// @param[in] jitter rtp reorder jitter(ms), e.g. 200(ms) +/// @param[in] frequency audio/video sample rate, e.g. video 90000, audio 48000 +/// @param[in] payload rtp payload id, see more @rtp-profile.h +/// @param[in] encoding rtp payload encoding, see more @rtp-profile.h +struct rtp_demuxer_t *rtp_demuxer_create(int jitter, int frequency, int payload, const char *encoding, + rtp_demuxer_onpacket onpkt, void *param); +int rtp_demuxer_destroy(struct rtp_demuxer_t **rtp); + +/// @param[in] data a rtp/rtcp packet +/// @return >0-rtcp message, 0-ok, <0-error +int rtp_demuxer_input(struct rtp_demuxer_t *rtp, const void *data, int bytes); + +/// @return >0-rtcp report length, 0-don't need send rtcp +int rtp_demuxer_rtcp(struct rtp_demuxer_t *rtp, void *buf, int len); + +/// @param[out] lost read lost packets by jitter +/// @param[out] late received after read +/// @param[out] misorder reorder packets +/// @param[out] duplicate exist in unread queue +void rtp_demuxer_stats(struct rtp_demuxer_t *rtp, int *lost, int *late, int *misorder, int *duplicate); + +#if defined(__cplusplus) +} +#endif +#endif /* _rtp_demuxer_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-ext.h b/src/tuya_p2p/lib_rtp/include/rtp-ext.h new file mode 100755 index 000000000..c2d442fc2 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-ext.h @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021 ireader . All rights reserved. + */ + +#ifndef _rtp_ext_h_ +#define _rtp_ext_h_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +// https://www.iana.org/assignments/rtcp-xr-block-types/rtcp-xr-block-types.xhtml + +/* +RTCP Control Packet Types (PT) + Value Abbrev. Name Reference + 0 Reserved + 1-191 Unassigned + 192 Reserved (Historic-FIR) [RFC2032] + 193 Reserved (Historic-NACK) [RFC2032] + 194 SMPTETC SMPTE time-code mapping [RFC5484] + 195 IJ Extended inter-arrival jitter report [RFC5450] + 196-199 Unassigned + 200 SR sender report [RFC3550] + 201 RR receiver report [RFC3550] + 202 SDES source description [RFC3550] + 203 BYE goodbye [RFC3550] + 204 APP application-defined [RFC3550] + 205 RTPFB Generic RTP Feedback [RFC4585] + 206 PSFB Payload-specific [RFC4585] + 207 XR extended report [RFC3611] + 208 AVB AVB RTCP packet ["Standard for Layer 3 Transport Protocol for Time Sensitive +Applications in Local Area Networks." Work in progress.] 209 RSI Receiver Summary Information [RFC5760] + 210 TOKEN Port Mapping [RFC6284] + 211 IDMS IDMS Settings [RFC7272] + 212 RGRS Reporting Group Reporting Sources [RFC8861] + 213 SNM Splicing Notification Message [RFC8286] + 214-254 Unassigned + 255 Reserved + + +RTP SDES Item Types + Value Abbrev. Name Reference + 0 END end of SDES list [RFC3550] + 1 CNAME canonical name [RFC3550] + 2 NAME user name [RFC3550] + 3 EMAIL user's electronic mail address [RFC3550] + 4 PHONE user's phone number [RFC3550] + 5 LOC geographic user location [RFC3550] + 6 TOOL name of application or tool [RFC3550] + 7 NOTE notice about the source [RFC3550] + 8 PRIV private extensions [RFC3550] + 9 H323-CADDR H.323 callable address [Vineet_Kumar] + 10 APSI Application Specific Identifier [RFC6776] + 11 RGRP Reporting Group Identifier [RFC8861] + 12 RtpStreamId RTP Stream Identifier [RFC8852] + 13 RepairedRtpStreamId Repaired RTP Stream Identifier [RFC8852] + 14 CCID CLUE CaptId [RFC8849] + 15 MID Media Identification [RFC-ietf-mmusic-rfc8843bis-05] + 16-255 Unassigned + + +FMT Values for RTPFB Payload Types + Value Name Long Name Reference + 1 Generic NACK Generic negative acknowledgement [RFC4585] + 2 Reserved [RFC5104] + 3 TMMBR Temporary Maximum Media Stream Bit Rate Request [RFC5104] + 4 TMMBN Temporary Maximum Media Stream Bit Rate Notification [RFC5104] + 5 RTCP-SR-REQ RTCP Rapid Resynchronisation Request [RFC6051] + 6 RAMS Rapid Acquisition of Multicast Sessions [RFC6285] + 7 TLLEI Transport-Layer Third-Party Loss Early Indication [RFC6642] + 8 RTCP-ECN-FB RTCP ECN Feedback [RFC6679] + 9 PAUSE-RESUME Media Pause/Resume [RFC7728] + 10 DBI Delay Budget Information (DBI) [3GPP TS 26.114 v16.3.0][Ozgur_Oyman] + 11 CCFB RTP Congestion Control Feedback [RFC8888] + 12-30 Unassigned + 31 Extension Reserved for future extensions [RFC4585] + + +FMT Values for PSFB Payload Types + Value Name Long Name Reference + 1 PLI Picture Loss Indication [RFC4585] + 2 SLI Slice Loss Indication [RFC4585] + 3 RPSI Reference Picture Selection Indication [RFC4585] + 4 FIR Full Intra Request Command [RFC5104] + 5 TSTR Temporal-Spatial Trade-off Request [RFC5104] + 6 TSTN Temporal-Spatial Trade-off Notification [RFC5104] + 7 VBCM Video Back Channel Message [RFC5104] + 8 PSLEI Payload-Specific Third-Party Loss Early Indication [RFC6642] + 9 ROI Video region-of-interest (ROI) [3GPP TS 26.114 v16.3.0][Ozgur_Oyman] + 10 LRR Layer Refresh Request Command [RFC-ietf-avtext-lrr-07] + 11-14 Unassigned + 15 AFB Application Layer Feedback [RFC4585] + 16-30 Unassigned + 31 Extension Reserved for future extensions [RFC4585] + + +RTP Compact Header Extensions + Extension URI Description Contact Reference + urn:ietf:params:rtp-hdrext:toffset Transmission Time offsets [Singer] [RFC5450] + urn:ietf:params:rtp-hdrext:smpte-tc SMPTE time-code mapping [Singer] [RFC5484] + urn:ietf:params:rtp-hdrext:ntp-64 Synchronisation metadata: 64-bit [Thomas_Schierl] [IETF +Audio/Video Transport timestamp format Working Group][RFC6051] + urn:ietf:params:rtp-hdrext:ntp-56 Synchronisation metadata: 56-bit [Thomas_Schierl] [IETF +Audio/Video Transport timestamp format Working Group][RFC6051] + urn:ietf:params:rtp-hdrext:ssrc-audio-level Audio Level [Jonathan_Lennox] [RFC6464] + urn:ietf:params:rtp-hdrext:csrc-audio-level Mixer-to-client audio level indicators [Emil_Ivov] [RFC6465] + urn:ietf:params:rtp-hdrext:encrypt Encrypted extension header element [Jonathan_Lennox] [RFC6904] + urn:3gpp:video-orientation Coordination of video orientation (CVO) [Specifications_Manager_3GPP] +[3GPP TS 26.114, version feature, see clause 6.2.3 12.5.0] Higher +granularity (6-bit) coordination [3GPP TS 26.114, version urn:3gpp:video-orientation:6 +of video orientation (CVO) feature, see [Specifications_Manager_3GPP] 12.5.0] clause 6.2.3 Signalling of the arbitrary +[3GPP TS 26.114, version urn:3gpp:roi-sent region-of-interest (ROI) information for +[Specifications_Manager_3GPP] 13.1.0] the sent video, see clause 6.2.3.4 Signalling of the predefined [3GPP TS 26.114, +version urn:3gpp:predefined-roi-sent region-of-interest (ROI) information for +[Specifications_Manager_3GPP] 13.1.0] the sent video, see clause 6.2.3.4 Reserved as base URN for RTCP SDES items + urn:ietf:params:rtp-hdrext:sdes that are also defined as RTP compact Authors of [RFC7941] [RFC7941] + header extensions. + urn:ietf:params:rtp-hdrext:splicing-interval Splicing Interval [Jinwei_Xia] [RFC8286] + + +RTP SDES Compact Header Extensions + Extension URI Description Contact Reference + urn:ietf:params:rtp-hdrext:sdes:cname Source Description: Canonical Authors of [RFC7941] +[RFC7941] End-Point Identifier (SDES CNAME) urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id RTP Stream Identifier +[Adam_Roach] [RFC8852] urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id RTP Repaired Stream Identifier +[Adam_Roach] [RFC8852] urn:ietf:params:rtp-hdrext:sdes:CaptId CLUE CaptId [Roni_Even] [RFC8849] + urn:ietf:params:rtp-hdrext:sdes:mid Media identification [IESG] +[RFC-ietf-mmusic-rfc8843bis-05] +*/ + +enum RTPExtensionType { + RTP_HDREXT_PADDING = 0, + RTP_HDREXT_SSRC_AUDIO_LEVEL_ID, // [rfc6464] urn:ietf:params:rtp-hdrext:ssrc-audio-level + RTP_HDREXT_CSRC_AUDIO_LEVEL_ID, // [rfc6465] urn:ietf:params:rtp-hdrext:csrc-audio-level + RTP_HDREXT_FRAME_MARKING_ID, // [rfc8852] urn:ietf:params:rtp-hdrext:framemarking + RTP_HDREXT_SDES_MID_ID, // [rfc8852] urn:ietf:params:rtp-hdrext:sdes:mid + RTP_HDREXT_SDES_RTP_STREAM_ID, // urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id + RTP_HDREXT_SDES_REPAIRED_RTP_STREAM_ID, // urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id + RTP_HDREXT_TOFFSET_ID, // [rfc5450] urn:ietf:params:rtp-hdrext:toffset + RTP_HDREXT_VIDEO_ORIENTATION_ID, // urn:3gpp:video-orientation + // (http://www.etsi.org/deliver/etsi_ts/126100_126199/126114/12.07.00_60/ts_126114v120700p.pdf) + RTP_HDREXT_ABSOLUTE_SEND_TIME_ID, // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time + RTP_HDREXT_ABSOLUTE_CAPTURE_TIME_ID, // http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time + RTP_HDREXT_TRANSPORT_WIDE_CC_ID_01, // http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 + RTP_HDREXT_TRANSPORT_WIDE_CC_ID, // http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02 + RTP_HDREXT_VIDEO_TIMING_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-timing + RTP_HDREXT_PLAYOUT_DELAY_ID, // http://www.webrtc.org/experiments/rtp-hdrext/playout-delay + RTP_HDREXT_ONE_BYTE_RESERVED, + RTP_HDREXT_COLOR_SPACE_ID, // http://www.webrtc.org/experiments/rtp-hdrext/color-space + RTP_HDREXT_VIDEO_CONTENT_TYPE_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-content-type + RTP_HDREXT_INBAND_CN_ID, // http://www.webrtc.org/experiments/rtp-hdrext/inband-cn + RTP_HDREXT_VIDEO_FRAME_TRACKING_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-frame-tracking-id + RTP_HDREXT_VIDEO_LAYERS_ALLOCATION_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00 + // RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_00, // + // http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00 RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_02, + // // http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-02 RTP_HDREXT_ENCRYPT, // [rfc6904] + // urn:ietf:params:rtp-hdrext:encrypt + + RTP_HDREXT_NUM +}; + +enum { + RTP_HDREXT_PROFILE_ONE_BYTE = 0xBEDE, + RTP_HDREXT_PROFILE_TWO_BYTE = 0x1000, + RTP_HDREXT_PROFILE_TWO_BYTE_FILTER = 0xFFF0, +}; + +enum { RTP_VIDEO_CONTENT_TYPE_UNSPECIFIED = 0, RTP_VIDEO_CONTENT_TYPE_SCREENSHARE }; + +struct rtp_ext_uri_t { + uint8_t id; + const char *uri; +}; + +struct rtp_ext_data_t { + uint32_t id : 8; + uint32_t len : 8; // bytes + uint32_t off : 16; // offset +}; + +struct rtp_ext_absolute_capture_time_t { + uint64_t timestamp; // absolute capture timestamp + uint64_t offset; // estimated capture clock offset +}; + +struct rtp_ext_transport_wide_cc_t { + uint32_t seq : 16; + uint32_t t : 1; + uint32_t count : 15; +}; + +struct rtp_ext_video_orientation_t { + int camera; // 1-Back-facing camera, 0-Front-facing camera + int flip; // 1-Horizontal flip operation + int rotaion; // 0/90/180/270 +}; + +struct rtp_ext_video_timing_t { + int flags; // 0x01-extension is set due to timer, 0x02-extension is set because the frame is larger than usual + uint16_t encode_start; + uint16_t encode_finish; + uint16_t packetization_complete; + uint16_t last_packet_left_the_pacer; + uint16_t network_timestamp; + uint16_t network_timestamp2; +}; + +struct rtp_ext_playout_delay_t { + uint16_t min_delay; + uint16_t max_delay; +}; + +struct rtp_ext_color_space_t { + uint8_t primaries; // Color primaries value according to ITU-T H.273 Table 2. + uint8_t transfer; // Transfer characteristic value according to ITU-T H.273 Table 3. + uint8_t matrix; // Matrix coefficients value according to ITU-T H.273 Table 4. + uint8_t range_chroma_siting; // https://www.webmproject.org/docs/container/#colour + + // HDR metadata(tow-byte RTP header extension) + uint16_t luminance_max; // Luminance max, specified in nits, where 1 nit = 1 cd/m2. (16-bit unsigned integer) + uint16_t luminance_min; // Luminance min, scaled by a factor of 10000 and specified in the unit 1/10000 nits. + // (16-bit unsigned integer) + uint32_t mastering_metadata_primary_red; // CIE 1931 xy chromaticity coordinates of the primary red, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint32_t mastering_metadata_primary_green; // CIE 1931 xy chromaticity coordinates of the primary green, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint32_t mastering_metadata_primary_blue; // CIE 1931 xy chromaticity coordinates of the primary blue, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint32_t mastering_metadata_primary_white; // CIE 1931 xy chromaticity coordinates of the white point, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint16_t max_content_light_level; // Max content light level, specified in nits. (16-bit unsigned integer) + uint16_t + max_frame_average_light_level; // Max frame average light level, specified in nits. (16-bit unsigned integer) +}; + +struct rtp_ext_frame_marking_t { + uint32_t s : 1; /* Start of Frame */ + uint32_t e : 1; /* End of Frame */ + uint32_t i : 1; /* Independent Frame */ + uint32_t d : 1; /* Discardable Frame */ + uint32_t b : 1; /* Base Layer Sync */ + uint32_t tid : 3; // The temporal layer ID of current frame + uint32_t lid : 8; + uint32_t tl0_pic_idx : 8; // 8 bits temporal layer zero index +}; + +struct rtp_ext_video_layers_allocation_t { + uint8_t rid; +}; + +/// count: RTP_HDREXT_NUM-1(skip padding) +/// @return ext id/uri +const struct rtp_ext_uri_t *rtp_ext_list(); +const struct rtp_ext_uri_t *rtp_ext_find_uri(const char *uri); + +/// @param[out] exts parsed rtpext header payload offset/bytes (MUST memset(exts, 0, sizeof(exts))); +/// @return 0-ok, other-error +int rtp_ext_read(uint16_t profile, const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]); + +/// @param[in] profile RTP_HDREXT_PROFILE_ONE_BYTE/RTP_HDREXT_PROFILE_TWO_BYTE, 0-auto(detect one/two byte by length) +/// @param[in] count rtp hdrext item count(exts) +/// @return >0-ok, other-error +int rtp_ext_write(uint16_t profile, const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, + uint8_t *data, int bytes); + +/// @param[in] n should be at least bytes + 1 +/// @return 0-ok, other-error +int rtp_ext_string_parse(const uint8_t *data, int bytes, char *v, int n); +/// @return write bytes +int rtp_ext_string_write(uint8_t *data, int bytes, const char *v, int n); + +/// @param[out] activity 0-inactivity, 1-activity +/// @return 0-ok, other-error +int rtp_ext_ssrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t *activity, uint8_t *level); +/// @return write bytes +int rtp_ext_ssrc_audio_level_write(uint8_t *data, int bytes, uint8_t activity, uint8_t level); +/// @return 0-ok, other-error +int rtp_ext_csrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t levels[], int num); +/// @return write bytes +int rtp_ext_csrc_audio_level_write(uint8_t *data, int bytes, const uint8_t levels[], int num); +/// @return 0-ok, other-error +int rtp_ext_frame_marking_parse(const uint8_t *data, int bytes, struct rtp_ext_frame_marking_t *ext); +/// @return write bytes +int rtp_ext_frame_marking_write(uint8_t *data, int bytes, const struct rtp_ext_frame_marking_t *ext); +// int rtp_ext_sdes_mid(void* param, const uint8_t* data, int bytes); +// int rtp_ext_sdes_rtp_stream_id(void* param, const uint8_t* data, int bytes); +// int rtp_ext_sdes_repaired_rtp_stream_id(void* param, const uint8_t* data, int bytes); +/// @param[out] timestamp rtp time +/// @return 0-ok, other-error +int rtp_ext_toffset_parse(const uint8_t *data, int bytes, uint32_t *timestamp); +/// @return write bytes +int rtp_ext_toffset_write(uint8_t *data, int bytes, uint32_t timestamp); +/// @param[out] rotaion 0/90/180/270 +/// @return 0-ok, other-error +int rtp_ext_video_orientation_parse(const uint8_t *data, int bytes, struct rtp_ext_video_orientation_t *ext); +/// @return write bytes +int rtp_ext_video_orientation_write(uint8_t *data, int bytes, const struct rtp_ext_video_orientation_t *ext); +/// @param[out] timestamp in millisecond +/// @return 0-ok, other-error +int rtp_ext_abs_send_time_parse(const uint8_t *data, int bytes, uint64_t *timestamp); +/// @return write bytes +int rtp_ext_abs_send_time_write(uint8_t *data, int bytes, uint64_t timestamp); +/// @return 0-ok, other-error +int rtp_ext_absolute_capture_time_parse(const uint8_t *data, int bytes, struct rtp_ext_absolute_capture_time_t *ext); +/// @return write bytes +int rtp_ext_absolute_capture_time_write(uint8_t *data, int bytes, const struct rtp_ext_absolute_capture_time_t *ext); +/// @return 0-ok, other-error +int rtp_ext_transport_wide_cc_parse(const uint8_t *data, int bytes, struct rtp_ext_transport_wide_cc_t *ext); +/// @return write bytes(2-v1, 4-v2) +int rtp_ext_transport_wide_cc_write(uint8_t *data, int bytes, const struct rtp_ext_transport_wide_cc_t *ext); +/// @return 0-ok, other-error +int rtp_ext_video_timing_parse(const uint8_t *data, int bytes, struct rtp_ext_video_timing_t *ext); +/// @return write bytes +int rtp_ext_video_timing_write(uint8_t *data, int bytes, const struct rtp_ext_video_timing_t *ext); +/// @return 0-ok, other-error +int rtp_ext_playout_delay_parse(const uint8_t *data, int bytes, struct rtp_ext_playout_delay_t *ext); +/// @return write bytes +int rtp_ext_playout_delay_write(uint8_t *data, int bytes, const struct rtp_ext_playout_delay_t *ext); +/// @return 0-ok, other-error +int rtp_ext_color_space_parse(const uint8_t *data, int bytes, struct rtp_ext_color_space_t *ext); +/// @return write bytes +int rtp_ext_color_space_write(uint8_t *data, int bytes, const struct rtp_ext_color_space_t *ext); +/// @return 0-ok, other-error +int rtp_ext_video_content_type_parse(const uint8_t *data, int bytes, uint8_t *ext); +/// @return write bytes +int rtp_ext_video_content_type_write(uint8_t *data, int bytes, uint8_t ext); +/// @return 0-ok, other-error +int rtp_ext_inband_cn_parse(const uint8_t *data, int bytes, uint8_t *level); +/// @return write bytes +int rtp_ext_inband_cn_write(uint8_t *data, int bytes, uint8_t level); +/// @return 0-ok, other-error +int rtp_ext_video_frame_tracking_id_parse(const uint8_t *data, int bytes, uint16_t *id); +/// @return write bytes +int rtp_ext_video_frame_tracking_id_write(uint8_t *data, int bytes, uint16_t id); +/// @return 0-ok, other-error +int rtp_ext_video_layers_allocation_parse(const uint8_t *data, int bytes, + struct rtp_ext_video_layers_allocation_t *ext); +/// @return write bytes +int rtp_ext_video_layers_allocation_write(uint8_t *data, int bytes, + const struct rtp_ext_video_layers_allocation_t *ext); + +#ifdef __cplusplus +} +#endif +#endif /* !_rtp_ext_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-header-extension.h b/src/tuya_p2p/lib_rtp/include/rtp-header-extension.h new file mode 100755 index 000000000..de3894906 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-header-extension.h @@ -0,0 +1,312 @@ +#ifndef _rtp_header_extension_h_ +#define _rtp_header_extension_h_ + +#include +#include +#include "rtp-ext.h" + +template +bool GetRtpExtensionWithMap(const void *data, const struct rtp_ext_data_t view[256], const uint8_t exts[256], + Values... values) +{ + uint8_t id = exts[Extension::Id]; + assert(0 == view[id].id || id == view[id].id); + return 0 != id && id == view[id].id && + Extension::Parse((const uint8_t *)data + view[id].off, (uint16_t)view[id].len, values...); +} + +template +bool GetRtpExtension(const void *data, const struct rtp_ext_data_t view[256], Values... values) +{ + uint8_t id = view[Extension::Id].id; + assert(0 == id || id == Extension::Id); + return 0 != id && Extension::Parse((const uint8_t *)data + view[id].off, (uint16_t)view[id].len, values...); +} + +template +int SetRtpExtension(void *data, int bytes, const struct rtp_ext_data_t view[256], Values... values) +{ + if (bytes < Extension::Size) + return -1; + return Extension::Write((uint8_t *)data, bytes, view[Extension::Id].id, values...); +} + +class RtpExtensionSsrcAudioLevel +{ +public: + static const uint8_t Id = RTP_HDREXT_SSRC_AUDIO_LEVEL_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *activity, uint8_t *level) + { + return 0 == rtp_ext_ssrc_audio_level_parse(data, bytes, activity, level); + } + static int Write(uint8_t *data, uint16_t bytes, uint8_t activity, uint8_t level) + { + return rtp_ext_ssrc_audio_level_write(data, bytes, activity, level); + } +}; + +class RtpExtensionCsrcAudioLevel +{ +public: + static const uint8_t Id = RTP_HDREXT_CSRC_AUDIO_LEVEL_ID; + static const uint16_t Size = 15; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *levels, int num) + { + return 0 == rtp_ext_csrc_audio_level_parse(data, bytes, levels, num); + } + static int Write(uint8_t *data, uint16_t bytes, const uint8_t *levels, int num) + { + return rtp_ext_csrc_audio_level_write(data, bytes, levels, num); + } +}; + +class RtpExtensionFrameMarking +{ +public: + static const uint8_t Id = RTP_HDREXT_FRAME_MARKING_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_frame_marking_t *ext) + { + return 0 == rtp_ext_frame_marking_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_frame_marking_t *ext) + { + return rtp_ext_frame_marking_write(data, bytes, ext); + } +}; + +class RtpExtensionString +{ +public: + static bool Parse(const uint8_t *data, uint16_t bytes, std::string &rid) + { + rid.reserve(bytes); + return 0 == rtp_ext_string_parse(data, bytes, (char *)rid.data(), rid.capacity()); + } + static int Write(uint8_t *data, uint16_t bytes, const char *v, int n) + { + return rtp_ext_string_write(data, bytes, v, n); + } +}; + +class RtpExtensionSdesMid : public RtpExtensionString +{ +public: + static const uint8_t Id = RTP_HDREXT_SDES_MID_ID; + static const uint16_t Size = 255; + static const char *Uri() { return rtp_ext_list()[Id].uri; } +}; + +class RtpExtensionSdesRtpStreamId : public RtpExtensionString +{ +public: + static const uint8_t Id = RTP_HDREXT_SDES_RTP_STREAM_ID; + static const uint16_t Size = 255; + static const char *Uri() { return rtp_ext_list()[Id].uri; } +}; + +class RtpExtensionSdesRepairedRtpStreamId : public RtpExtensionString +{ +public: + static const uint8_t Id = RTP_HDREXT_SDES_REPAIRED_RTP_STREAM_ID; + static const uint16_t Size = 255; + static const char *Uri() { return rtp_ext_list()[Id].uri; } +}; + +class RtpExtensionTransmissionOffset +{ +public: + static const uint8_t Id = RTP_HDREXT_TOFFSET_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint32_t *timestamp) + { + return 0 == rtp_ext_toffset_parse(data, bytes, timestamp); + } + static int Write(uint8_t *data, uint16_t bytes, uint32_t timestamp) + { + return rtp_ext_toffset_write(data, bytes, timestamp); + } +}; + +class RtpExtensionVideoOrientation +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_ORIENTATION_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_video_orientation_t *ext) + { + return 0 == rtp_ext_video_orientation_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_video_orientation_t *ext) + { + return rtp_ext_video_orientation_write(data, bytes, ext); + } +}; + +class RtpExtensionAbsoluteSendTime +{ +public: + static const uint8_t Id = RTP_HDREXT_ABSOLUTE_SEND_TIME_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint64_t *ms) + { + return 0 == rtp_ext_abs_send_time_parse(data, bytes, ms); + } + static int Write(uint8_t *data, uint16_t bytes, uint64_t ms) + { + return rtp_ext_abs_send_time_write(data, bytes, ms); + } +}; + +class RtpExtensionAbsoluteCaptureTime +{ +public: + static const uint8_t Id = RTP_HDREXT_ABSOLUTE_CAPTURE_TIME_ID; + static const uint16_t Size = 16; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_absolute_capture_time_t *ext) + { + return 0 == rtp_ext_absolute_capture_time_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_absolute_capture_time_t *ext) + { + return rtp_ext_absolute_capture_time_write(data, bytes, ext); + } +}; + +class RtpExtensionTransportSequenceNumber +{ +public: + static const uint8_t Id = RTP_HDREXT_TRANSPORT_WIDE_CC_ID; + static const uint16_t Size = 4; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_transport_wide_cc_t *ext) + { + return 0 == rtp_ext_transport_wide_cc_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_transport_wide_cc_t *ext) + { + return rtp_ext_transport_wide_cc_write(data, bytes, ext); + } +}; + +class RtpExtensionVideoTiming +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_TIMING_ID; + static const uint16_t Size = 13; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_video_timing_t *ext) + { + return 0 == rtp_ext_video_timing_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_video_timing_t *ext) + { + return rtp_ext_video_timing_write(data, bytes, ext); + } +}; + +class RtpExtensionPlayoutDelay +{ +public: + static const uint8_t Id = RTP_HDREXT_PLAYOUT_DELAY_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_playout_delay_t *ext) + { + return 0 == rtp_ext_playout_delay_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_playout_delay_t *ext) + { + return rtp_ext_playout_delay_write(data, bytes, ext); + } +}; + +class RtpExtensionColorSpace +{ +public: + static const uint8_t Id = RTP_HDREXT_COLOR_SPACE_ID; + static const uint16_t Size = 28; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_color_space_t *ext) + { + return 0 == rtp_ext_color_space_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_color_space_t *ext) + { + return rtp_ext_color_space_write(data, bytes, ext); + } +}; + +class RtpExtensionVideoContentType +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_CONTENT_TYPE_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *ext) + { + return 0 == rtp_ext_video_content_type_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, uint8_t ext) + { + return rtp_ext_video_content_type_write(data, bytes, ext); + } +}; + +class RtpExtensionInbandComfortNoise +{ +public: + static const uint8_t Id = RTP_HDREXT_INBAND_CN_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *level) + { + return 0 == rtp_ext_inband_cn_parse(data, bytes, level); + } + static int Write(uint8_t *data, uint16_t bytes, uint8_t level) + { + return rtp_ext_inband_cn_write(data, bytes, level); + } +}; + +class RtpExtensionVideoFrameTracking +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_FRAME_TRACKING_ID; + static const uint16_t Size = 2; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint16_t *ext) + { + return 0 == rtp_ext_video_frame_tracking_id_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, uint16_t ext) + { + return rtp_ext_video_frame_tracking_id_write(data, bytes, ext); + } +}; + +class RtpExtensionVideoLayersAllocation +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_LAYERS_ALLOCATION_ID; + static const uint16_t Size = 2; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_video_layers_allocation_t *ext) + { + return 0 == rtp_ext_video_layers_allocation_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_video_layers_allocation_t *ext) + { + return rtp_ext_video_layers_allocation_write(data, bytes, ext); + } +}; + +#endif /* !_rtp_header_extension_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-header.h b/src/tuya_p2p/lib_rtp/include/rtp-header.h new file mode 100755 index 000000000..93e06f8b6 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-header.h @@ -0,0 +1,28 @@ +#ifndef _rtp_header_h_ +#define _rtp_header_h_ + +#include + +#define RTP_VERSION 2 // RTP version field must equal 2 (p66) + +typedef struct _rtp_header_t { + uint32_t v : 2; /* protocol version */ + uint32_t p : 1; /* padding flag */ + uint32_t x : 1; /* header extension flag */ + uint32_t cc : 4; /* CSRC count */ + uint32_t m : 1; /* marker bit */ + uint32_t pt : 7; /* payload type */ + uint32_t seq : 16; /* sequence number */ + uint32_t timestamp; /* timestamp */ + uint32_t ssrc; /* synchronization source */ +} rtp_header_t; + +#define RTP_V(v) ((v >> 30) & 0x03) /* protocol version */ +#define RTP_P(v) ((v >> 29) & 0x01) /* padding flag */ +#define RTP_X(v) ((v >> 28) & 0x01) /* header extension flag */ +#define RTP_CC(v) ((v >> 24) & 0x0F) /* CSRC count */ +#define RTP_M(v) ((v >> 23) & 0x01) /* marker bit */ +#define RTP_PT(v) ((v >> 16) & 0x7F) /* payload type */ +#define RTP_SEQ(v) ((v >> 00) & 0xFFFF) /* sequence number */ + +#endif /* !_rtp_header_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-internal.h b/src/tuya_p2p/lib_rtp/include/rtp-internal.h new file mode 100755 index 000000000..7b356248b --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-internal.h @@ -0,0 +1,67 @@ +#ifndef _rtp_internal_h_ +#define _rtp_internal_h_ + +#include "rtp.h" +#include "rtp-header.h" +#include "rtcp-header.h" +#include "rtp-member.h" +#include "rtp-member-list.h" +#include +#include +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +struct rtp_context { + struct rtp_event_t handler; + void *cbparam; + + void *members; // rtp source list + void *senders; // rtp sender list + struct rtp_member *self; + + // RTP/RTCP + int avg_rtcp_size; + int rtcp_bw; + int rtcp_cycle; // for RTCP SDES + int frequence; + int init; + int role; +}; + +struct rtp_member *rtp_sender_fetch(struct rtp_context *ctx, uint32_t ssrc); +struct rtp_member *rtp_member_fetch(struct rtp_context *ctx, uint32_t ssrc); + +int rtcp_input_rtp(struct rtp_context *ctx, const void *data, int bytes); +int rtcp_input_rtcp(struct rtp_context *ctx, const void *data, int bytes); + +int rtcp_rr_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_sr_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_sdes_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_bye_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_app_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes, const char name[4], const void *app, int len); +int rtcp_rtpfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_rtpfb_type_t id, + const rtcp_rtpfb_t *rtpfb); +int rtcp_psfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_psfb_type_t id, + const rtcp_psfb_t *psfb); +int rtcp_xr_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr); +void rtcp_rr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_sr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_sdes_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_bye_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_app_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_rtpfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_psfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_xr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); + +int rtcp_report_block(struct rtp_member *sender, uint8_t *ptr, int bytes); + +uint64_t rtpclock(void); +uint64_t ntp2clock(uint64_t ntp); +uint64_t clock2ntp(uint64_t clock); + +uint32_t rtp_ssrc(void); + +#endif /* !_rtp_internal_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-member-list.h b/src/tuya_p2p/lib_rtp/include/rtp-member-list.h new file mode 100755 index 000000000..3633da9b2 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-member-list.h @@ -0,0 +1,17 @@ +#ifndef _rtp_member_list_h_ +#define _rtp_member_list_h_ + +#include "rtp-member.h" + +void *rtp_member_list_create(void); +void rtp_member_list_destroy(void *members); + +int rtp_member_list_count(void *members); +struct rtp_member *rtp_member_list_get(void *members, int index); + +struct rtp_member *rtp_member_list_find(void *members, uint32_t ssrc); + +int rtp_member_list_add(void *members, struct rtp_member *source); +int rtp_member_list_delete(void *members, uint32_t ssrc); + +#endif /* !_rtp_member_list_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-member.h b/src/tuya_p2p/lib_rtp/include/rtp-member.h new file mode 100755 index 000000000..c5d472196 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-member.h @@ -0,0 +1,42 @@ +#ifndef _rtp_member_h_ +#define _rtp_member_h_ + +#include "rtcp-header.h" + +#define RTP_PROBATION 2 +#define RTP_DROPOUT 500 +#define RTP_MISORDER 100 + +struct rtp_member { + int32_t ref; + + uint32_t ssrc; // ssrc == rtcp_sr.ssrc == rtcp_rb.ssrc + rtcp_sr_t rtcp_sr; + // rtcp_rb_t rtcp_rb; + rtcp_sdes_item_t sdes[9]; // SDES item + + uint64_t rtcp_clock; // last RTCP SR/RR packet clock(local time) + + uint16_t rtp_seq; // last send/received RTP packet RTP sequence(in packet header) + uint32_t rtp_timestamp; // last send/received RTP packet RTP timestamp(in packet header) + uint64_t rtp_clock; // last send/received RTP packet clock(local time) + uint32_t rtp_packets; // send/received RTP packet count(include duplicate, late) + uint64_t rtp_bytes; // send/received RTP octet count + + double jitter; + uint32_t rtp_packets0; // last SR received RTP packets + uint32_t rtp_expected0; // last SR expect RTP sequence number + + uint16_t rtp_probation; + uint16_t rtp_seq_base; // init sequence number + uint32_t rtp_seq_bad; // bad sequence number + uint32_t rtp_seq_cycles; // high extension sequence number +}; + +struct rtp_member *rtp_member_create(uint32_t ssrc); +void rtp_member_addref(struct rtp_member *member); +void rtp_member_release(struct rtp_member *member); + +int rtp_member_setvalue(struct rtp_member *member, int item, const uint8_t *data, int bytes); + +#endif /* !_rtp_member_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-packet.h b/src/tuya_p2p/lib_rtp/include/rtp-packet.h new file mode 100755 index 000000000..5ca410b9a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-packet.h @@ -0,0 +1,24 @@ +#ifndef _rtp_packet_h_ +#define _rtp_packet_h_ + +#include "rtp-header.h" + +#define RTP_FIXED_HEADER 12 + +struct rtp_packet_t { + rtp_header_t rtp; + uint32_t csrc[16]; + const void *extension; // extension(valid only if rtp.x = 1) + uint16_t extlen; // extension length in bytes + uint16_t extprofile; // extension reserved + const void *payload; // payload + int payloadlen; // payload length in bytes +}; + +///@return 0-ok, other-error +int rtp_packet_deserialize(struct rtp_packet_t *pkt, const void *data, int bytes); + +///@return <0-error, >0-rtp packet size, =0-impossible +int rtp_packet_serialize(const struct rtp_packet_t *pkt, void *data, int bytes); + +#endif /* !_rtp_packet_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-param.h b/src/tuya_p2p/lib_rtp/include/rtp-param.h new file mode 100755 index 000000000..57693470d --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-param.h @@ -0,0 +1,15 @@ +#ifndef _rtp_param_h_ +#define _rtp_param_h_ + +// RFC3550 6.2 RTCP Transmission Interval (p21) +// It is recommended that the fraction of the session bandwidth added for RTCP be fixed at 5%. +// It is also recommended that 1/4 of the RTCP bandwidth be dedicated to participants that are sending data +#define RTCP_BANDWIDTH_FRACTION 0.05 +#define RTCP_SENDER_BANDWIDTH_FRACTION 0.25 + +#define RTCP_REPORT_INTERVAL 5000 /* milliseconds RFC3550 p25 */ +#define RTCP_REPORT_INTERVAL_MIN 2500 /* milliseconds RFC3550 p25 */ + +#define RTP_PAYLOAD_MAX_SIZE (10 * 1024 * 1024) + +#endif /* !_rtp_param_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-payload.h b/src/tuya_p2p/lib_rtp/include/rtp-payload.h new file mode 100755 index 000000000..ab2447ac4 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-payload.h @@ -0,0 +1,73 @@ +#ifndef _rtp_payload_h_ +#define _rtp_payload_h_ + +// https://en.wikipedia.org/wiki/RTP_audio_video_profile + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// RTP packet lost(miss packet before this frame) +#define RTP_PAYLOAD_FLAG_PACKET_LOST 0x0100 // some packets lost before the packet +#define RTP_PAYLOAD_FLAG_PACKET_CORRUPT 0x0200 // the packet data is corrupt + +struct rtp_payload_t { + void *(*alloc)(void *param, int bytes); + void (*free)(void *param, void *packet); + + /// @return 0-ok, other-error + int (*packet)(void *param, const void *packet, int bytes, uint32_t timestamp, int flags); +}; + +/// Create RTP packet encoder +/// @param[in] payload RTP payload type, value: [0, 127] (see more about rtp-profile.h) +/// @param[in] name RTP payload name +/// @param[in] seq RTP header sequence number filed +/// @param[in] ssrc RTP header SSRC filed +/// @param[in] handler user-defined callback functions +/// @param[in] cbparam user-defined parameter +/// @return NULL-error, other-ok +void *rtp_payload_encode_create(int payload, const char *name, uint16_t seq, uint32_t ssrc, + struct rtp_payload_t *handler, void *cbparam); +void rtp_payload_encode_destroy(void *encoder); + +/// Get rtp last packet sequence number and timestamp +/// @param[in] encoder RTP packet encoder(create by rtp_payload_encode_create) +/// @param[in] seq RTP header sequence number +/// @param[in] timestamp RTP header timestamp +void rtp_payload_encode_getinfo(void *encoder, uint16_t *seq, uint32_t *timestamp); + +/// Encode RTP packet +/// @param[in] encoder RTP packet encoder(create by rtp_payload_encode_create) +/// @param[in] data stream data +/// @param[in] bytes stream length in bytes +/// @param[in] timestamp RTP header timestamp +/// @return 0-ok, ENOMEM-alloc failed, <0-failed +int rtp_payload_encode_input(void *encoder, const void *data, int bytes, uint32_t timestamp); + +/// Create RTP packet decoder +/// @param[in] payload RTP payload type, value: [0, 127] (see more about rtp-profile.h) +/// @param[in] name RTP payload name +/// @param[in] handler user-defined callback functions +/// @param[in] cbparam user-defined parameter +/// @return NULL-error, other-ok +void *rtp_payload_decode_create(int payload, const char *name, struct rtp_payload_t *handler, void *cbparam); +void rtp_payload_decode_destroy(void *decoder); + +/// Decode RTP packet +/// @param[in] decoder RTP packet decoder(create by rtp_payload_decode_create) +/// @param[in] packet RTP packet, include rtp header(12 bytes) +/// @param[in] bytes RTP packet length in bytes +/// @return 1-packet handled, 0-packet discard, <0-failed +int rtp_payload_decode_input(void *decoder, const void *packet, int bytes); + +/// Set/Get rtp encode packet size(include rtp header) +void rtp_packet_setsize(int bytes); +int rtp_packet_getsize(void); + +#ifdef __cplusplus +} +#endif +#endif /* !_rtp_payload_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-profile.h b/src/tuya_p2p/lib_rtp/include/rtp-profile.h new file mode 100755 index 000000000..008b98fcf --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-profile.h @@ -0,0 +1,133 @@ +#ifndef _rtp_profile_h_ +#define _rtp_profile_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + RTP_TYPE_UNKNOWN = 0, + RTP_TYPE_AUDIO, + RTP_TYPE_VIDEO, + RTP_TYPE_SYSTEM, +}; + +enum { + RTP_PAYLOAD_DYNAMIC = 96, +}; + +/// https://en.wikipedia.org/wiki/RTP_audio_video_profile +/// RFC3551 6. Payload Type Definitions (p28) +struct rtp_profile_t { + int payload; // 0~127, 96-127 dynamic, 35-71 unassigned, 72-76 reserved, 77-95 unassigned + int avtype; // 0-unknown, 1-audio, 2-video, 3-system(audio/video) + int channels; // number of channels + int frequency; // clock rate + char name[32]; // case insensitive +}; + +/*** +{ + // audio + { 0, "PCMU", 8000, 1 }, // G711 mu-law + { 1, "", 0, 0 }, // reserved + { 2, "", 0, 0 }, // reserved + { 3, "GSM", 8000, 1 }, + { 4, "G723", 8000, 1 }, + { 5, "DVI4", 8000, 1 }, + { 6, "DVI4", 16000, 1 }, + { 7, "LPC", 8000, 1 }, + { 8, "PCMA", 8000, 1 }, // G711 A-law + { 9, "G722", 8000, 1 }, + {10, "L16", 44100, 2 }, + {11, "L16", 44100, 1 }, + {12, "QCELP", 8000, 1 }, + {13, "CN", 8000, 1 }, + {14, "MPA", 90000, 0 }, // MPEG-1/MPEG-2 audio + {15, "G728", 8000, 1 }, + {16, "DVI4", 11025, 1 }, + {17, "DVI4", 22050, 1 }, + {18, "G729", 8000, 1 }, + {19, "", 0, 0 }, // reserved + {20, "", 0, 0 }, // unassigned + {21, "", 0, 0 }, // unassigned + {22, "", 0, 0 }, // unassigned + {23, "", 0, 0 }, // unassigned + //{ 0, "G726-40", 8000, 1 }, + //{ 0, "G726-32", 8000, 1 }, + //{ 0, "G726-24", 8000, 1 }, + //{ 0, "G726-16", 8000, 1 }, + //{ 0, "G729-D", 8000, 1 }, + //{ 0, "G729-E", 8000, 1 }, + //{ 0, "GSM-EFR", 8000, 1 }, + //{ 0, "L8", var, 1 }, + + // video + {24, "", 0, 0 }, // unassigned + {25, "CelB", 90000, 0 }, // SUN CELL-B + {26, "JPEG", 90000, 0 }, + {27, "", 0, 0 }, // unassigned + {28, "nv", 90000, 0 }, + {29, "", 0, 0 }, // unassigned + {30, "", 0, 0 }, // unassigned + {31, "H261", 90000, 0 }, + {32, "MPV", 90000, 0 }, // MPEG-1/MPEG-2 video + {33, "MP2T", 90000, 0 }, // MPEG-2 TS + {34, "H263", 90000, 0 }, + //{ 0, "H263-1998",90000, 0 }, + + // 35-71 unassigned + // 72-76 reserved + // 77-95 unassigned + // 96-127 dynamic + {96, "MPG4", 90000, 0 }, // RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams + {97, "MP2P", 90000, 0 }, // RFC3555 4.2.11 Registration of MIME media type video/MP2P + {98, "H264", 90000, 0 }, // RFC6184 RTP Payload Format for H.264 Video +}; +***/ + +enum { + RTP_PAYLOAD_PCMU = 0, // ITU-T G.711 PCM µ-Law audio 64 kbit/s (rfc3551) + RTP_PAYLOAD_G723 = 4, // ITU-T G.723.1 8000/1, 30ms (rfc3551) + RTP_PAYLOAD_PCMA = 8, // ITU-T G.711 PCM A-Law audio 64 kbit/s (rfc3551) + RTP_PAYLOAD_G722 = 9, // ITU-T G.722 audio 64 kbit/s (rfc3551) + RTP_PAYLOAD_CN = 13, // Real-time Transport Protocol (RTP) Payload for Comfort Noise (CN) (rfc3389) + RTP_PAYLOAD_MP3 = 14, // MPEG-1/MPEG-2 audio (rfc2250) + RTP_PAYLOAD_G729 = 18, // ITU-T G.729 and G.729a audio 8 kbit/s (rfc3551) + RTP_PAYLOAD_SVACA = 20, // GB28181-2016 + + RTP_PAYLOAD_JPEG = 26, // JPEG video (rfc2435) + RTP_PAYLOAD_MPV = 32, // MPEG-1 and MPEG-2 video (rfc2250) + RTP_PAYLOAD_MP2T = 33, // MPEG-2 transport stream (rfc2250) + RTP_PAYLOAD_H263 = 34, // H.263 video, first version (1996) (rfc2190) + RTP_PAYLOAD_AV1X = 35, // https://bugs.chromium.org/p/webrtc/issues/detail?id=11042 + + RTP_PAYLOAD_MP2P = 96, // MPEG-2 Program Streams video (rfc2250) + RTP_PAYLOAD_MP4V = 97, // MP4V-ES MPEG-4 Visual (rfc6416) + RTP_PAYLOAD_H264 = 98, // H.264 video (MPEG-4 Part 10) (rfc6184) + RTP_PAYLOAD_SVAC = 99, // GB28181-2016 + RTP_PAYLOAD_H265 = 100, // H.265 video (MPEG-H Part 2) (rfc7798) + RTP_PAYLOAD_MP4A = 101, // MPEG4-generic audio/video MPEG-4 Elementary Streams (rfc3640) + RTP_PAYLOAD_LATM = 102, // MP4A-LATM MPEG-4 Audio (rfc6416) + RTP_PAYLOAD_OPUS = 103, // RTP Payload Format for the Opus Speech and Audio Codec (rfc7587) + RTP_PAYLOAD_MP4ES = 104, // MPEG4-generic audio/video MPEG-4 Elementary Streams (rfc3640) + RTP_PAYLOAD_VP8 = 105, // RTP Payload Format for VP8 Video (rfc7741) + RTP_PAYLOAD_VP9 = 106, // RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + RTP_PAYLOAD_AV1 = 107, // https://aomediacodec.github.io/av1-rtp-spec/ + RTP_PAYLOAD_H266 = 108, // https://www.ietf.org/archive/id/draft-ietf-avtcore-rtp-vvc-18.html + + RTP_PAYLOAD_RTX = 110, // RTP Retransmission Payload Format (rfc4588) + RTP_PAYLOAD_RED = 111, // RTP Payload for Redundant Audio Data (rfc2198) + RTP_PAYLOAD_FEC_ULP = 112, // RTP Payload Format for Generic Forward Error Correction (rfc5109) + RTP_PAYLOAD_FEC_FLEX = 113, // RTP Payload Format for Flexible Forward Error Correction (rfc8267) + RTP_PAYLOAD_FEC_RS = 114, // RTP Payload Format for Reed-Solomon(non-standard/private) +}; + +///@param[in] payload RTP payload type(0 ~ 127) +///@return NULL if not exist +const struct rtp_profile_t *rtp_profile_find(int payload); + +#ifdef __cplusplus +} +#endif +#endif /* _rtp_profile_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-queue.h b/src/tuya_p2p/lib_rtp/include/rtp-queue.h new file mode 100755 index 000000000..0ea9259a8 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-queue.h @@ -0,0 +1,35 @@ +#ifndef _rtp_queue_h_ +#define _rtp_queue_h_ + +#include "rtp-packet.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct rtp_queue_t rtp_queue_t; + +rtp_queue_t *rtp_queue_create(int threshold, int frequency, void (*freepkt)(void *, struct rtp_packet_t *), + void *param); +int rtp_queue_destroy(rtp_queue_t *queue); + +/// @return 1-ok, 0-discard, <0-error +int rtp_queue_write(rtp_queue_t *queue, struct rtp_packet_t *pkt); +struct rtp_packet_t *rtp_queue_read(rtp_queue_t *queue); + +struct rtp_queue_stats_t { + int total; + + int duplicate; + int reorder; // misorder + int late; // two late + int bad; // bad seq + + int lost; // read discard by threshold +}; +void rtp_queue_stats(rtp_queue_t *queue, struct rtp_queue_stats_t *stats); + +#if defined(__cplusplus) +} +#endif +#endif /* !_rtp_queue_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-util.h b/src/tuya_p2p/lib_rtp/include/rtp-util.h new file mode 100755 index 000000000..ec411c0e3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-util.h @@ -0,0 +1,67 @@ +#ifndef _rtp_util_h_ +#define _rtp_util_h_ + +#include "rtp-header.h" +#include "rtcp-header.h" + +// The Internet Protocol defines big-endian as the standard network byte order +#define nbo_r16 rtp_read_uint16 +#define nbo_r32 rtp_read_uint32 +#define nbo_w16 rtp_write_uint16 +#define nbo_w32 rtp_write_uint32 + +static inline uint16_t rtp_read_uint16(const uint8_t *ptr) +{ + return (((uint16_t)ptr[0]) << 8) | ptr[1]; +} + +static inline uint32_t rtp_read_uint32(const uint8_t *ptr) +{ + return (((uint32_t)ptr[0]) << 24) | (((uint32_t)ptr[1]) << 16) | (((uint32_t)ptr[2]) << 8) | ptr[3]; +} + +static inline uint64_t rtp_read_uint64(const uint8_t *ptr) +{ + return (((uint64_t)rtp_read_uint32(ptr)) << 32) | rtp_read_uint32(ptr + 4); +} + +static inline void rtp_write_uint16(uint8_t *ptr, uint16_t val) +{ + ptr[0] = (uint8_t)(val >> 8); + ptr[1] = (uint8_t)val; +} + +static inline void rtp_write_uint32(uint8_t *ptr, uint32_t val) +{ + ptr[0] = (uint8_t)(val >> 24); + ptr[1] = (uint8_t)(val >> 16); + ptr[2] = (uint8_t)(val >> 8); + ptr[3] = (uint8_t)val; +} + +static inline void rtp_write_uint64(uint8_t *ptr, uint64_t val) +{ + rtp_write_uint32(ptr, (uint32_t)(val >> 32)); + rtp_write_uint32(ptr + 4, (uint32_t)val); +} + +static inline void nbo_write_rtp_header(uint8_t *ptr, const rtp_header_t *header) +{ + ptr[0] = (uint8_t)((header->v << 6) | (header->p << 5) | (header->x << 4) | header->cc); + ptr[1] = (uint8_t)((header->m << 7) | header->pt); + ptr[2] = (uint8_t)(header->seq >> 8); + ptr[3] = (uint8_t)(header->seq & 0xFF); + + nbo_w32(ptr + 4, header->timestamp); + nbo_w32(ptr + 8, header->ssrc); +} + +static inline void nbo_write_rtcp_header(uint8_t *ptr, const rtcp_header_t *header) +{ + ptr[0] = (uint8_t)((header->v << 6) | (header->p << 5) | header->rc); + ptr[1] = (uint8_t)(header->pt); + ptr[2] = (uint8_t)(header->length >> 8); + ptr[3] = (uint8_t)(header->length & 0xFF); +} + +#endif /* !_rtp_util_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp.h b/src/tuya_p2p/lib_rtp/include/rtp.h new file mode 100755 index 000000000..f42f6407a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp.h @@ -0,0 +1,136 @@ +#ifndef _rtp_h_ +#define _rtp_h_ + +#include +#include "rtcp-header.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtcp_msg_t { + int type; + uint32_t ssrc; // rtcp message sender + + union rtcp_msg_u { + // type = RTCP_SR + rtcp_rb_t sr; + + // type = RTCP_SR + rtcp_rb_t rr; + + // type = RTCP_SDES + rtcp_sdes_item_t sdes; + + // type = RTCP_BYE + rtcp_bye_t bye; + + // type = RTCP_APP + rtcp_app_t app; + + // type = RTCP_RTPFB | (RTCP_RTPFB_NACK << 8) + rtcp_rtpfb_t rtpfb; + + // type = RTCP_PSFB | (RTCP_PSFB_PLI << 8) + rtcp_psfb_t psfb; + + // type = RTCP_XR | (RTCP_XR_DLRR << 8) + rtcp_xr_t xr; + } u; +}; + +struct rtp_event_t { + void (*on_rtcp)(void *param, const struct rtcp_msg_t *msg); +}; + +/// @param[in] ssrc RTP SSRC +/// @param[in] timestamp base timestamp +/// @param[in] frequence RTP frequence +/// @param[in] bandwidth in byte +/// @param[in] sender 1-rtp sender(SR), 0-rtp receiver(RR) +void *rtp_create(struct rtp_event_t *handler, void *param, uint32_t ssrc, uint32_t timestamp, int frequence, + int bandwidth, int sender); +int rtp_destroy(void *rtp); + +/// RTP send notify +/// @param[in] rtp RTP object +/// @param[in] data RTP packet(include RTP Header) +/// @param[in] bytes RTP packet size in byte +/// @return 0-ok, <0-error +int rtp_onsend(void *rtp, const void *data, int bytes); + +/// RTP receive notify +/// @param[in] rtp RTP object +/// @param[in] data RTP packet(include RTP Header) +/// @param[in] bytes RTP packet size in byte +/// @return 1-ok, 0-rtp packet ok, seq disorder, <0-error +int rtp_onreceived(void *rtp, const void *data, int bytes); + +/// received RTCP packet +/// @param[in] rtp RTP object +/// @param[in] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-ok, <0-error +int rtp_onreceived_rtcp(void *rtp, const void *rtcp, int bytes); + +/// create RTCP Report(SR/RR) packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_report(void *rtp, void *rtcp, int bytes); + +/// create RTCP BYE packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_bye(void *rtp, void *rtcp, int bytes); + +/// create RTCP APP packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_app(void *rtp, void *rtcp, int bytes, const char name[4], const void *app, int len); + +/// create RTCP RTPFB packet +/// @param[in] rtp RTP object +/// @param[out] data RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @param[in] id FMT Values for RTPFB Payload Types +/// @param[in] rtpfb RTPFB info +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_rtpfb(void *rtp, void *data, int bytes, enum rtcp_rtpfb_type_t id, const rtcp_rtpfb_t *rtpfb); + +/// create RTCP PSFB packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @param[in] id FMT Values for PSFB Payload Types +/// @param[in] psfb PSFB info +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_psfb(void *rtp, void *data, int bytes, enum rtcp_psfb_type_t id, const rtcp_psfb_t *psfb); + +/// create RTCP XR packet +/// @param[in] rtp RTP object +/// @param[out] data RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @param[in] id FMT Values for XR Payload Types +/// @param[in] xr XR info +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_xr(void *rtp, void *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr); + +/// get RTCP interval +/// @param[in] rtp RTP object +/// 0-ok, <0-error +int rtp_rtcp_interval(void *rtp); + +const char *rtp_get_cname(void *rtp, uint32_t ssrc); +const char *rtp_get_name(void *rtp, uint32_t ssrc); +int rtp_set_info(void *rtp, const char *cname, const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* !_rtp_h_ */ diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c new file mode 100755 index 000000000..577b152eb --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c @@ -0,0 +1,330 @@ +// https://aomediacodec.github.io/av1-rtp-spec/ +// 7.1. Media Type Definition: video/av1 + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +// Timestamp: The RTP timestamp indicates the time when the input frame was sampled, at a clock rate of 90 kHz +#define KHz 90 // 90000Hz + +#define N_AV1_HEADER 1 + +#define OBU_SEQUENCE_HEADER 1 +#define OBU_TEMPORAL_DELIMITER 2 +#define OBU_FRAME_HEADER 3 +#define OBU_TILE_GROUP 4 +#define OBU_METADATA 5 +#define OBU_FRAME 6 +#define OBU_REDUNDANT_FRAME_HEADER 7 +#define OBU_TILE_LIST 8 + +#define AV1_AGGREGATION_HEADER_Z \ + 0x80 // set to 1 if the first OBU element is an OBU fragment that is a continuation of an OBU fragment from the + // previous packet, 0 otherwise. +#define AV1_AGGREGATION_HEADER_Y \ + 0x40 // set to 1 if the last OBU element is an OBU fragment that will continue in the next packet, 0 otherwise. +#define AV1_AGGREGATION_HEADER_N \ + 0x08 // set to 1 if the packet is the first packet of a coded video sequence, 0 otherwise. Note: if N equals 1 then + // Z must equal 0. + +struct rtp_encode_av1_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; + + uint8_t *ptr; + int offset; + + uint8_t aggregation; +}; + +static inline const uint8_t *leb128(const uint8_t *data, size_t bytes, uint64_t *size) +{ + size_t i; + for (*size = i = 0; i * 7 < 64 && i < bytes;) { + *size |= ((uint64_t)(data[i] & 0x7F)) << (i * 7); + if (0 == (data[i++] & 0x80)) + break; + } + return data + i; +} + +static inline uint8_t *leb128_write(int64_t size, uint8_t *data, size_t bytes) +{ + size_t i; + for (i = 0; i * 7 < 64 && i < bytes;) { + data[i] = (uint8_t)(size & 0x7F); + size >>= 7; + data[i++] |= size > 0 ? 0x80 : 0; + if (0 == size) + break; + } + return data + i; +} + +static void *rtp_av1_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_av1_pack_destroy(void *pack) +{ + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_av1_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_av1_pack_flush(struct rtp_encode_av1_t *packer, uint8_t aggregation) +{ + int r, n; + if (!packer->ptr || packer->offset <= RTP_FIXED_HEADER) + return 0; // nothing to send + + packer->ptr[RTP_FIXED_HEADER] = aggregation; + packer->pkt.payloadlen = packer->offset - RTP_FIXED_HEADER; + n = rtp_packet_serialize_header(&packer->pkt, packer->ptr, packer->size); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + packer->pkt.rtp.m = 0; // clear marker bit + packer->aggregation &= ~(AV1_AGGREGATION_HEADER_N | AV1_AGGREGATION_HEADER_Z); + + r = packer->handler.packet(packer->cbparam, packer->ptr, n + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, packer->ptr); + packer->offset = 0; + packer->ptr = NULL; + return r; +} + +static int rtp_av1_pack_obu(struct rtp_encode_av1_t *packer, const uint8_t *obu, int64_t bytes) +{ + int r; + int64_t n; + uint8_t *ptr, *end; + + while (bytes > 0) { + if (NULL == packer->ptr) { + packer->ptr = (uint8_t *)packer->handler.alloc(packer->cbparam, packer->size); + if (!packer->ptr) + return -ENOMEM; + packer->offset = RTP_FIXED_HEADER + 1; // RTP Header + AV1 aggregation header + } + + ptr = packer->ptr + packer->offset; + end = packer->ptr + packer->size; + + // OBU element size + assert(packer->size < 0x3FFF); // 14bits + if (ptr + bytes + ((bytes > 0x7F) ? 2 : 1) > end) + n = end - ptr - 2; + else + n = bytes; + + ptr = leb128_write(n, ptr, end - ptr); + memcpy(ptr, obu, (size_t)n); + ptr += n; + obu += n; + bytes -= n; + packer->offset = (int)(ptr - packer->ptr); + + if (packer->size - packer->offset < 8) { + r = rtp_av1_pack_flush(packer, packer->aggregation | (bytes > 0 ? AV1_AGGREGATION_HEADER_Y : 0)); + if (0 != r) + return r; + } + + if (bytes > 0) + packer->aggregation |= AV1_AGGREGATION_HEADER_Z; + } + + return 0; +} + +/// https://aomediacodec.github.io/av1-spec/av1-spec.pd +/// Annex B: Length delimited bitstream format +/// @param[in] data temporal_unit +/// @param[in] bytes temporal_unit_sizetemporal_unit_size +static int rtp_av1_pack_input_annexb(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + // uint8_t obu_has_size_field; + uint8_t obu_extension_flag; + uint8_t temporal_id, temporal_id0; + uint8_t spatial_id, spatial_id0; + uint8_t obu_type; + uint64_t obu_size, frame_size; + const uint8_t *ptr, *end, *frame_end, *obu_end; + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)pack; + packer->pkt.rtp.timestamp = timestamp; + packer->pkt.rtp.m = 0; + packer->ptr = NULL; // TODO: ptr memory leak + + temporal_id0 = spatial_id0 = 0; + ptr = (const uint8_t *)data; + end = ptr + bytes; + for (packer->aggregation = AV1_AGGREGATION_HEADER_N; ptr < end; ptr = frame_end) { + ptr = leb128(ptr, end - ptr, &frame_size); + frame_end = ptr + frame_size; + if (frame_end > end) { + assert(0); + return -1; + } + + for (; ptr < frame_end; ptr = obu_end) { + ptr = leb128(ptr, bytes, &obu_size); + obu_end = ptr + obu_size; + if (obu_end > frame_end) { + assert(0); + return -1; + } + + obu_type = (*ptr >> 3) & 0x0F; + obu_extension_flag = *ptr & 0x04; + // obu_has_size_field = *ptr & 0x02; + if (obu_extension_flag) { + temporal_id = (ptr[1] >> 5) & 0x07; + spatial_id = (ptr[1] >> 3) & 0x03; + + // If more than one OBU contained in an RTP packet has an OBU extension header + // then the values of the temporal_id and spatial_id must be the same in all such + // OBUs in the RTP packet. + if (temporal_id != temporal_id0 || spatial_id != spatial_id0) { + r = rtp_av1_pack_flush(packer, packer->aggregation); + if (0 != r) + return r; + + temporal_id0 = temporal_id; + spatial_id0 = spatial_id; + } + } + + // 5. Packetization rules + // The temporal delimiter OBU, if present, SHOULD be removed + // when transmitting, and MUST be ignored by receivers. + if (OBU_TEMPORAL_DELIMITER == obu_type) + continue; + + if (0 != rtp_av1_pack_obu(packer, ptr, obu_size)) + return -ENOMEM; + } + } + + // The RTP header Marker bit MUST be set equal to 0 if the packet is not the last + // packet of the temporal unit, it SHOULD be set equal to 1 otherwise. + // Note: It is possible for a receiver to receive the last packet of a temporal unit + // without the marker bit being set equal to 1, and a receiver should be able to handle + // this case. The last packet of a temporal unit is also indicated by the next packet, + // in RTP sequence number order, having an incremented timestamp. + packer->pkt.rtp.m = 1; + return rtp_av1_pack_flush(packer, packer->aggregation); +} + +/// http://aomedia.org/av1/specification/syntax/#general-obu-syntax +/// Low overhead bitstream format +static int rtp_av1_pack_input_obu(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + size_t i; + size_t offset; + uint64_t len; + uint8_t obu_type; + const uint8_t *ptr, *raw; + struct rtp_encode_av1_t *packer; + + packer = (struct rtp_encode_av1_t *)pack; + packer->pkt.rtp.timestamp = timestamp; + packer->pkt.rtp.m = 0; + packer->ptr = NULL; // TODO: ptr memory leak + packer->aggregation = 0; + + raw = (const uint8_t *)data; + for (i = r = 0; i < bytes && 0 == r; i += (size_t)len) { + // http://aomedia.org/av1/specification/syntax/#obu-header-syntax + obu_type = (raw[i] >> 3) & 0x0F; + if (raw[i] & 0x04) // obu_extension_flag + { + // http://aomedia.org/av1/specification/syntax/#obu-extension-header-syntax + // temporal_id = (obu[1] >> 5) & 0x07; + // spatial_id = (obu[1] >> 3) & 0x03; + offset = 2; + } else { + offset = 1; + } + + if (raw[i] & 0x02) // obu_has_size_field + { + ptr = leb128(raw + i + offset, (int)(bytes - i - offset), &len); + if (ptr + len > raw + bytes) + return -1; + len += ptr - raw - i; + } else { + len = bytes - i; + } + + // 5. Packetization rules + // The temporal delimiter OBU, if present, SHOULD be removed + // when transmitting, and MUST be ignored by receivers. + if (OBU_TEMPORAL_DELIMITER == obu_type) + continue; + + packer->aggregation |= OBU_SEQUENCE_HEADER == obu_type ? AV1_AGGREGATION_HEADER_N : 0; + r = rtp_av1_pack_obu(packer, raw + i, (size_t)len); + } + + // The RTP header Marker bit MUST be set equal to 0 if the packet is not the last + // packet of the temporal unit, it SHOULD be set equal to 1 otherwise. + // Note: It is possible for a receiver to receive the last packet of a temporal unit + // without the marker bit being set equal to 1, and a receiver should be able to handle + // this case. The last packet of a temporal unit is also indicated by the next packet, + // in RTP sequence number order, having an incremented timestamp. + packer->pkt.rtp.m = 1; + return rtp_av1_pack_flush(packer, packer->aggregation); +} + +struct rtp_payload_encode_t *rtp_av1_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_av1_pack_create, + rtp_av1_pack_destroy, + rtp_av1_pack_get_info, + rtp_av1_pack_input_obu, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c new file mode 100755 index 000000000..85dffe929 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c @@ -0,0 +1,377 @@ +// https://aomediacodec.github.io/av1-rtp-spec/#41-rtp-header-usage + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +#define N_MAX_OBU 255 +#define N_RESERVED_OBU_SIZE_FIELD 2 + +struct rtp_decode_av1_obu_t { + int off; + int len; +}; + +struct rtp_decode_av1_t { + struct rtp_payload_t handler; + void *cbparam; + + int lost; + uint16_t seq; // rtp seq + uint32_t timestamp; + + int flags; + + struct { + struct rtp_decode_av1_obu_t *arr; + int num, cap; + } obu; + + struct { + uint8_t *ptr; + int len, cap; + } ptr; +}; + +static inline const uint8_t *leb128(const uint8_t *data, size_t bytes, int64_t *size) +{ + size_t i; + for (*size = i = 0; i * 7 < 64 && i < bytes;) { + *size |= ((int64_t)(data[i] & 0x7F)) << (i * 7); + if (0 == (data[i++] & 0x80)) + break; + } + return data + i; +} + +// static inline uint8_t* leb128_write(int64_t size, uint8_t* data, size_t bytes) +//{ +// size_t i; +// assert(3 == bytes && size <= 0x1FFFFF); +// for (i = 0; i * 7 < 64 && i < bytes; i++) +// { +// data[i] = (uint8_t)(size & 0x7F); +// size >>= 7; +// data[i] |= (size > 0 || i + 1 < bytes)? 0x80 : 0; +// } +// return data + i; +// } + +static inline int leb128_write(int64_t size, uint8_t *data, int bytes) +{ + int i; + for (i = 0; i * 7 < 64 && i < bytes;) { + data[i] = (uint8_t)(size & 0x7F); + size >>= 7; + data[i++] |= size > 0 ? 0x80 : 0; + if (0 == size) + break; + } + return i; +} + +static void *rtp_av1_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_av1_t *unpacker; + unpacker = (struct rtp_decode_av1_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_av1_unpack_destroy(void *p) +{ + struct rtp_decode_av1_t *unpacker; + unpacker = (struct rtp_decode_av1_t *)p; + + if (unpacker->obu.arr) + free(unpacker->obu.arr); + if (unpacker->ptr.ptr) + free(unpacker->ptr.ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +static int rtp_av1_unpack_obu_append(struct rtp_decode_av1_t *unpacker, const uint8_t *data, int bytes, int start) +{ + void *p; + int size; + int64_t n; + uint8_t head; + const uint8_t *pend; + + pend = data + bytes; + size = unpacker->ptr.len + bytes + 9 /*obu_size*/ + 2 /*obu temporal delimiter*/; + if (size > RTP_PAYLOAD_MAX_SIZE || size < 0 || bytes < 2) + return -EINVAL; + + if (size >= unpacker->ptr.cap) { + size += size / 4 > 16000 ? size / 4 : 16000; + p = realloc(unpacker->ptr.ptr, size); + if (!p) { + // unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->lost = 1; + // unpacker->size = 0; + return -ENOMEM; + } + + unpacker->ptr.ptr = (uint8_t *)p; + unpacker->ptr.cap = size; + } + + if (unpacker->obu.num + start >= unpacker->obu.cap) { + if (unpacker->obu.cap >= N_MAX_OBU) + return -E2BIG; + p = realloc(unpacker->obu.arr, sizeof(struct rtp_decode_av1_obu_t) * (unpacker->obu.cap + 8)); + if (!p) + return -ENOMEM; + + memset((struct rtp_decode_av1_obu_t *)p + unpacker->obu.cap, 0, sizeof(struct rtp_decode_av1_obu_t) * 8); + unpacker->obu.arr = (struct rtp_decode_av1_obu_t *)p; + unpacker->obu.cap += 8; + } + + // add temporal delimiter obu + // if (0 == unpacker->ptr.len) + //{ + // static const uint8_t av1_temporal_delimiter[] = { 0x12, 0x00 }; + // assert(0 == unpacker->ptr.len); + // memcpy(unpacker->ptr.ptr, av1_temporal_delimiter, sizeof(av1_temporal_delimiter)); + // unpacker->ptr.len += sizeof(av1_temporal_delimiter); + //} + + if (start) { + unpacker->obu.arr[unpacker->obu.num].off = unpacker->ptr.len; + unpacker->obu.arr[unpacker->obu.num].len = 0; + + // obu_head + head = *data++; + unpacker->ptr.ptr[unpacker->ptr.len++] = head; + if (head & 0x04) { // obu_extension_flag + unpacker->ptr.ptr[unpacker->ptr.len++] = *data++; + } + + if (head & 0x02) { // obu_has_size_field + data = leb128(data, pend - data, &n); + unpacker->ptr.len += + leb128_write(n, unpacker->ptr.ptr + unpacker->ptr.len, unpacker->ptr.cap - unpacker->ptr.len); + } else { + unpacker->ptr.len += N_RESERVED_OBU_SIZE_FIELD; // obu_size + } + + unpacker->obu.num++; + } + + unpacker->obu.arr[unpacker->obu.num - 1].len += (int)(intptr_t)(pend - data); + + // obu + memcpy(unpacker->ptr.ptr + unpacker->ptr.len, data, pend - data); + unpacker->ptr.len += (int)(intptr_t)(pend - data); + return 0; +} + +int rtp_av1_unpack_onframe(struct rtp_decode_av1_t *unpacker) +{ + int i, j, r, n; + uint8_t *obu, *data, obu_size_field[9]; + int64_t len; + r = 0; + + if (unpacker->obu.num < unpacker->obu.cap && unpacker->obu.arr[0].len > 0 +#if !defined(RTP_ENABLE_COURRUPT_PACKET) + && 0 == unpacker->lost +#endif + ) { + // write obu length + for (i = 0; i < unpacker->obu.num; i++) { + obu = unpacker->ptr.ptr + unpacker->obu.arr[i].off; + data = obu + ((0x04 & obu[0]) ? 2 : 1); + if (0x02 & obu[0]) { // obu_has_size_field + assert(unpacker->lost || (leb128(data, 9, &len) && len == unpacker->obu.arr[i].len)); + continue; + } + + obu[0] |= 0x02; // obu_has_size_field + n = leb128_write(unpacker->obu.arr[i].len, obu_size_field, sizeof(obu_size_field)); + if (n != N_RESERVED_OBU_SIZE_FIELD) { + memmove(data + n, data + N_RESERVED_OBU_SIZE_FIELD, + unpacker->ptr.ptr + unpacker->ptr.len - (data + N_RESERVED_OBU_SIZE_FIELD)); + unpacker->ptr.len -= N_RESERVED_OBU_SIZE_FIELD - n; + for (j = i + 1; j < unpacker->obu.num; j++) { + assert(unpacker->obu.arr[j].off + n > N_RESERVED_OBU_SIZE_FIELD); + unpacker->obu.arr[j].off = unpacker->obu.arr[j].off + n - N_RESERVED_OBU_SIZE_FIELD; + } + } + memcpy(data, obu_size_field, n); + } + + // previous packet done + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr.ptr, unpacker->ptr.len, unpacker->timestamp, + unpacker->flags | (unpacker->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0)); + + // RTP_PAYLOAD_FLAG_PACKET_LOST: miss + unpacker->flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST; // clear packet lost flag + } + + // set packet lost flag on next frame + if (unpacker->lost) + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + + // new frame start + unpacker->lost = 0; + unpacker->ptr.len = 0; + unpacker->obu.num = 0; + memset(unpacker->obu.arr, 0, sizeof(struct rtp_decode_av1_obu_t) * unpacker->obu.cap); + return r; +} + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| 0x100 | 0x0 | extensions length | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| 0x1(ID) | hdr_length | | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | +| | +| dependency descriptor (hdr_length #octets) | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | Other rtp header extensions...| ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| AV1 aggr hdr | | ++-+-+-+-+-+-+-+-+ | +| | +| Bytes 2..N of AV1 payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_av1_unpack_input(void *p, const void *packet, int bytes) +{ + int lost; + int64_t size; + uint8_t z, y, w, n, i; + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_decode_av1_t *unpacker; + + unpacker = (struct rtp_decode_av1_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + lost = 0; + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + lost = 1; + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->ptr.len = 0; // discard previous packets + } + + // check timestamp + if (pkt.rtp.timestamp != unpacker->timestamp) { + rtp_av1_unpack_onframe(unpacker); + + // lost: + // 0 - packet lost before timestamp change + // 1 - packet lost on timestamp changed, can't known losted packet is at old packet tail or new packet start, so + // two packets mark as packet lost + if (0 != lost) + unpacker->lost = lost; + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + unpacker->timestamp = pkt.rtp.timestamp; + + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // AV1 aggregation header + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Z|Y| W |N|-|-|-| + +-+-+-+-+-+-+-+-+ + */ + z = ptr[0] & 0x80; // MUST be set to 1 if the first OBU element is an OBU fragment that is a continuation of an OBU + // fragment from the previous packet, and MUST be set to 0 otherwise. + y = ptr[0] & 0x40; // MUST be set to 1 if the last OBU element is an OBU fragment that will continue in the next + // packet, and MUST be set to 0 otherwise. + w = (ptr[0] & 0x30) >> 4; // two bit field that describes the number of OBU elements in the packet. This field MUST + // be set equal to 0 or equal to the number of OBU elements contained in the packet. If + // set to 0, each OBU element MUST be preceded by a length field. + n = ptr[0] & 0x08; // MUST be set to 1 if the packet is the first packet of a coded video sequence, and MUST be set + // to 0 otherwise. + (void)y; + + // if N equals 1 then Z must equal 0. + assert(!n || 0 == z); + if (0 == z && 1 == n) { + // new video codec sequence + rtp_av1_unpack_onframe(unpacker); + } + + for (i = 1, ptr++; ptr < pend; ptr += size, i++) { + if (i < w || 0 == w) { + ptr = leb128(ptr, pend - ptr, &size); + } else { + size = pend - ptr; + } + + // skip fragment frame OBU size + if (ptr + size > pend) { + // assert(0); + // unpacker->size = 0; + unpacker->lost = 1; + // unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + rtp_av1_unpack_obu_append(unpacker, ptr, (int)size, (1 != i || 0 == z) ? 1 : 0); + } + + // The RTP header Marker bit MUST be set equal to 0 + // if the packet is not the last packet of the temporal unit, + // it SHOULD be set equal to 1 otherwise. + if (pkt.rtp.m) { + rtp_av1_unpack_onframe(unpacker); + } + + return 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_av1_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_av1_unpack_create, + rtp_av1_unpack_destroy, + rtp_av1_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c b/src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c new file mode 100755 index 000000000..2c742db80 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#define RTP_H2645_BITSTREAM_FORMAT_DETECT 1 + +static const uint8_t *h264_startcode(const uint8_t *data, int bytes) +{ + int i; + for (i = 2; i + 1 < bytes; i++) { + if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2]) + return data + i + 1; + } + + return NULL; +} + +/// @return >0-ok, <=0-error +static inline int h264_avcc_length(const uint8_t *h264, int bytes, int avcc) +{ + int i; + uint32_t n; + + n = 0; + assert(3 <= avcc && avcc <= 4); + for (i = 0; i < avcc && i < bytes; i++) + n = (n << 8) | h264[i]; + return avcc >= bytes ? -1 : (int)n; +} + +/// @return 1-true, 0-false +static int h264_avcc_bitstream_valid(const uint8_t *h264, int bytes, int avcc) +{ + int n; + + while (avcc + 1 < bytes) { + n = h264_avcc_length(h264, bytes, avcc); + if (n < 0 || n + avcc > bytes) + return 0; // invalid + + h264 += n + avcc; + bytes -= n + avcc; + } + + return 0 == bytes ? 1 : 0; +} + +/// @return 0-annexb, >0-avcc, <0-error +static int h264_bitstream_format(const uint8_t *h264, int bytes) +{ + uint32_t n; + if (bytes < 4) + return -1; + + n = ((uint32_t)h264[0]) << 16 | ((uint32_t)h264[1]) << 8 | ((uint32_t)h264[2]); + if (0 == n && h264[3] <= 1) { + return 0; // annexb + } else if (1 == n) { + // try avcc & annexb + return h264_avcc_bitstream_valid(h264, bytes, 4) ? 4 : 0; + } else { + // try avcc 4/3 bytes + return h264_avcc_bitstream_valid(h264, bytes, 4) ? 4 : (h264_avcc_bitstream_valid(h264, bytes, 3) ? 3 : -1); + } +} + +static int h264_avcc_nalu(const void *h264, int bytes, int avcc, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param) +{ + int r; + uint32_t n; + const uint8_t *p, *end; + + r = 0; + p = (const uint8_t *)h264; + end = (const uint8_t *)h264 + bytes; + for (n = h264_avcc_length(p, (int)(end - p), avcc); 0 == r && p + n + avcc <= end; + n = h264_avcc_length(p, (int)(end - p), avcc)) { + assert(n > 0); + if (n > 0) { + r = handler(param, p + avcc, (int)n, p + avcc + n < end ? 0 : 1); + } + + p += n + avcc; + } + + return r; +} + +///@param[in] h264 H.264 byte stream format data(A set of NAL units) +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param) +{ + int r; + ptrdiff_t n; + const uint8_t *p, *next, *end; + +#if defined(RTP_H2645_BITSTREAM_FORMAT_DETECT) + int avcc; + avcc = h264_bitstream_format(h264, bytes); + if (avcc > 0) + return h264_avcc_nalu(h264, bytes, avcc, handler, param); +#endif + + end = (const uint8_t *)h264 + bytes; + p = h264_startcode((const uint8_t *)h264, bytes); + + r = 0; + while (p && 0 == r) { + next = h264_startcode(p, (int)(end - p)); + if (next) { + n = next - p - 3; + } else { + n = end - p; + } + + while (n > 0 && 0 == p[n - 1]) + n--; // filter tailing zero + + assert(n > 0); + if (n > 0) { + r = handler(param, p, (int)n, next ? 0 : 1); + } + + p = next; + } + + return r; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c new file mode 100755 index 000000000..d8da7e315 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c @@ -0,0 +1,194 @@ +// RFC6184 RTP Payload Format for H.264 Video +// +// 6.2. Single NAL Unit Mode (All receivers MUST support this mode) +// packetization-mode media type parameter is equal to 0 or the packetization - mode is not present. +// Only single NAL unit packets MAY be used in this mode. +// STAPs, MTAPs, and FUs MUST NOT be used. +// The transmission order of single NAL unit packets MUST comply with the NAL unit decoding order. +// 6.3. Non-Interleaved Mode (This mode SHOULD be supported) +// packetization-mode media type parameter is equal to 1. +// Only single NAL unit packets, STAP - As, and FU - As MAY be used in this mode. +// STAP-Bs, MTAPs, and FU-Bs MUST NOT be used. +// The transmission order of NAL units MUST comply with the NAL unit decoding order +// 6.4. Interleaved Mode +// packetization-mode media type parameter is equal to 2. +// STAP-Bs, MTAPs, FU-As, and FU-Bs MAY be used. +// STAP-As and single NAL unit packets MUST NOT be used. +// The transmission order of packets and NAL units is constrained as specified in Section 5.5. +// +// 5.1. RTP Header Usage (p10) +// The RTP timestamp is set to the sampling timestamp of the content. A 90 kHz clock rate MUST be used. + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz +#define FU_START 0x80 +#define FU_END 0x40 + +#define N_FU_HEADER 2 + +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param); + +struct rtp_encode_h264_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_h264_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_h264_pack_destroy(void *pack) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_h264_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_h264_pack_nalu(struct rtp_encode_h264_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + uint8_t *rtp; + + packer->pkt.payload = nalu; + packer->pkt.payloadlen = bytes; + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // packer->pkt.rtp.m = 1; // set marker flag + packer->pkt.rtp.m = (*nalu & 0x1f) <= 5 ? mark : 0; // VCL only + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + return r; +} + +static int rtp_h264_pack_fu_a(struct rtp_encode_h264_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + unsigned char *rtp; + + // RFC6184 5.3. NAL Unit Header Usage: Table 2 (p15) + // RFC6184 5.8. Fragmentation Units (FUs) (p29) + uint8_t fu_indicator = (*nalu & 0xE0) | 28; // FU-A + uint8_t fu_header = *nalu & 0x1F; + + r = 0; + nalu += 1; // skip NAL Unit Type byte + bytes -= 1; + assert(bytes > 0); + + // FU-A start + for (fu_header |= FU_START; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + if (bytes + RTP_FIXED_HEADER <= packer->size - N_FU_HEADER) { + assert(0 == (fu_header & FU_START)); + fu_header = FU_END | (fu_header & 0x1F); // FU-A end + packer->pkt.payloadlen = bytes; + } else { + packer->pkt.payloadlen = packer->size - RTP_FIXED_HEADER - N_FU_HEADER; + } + + packer->pkt.payload = nalu; + n = RTP_FIXED_HEADER + N_FU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (FU_END & fu_header) ? mark : 0; // set marker flag + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /*fu_indicator + fu_header*/ + rtp[n + 0] = fu_indicator; + rtp[n + 1] = fu_header; + memcpy(rtp + n + N_FU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + + r = packer->handler.packet(packer->cbparam, rtp, n + N_FU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + bytes -= packer->pkt.payloadlen; + nalu += packer->pkt.payloadlen; + fu_header &= 0x1F; // clear flags + } + + return r; +} + +static int rtp_h264_pack_handler(void *pack, const uint8_t *nalu, int bytes, int last) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; + if (bytes + RTP_FIXED_HEADER <= packer->size) { + // single NAl unit packet + return rtp_h264_pack_nalu(packer, nalu, bytes, last ? 1 : 0); + } else { + return rtp_h264_pack_fu_a(packer, nalu, bytes, last ? 1 : 0); + } +} + +static int rtp_h264_pack_input(void *pack, const void *h264, int bytes, uint32_t timestamp) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; + // assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)time * KHz; // ms -> 90KHZ + return rtp_h264_annexb_nalu(h264, bytes, rtp_h264_pack_handler, packer); +} + +struct rtp_payload_encode_t *rtp_h264_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_h264_pack_create, + rtp_h264_pack_destroy, + rtp_h264_pack_get_info, + rtp_h264_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c new file mode 100755 index 000000000..1dd7ce18f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c @@ -0,0 +1,329 @@ +// RFC6184 RTP Payload Format for H.264 Video + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define H264_NAL(v) ((v)&0x1F) +#define FU_START(v) ((v)&0x80) +#define FU_END(v) ((v)&0x40) +#define FU_NAL(v) ((v)&0x1F) + +struct rtp_decode_h264_t { + struct rtp_payload_t handler; + void *cbparam; + + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity; + + int flags; +}; + +static void *rtp_h264_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_h264_t *unpacker; + unpacker = (struct rtp_decode_h264_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_h264_unpack_destroy(void *p) +{ + struct rtp_decode_h264_t *unpacker; + unpacker = (struct rtp_decode_h264_t *)p; + + if (unpacker->ptr) + free(unpacker->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +// 5.7.1. Single-Time Aggregation Packet (STAP) (p23) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|STAP-B NAL HDR | DON | NALU 1 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | NALU 1 Data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +: : ++ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 Size | NALU 2 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 Data | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h264_unpack_stap(struct rtp_decode_h264_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp, + int stap_b) +{ + int r, n; + uint16_t len; + uint16_t don; + + r = 0; + n = stap_b ? 3 : 1; + if (bytes < n) { + assert(0); + return -EINVAL; // error + } + don = stap_b ? nbo_r16(ptr + 1) : 0; + ptr += n; // STAP-A / STAP-B HDR + DON + + for (bytes -= n; 0 == r && bytes > 2; bytes -= len + 2) { + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 2) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + assert(H264_NAL(ptr[2]) > 0 && H264_NAL(ptr[2]) < 24); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2, len, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + don = (don + 1) % 65536; + } + + return 0 == r ? 1 : r; // packet handled +} + +// 5.7.2. Multi-Time Aggregation Packets (MTAPs) (p27) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|MTAP16 NAL HDR | decoding order number base | NALU 1 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 DOND | NALU 1 TS offset | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 HDR | NALU 1 DATA | ++-+-+-+-+-+-+-+-+ + +: : ++ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 SIZE | NALU 2 DOND | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 TS offset | NALU 2 HDR | NALU 2 DATA | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h264_unpack_mtap(struct rtp_decode_h264_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp, + int n) +{ + int r; + // uint16_t dond; + uint16_t donb; + uint16_t len; + uint32_t ts; + + r = 0; + if (bytes < 3) { + assert(0); + return -EINVAL; // error + } + + donb = nbo_r16(ptr + 1); + ptr += 3; // MTAP16/MTAP24 HDR + DONB + + for (bytes -= 3; 0 == r && n + 3 < bytes; bytes -= len + 2) { + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 1 /*DOND*/ + n /*TS offset*/ + 1 /*NALU*/) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + // dond = (ptr[2] + donb) % 65536; + ts = (uint16_t)nbo_r16(ptr + 3); + if (3 == n) + ts = (ts << 8) | ptr[5]; // MTAP24 + + // if the NALU-time is larger than or equal to the RTP timestamp of the packet, + // then the timestamp offset equals (the NALU - time of the NAL unit - the RTP timestamp of the packet). + // If the NALU - time is smaller than the RTP timestamp of the packet, + // then the timestamp offset is equal to the NALU - time + (2 ^ 32 - the RTP timestamp of the packet). + ts += timestamp; // wrap 1 << 32 + + assert(H264_NAL(ptr[n + 3]) > 0 && H264_NAL(ptr[n + 3]) < 24); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2 + 1 + n, len - 1 - n, ts, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + } + + return 0 == r ? 1 : r; // packet handled +} + +// 5.8. Fragmentation Units (FUs) (p29) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| FU indicator | FU header | DON | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h264_unpack_fu(struct rtp_decode_h264_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp, + int fu_b) +{ + int r, n; + uint8_t fuheader; + // uint16_t don; + + r = 0; + n = fu_b ? 4 : 2; + if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { + assert(0); + return -EINVAL; // error + } + + if (unpacker->size + bytes - n + 1 /*NALU*/ > unpacker->capacity) { + void *p = NULL; + int size = unpacker->size + bytes + 1; + size += size / 4 > 128000 ? size / 4 : 128000; + p = realloc(unpacker->ptr, size); + if (!p) { + // set packet lost flag + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -ENOMEM; // error + } + unpacker->ptr = (uint8_t *)p; + unpacker->capacity = size; + } + + fuheader = ptr[1]; + // don = nbo_r16(ptr + 2); + if (FU_START(fuheader)) { +#if 0 + if (unpacker->size > 0) + { + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; + unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } +#endif + + unpacker->size = 1; // NAL unit type byte + unpacker->ptr[0] = (ptr[0] /*indicator*/ & 0xE0) | (fuheader & 0x1F); + assert(H264_NAL(unpacker->ptr[0]) > 0 && H264_NAL(unpacker->ptr[0]) < 24); + } else { + if (0 == unpacker->size) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + return 0; // packet discard + } + assert(unpacker->size > 0); + } + + unpacker->timestamp = timestamp; + if (bytes > n) { + assert(unpacker->capacity >= unpacker->size + bytes - n); + memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); + unpacker->size += bytes - n; + } + + if (FU_END(fuheader)) { + if (unpacker->size > 0) + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } + + return 0 == r ? 1 : r; // packet handled +} + +static int rtp_h264_unpack_input(void *p, const void *packet, int bytes) +{ + int r; + uint8_t nalt; + struct rtp_packet_t pkt; + struct rtp_decode_h264_t *unpacker; + + unpacker = (struct rtp_decode_h264_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; // discard previous packets + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + + nalt = ((unsigned char *)pkt.payload)[0]; + switch (nalt & 0x1F) { + case 0: // reserved + case 31: // reserved + assert(0); + return 0; // packet discard + + case 24: // STAP-A + return rtp_h264_unpack_stap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); + case 25: // STAP-B + return rtp_h264_unpack_stap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1); + case 26: // MTAP16 + return rtp_h264_unpack_mtap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 2); + case 27: // MTAP24 + return rtp_h264_unpack_mtap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 3); + case 28: // FU-A + return rtp_h264_unpack_fu(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); + case 29: // FU-B + return rtp_h264_unpack_fu(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1); + + default: // 1-23 NAL unit + r = unpacker->handler.packet(unpacker->cbparam, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, + unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + return 0 == r ? 1 : r; // packet handled + } +} + +struct rtp_payload_decode_t *rtp_h264_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_h264_unpack_create, + rtp_h264_unpack_destroy, + rtp_h264_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c new file mode 100755 index 000000000..bf512e69f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c @@ -0,0 +1,187 @@ +// RFC7798 RTP Payload Format for High Efficiency Video Coding (HEVC) +// +// 4.1. RTP Header Usage (p20) +// The RTP timestamp is set to the sampling timestamp of the content. A 90 kHz clock rate MUST be used. + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz +#define FU_START 0x80 +#define FU_END 0x40 + +#define H265_RTP_FU 49 + +#define N_FU_HEADER 3 + +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param); + +struct rtp_encode_h265_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_h265_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *param) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = param; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_h265_pack_destroy(void *pack) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_h265_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_h265_pack_nalu(struct rtp_encode_h265_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + uint8_t *rtp; + + if (bytes < 3) + return -1; + + packer->pkt.payload = nalu; + packer->pkt.payloadlen = bytes; + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // packer->pkt.rtp.m = 1; // set marker flag + packer->pkt.rtp.m = ((*nalu >> 1) & 0x3f) < 32 ? mark : 0; // VCL only + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + return r; +} + +static int rtp_h265_pack_fu(struct rtp_encode_h265_t *packer, const uint8_t *ptr, int bytes, int mark) +{ + int r, n; + unsigned char *rtp; + uint8_t fu_header; + uint16_t nalu_header; + + if (bytes < 3) + return -1; + + nalu_header = ((uint16_t)((ptr[0] & 0x81) | (H265_RTP_FU << 1)) << 8) | ptr[1]; // replace nalu type with 49(FU) + fu_header = (ptr[0] >> 1) & 0x3F; + + r = 0; + ptr += 2; // skip NAL Unit Type byte + bytes -= 2; + assert(bytes > 0); + + // FU-A start + for (fu_header |= FU_START; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + if (bytes + RTP_FIXED_HEADER <= packer->size - N_FU_HEADER) { + assert(0 == (fu_header & FU_START)); + fu_header = FU_END | (fu_header & 0x3F); // FU end + packer->pkt.payloadlen = bytes; + } else { + packer->pkt.payloadlen = packer->size - RTP_FIXED_HEADER - N_FU_HEADER; + } + + packer->pkt.payload = ptr; + n = RTP_FIXED_HEADER + N_FU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (FU_END & fu_header) ? mark : 0; // set marker flag + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /*header + fu_header*/ + rtp[n + 0] = (uint8_t)(nalu_header >> 8); + rtp[n + 1] = (uint8_t)(nalu_header & 0xFF); + rtp[n + 2] = fu_header; + memcpy(rtp + n + N_FU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + + r = packer->handler.packet(packer->cbparam, rtp, n + N_FU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + bytes -= packer->pkt.payloadlen; + ptr += packer->pkt.payloadlen; + fu_header &= 0x3F; // clear flags + } + + return r; +} + +static int rtp_h265_pack_handler(void *pack, const uint8_t *nalu, int bytes, int last) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; + if (bytes + RTP_FIXED_HEADER <= packer->size) { + // single NAl unit packet + return rtp_h265_pack_nalu(packer, nalu, bytes, last ? 1 : 0); + } else { + return rtp_h265_pack_fu(packer, nalu, bytes, last ? 1 : 0); + } +} + +static int rtp_h265_pack_input(void *pack, const void *h265, int bytes, uint32_t timestamp) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; + // assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)time * KHz; // ms -> 90KHZ + return rtp_h264_annexb_nalu(h265, bytes, rtp_h265_pack_handler, packer); +} + +struct rtp_payload_encode_t *rtp_h265_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_h265_pack_create, + rtp_h265_pack_destroy, + rtp_h265_pack_get_info, + rtp_h265_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c new file mode 100755 index 000000000..835753f0e --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c @@ -0,0 +1,280 @@ +// RFC7798 RTP Payload Format for High Efficiency Video Coding (HEVC) + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +/* +0 1 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|F| Type | LayerId | TID | ++-------------+-----------------+ + +Forbidden zero(F) : 1 bit +NAL unit type(Type) : 6 bits +NUH layer ID(LayerId) : 6 bits +NUH temporal ID plus 1 (TID) : 3 bits +*/ + +#define H265_TYPE(v) (((v) >> 1) & 0x3f) + +#define FU_START(v) ((v)&0x80) +#define FU_END(v) ((v)&0x40) +#define FU_NAL(v) ((v)&0x3F) + +struct rtp_decode_h265_t { + struct rtp_payload_t handler; + void *cbparam; + + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity; + + int flags; + int using_donl_field; +}; + +static void *rtp_h265_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_h265_t *unpacker; + unpacker = (struct rtp_decode_h265_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_h265_unpack_destroy(void *p) +{ + struct rtp_decode_h265_t *unpacker; + unpacker = (struct rtp_decode_h265_t *)p; + + if (unpacker->ptr) + free(unpacker->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +// 4.4.2. Aggregation Packets (APs) (p25) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=48) | NALU 1 DONL | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| NALU 1 Data . . . | +| | ++ . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 DOND | NALU 2 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 HDR | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 2 Data | +| | +| . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h265_unpack_ap(struct rtp_decode_h265_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r; + int n; + int len; + // uint16_t donl; + // uint16_t dond; + + // donl = unpacker->using_donl_field ? nbo_r16(ptr + 2) : 0; + ptr += 2; // PayloadHdr + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 2 : 0); + r = 0; + + for (bytes -= 2 /*PayloadHdr*/; 0 == r && bytes > n; bytes -= len + 2) { + bytes -= n - 2; // skip DON + ptr += n - 2; // skip DON + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 3) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + assert(H265_TYPE(ptr[2]) >= 0 && H265_TYPE(ptr[2]) < 48); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2, len, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 1 : 0); + } + + return 0 == r ? 1 : r; // packet handled +} + +// 4.4.3. Fragmentation Units (p29) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=49) | FU header | DONL (cond) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| DONL (cond) | | +|-+-+-+-+-+-+-+-+ | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E| FuType | ++---------------+ +*/ +static int rtp_h265_unpack_fu(struct rtp_decode_h265_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t fuheader; + + r = 0; + n = 1 /*FU header*/ + (unpacker->using_donl_field ? 4 : 2); + if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { + assert(0); + return -EINVAL; + } + + if (unpacker->size + bytes - n + 2 /*NALU*/ > unpacker->capacity) { + void *p = NULL; + int size = unpacker->size + bytes + 2; + size += size / 4 > 128000 ? size / 4 : 128000; + p = realloc(unpacker->ptr, size); + if (!p) { + // set packet lost flag + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -ENOMEM; + } + unpacker->ptr = (uint8_t *)p; + unpacker->capacity = size; + } + + fuheader = ptr[2]; + if (FU_START(fuheader)) { +#if 0 + if (unpacker->size > 0) + { + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; + unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } +#endif + + assert(unpacker->capacity > 2); + unpacker->size = 2; // NAL unit type byte + unpacker->ptr[0] = (FU_NAL(fuheader) << 1) | (ptr[0] & 0x81); // replace NAL Unit Type Bits + unpacker->ptr[1] = ptr[1]; + assert(H265_TYPE(unpacker->ptr[0]) >= 0 && H265_TYPE(unpacker->ptr[0]) <= 63); + } else { + if (0 == unpacker->size) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + return 0; // packet discard + } + assert(unpacker->size > 0); + } + + unpacker->timestamp = timestamp; + if (bytes > n) { + assert(unpacker->capacity >= unpacker->size + bytes - n); + memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); + unpacker->size += bytes - n; + } + + if (FU_END(fuheader)) { + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + } + + return 0 == r ? 1 : r; // packet handled +} + +static int rtp_h265_unpack_input(void *p, const void *packet, int bytes) +{ + int r, nal; + const uint8_t *ptr; + struct rtp_packet_t pkt; + struct rtp_decode_h265_t *unpacker; + + unpacker = (struct rtp_decode_h265_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || + pkt.payloadlen < (unpacker->using_donl_field ? 5 : 3)) + return -EINVAL; + + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; // discard previous packets + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + + assert(pkt.payloadlen > 2); + ptr = (const uint8_t *)pkt.payload; + nal = H265_TYPE(ptr[0]); + + if (nal > 50) + return 0; // packet discard, Unsupported (HEVC) NAL type + + switch (nal) { + case 48: // aggregated packet (AP) - with two or more NAL units + return rtp_h265_unpack_ap(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + case 49: // fragmentation unit (FU) + return rtp_h265_unpack_fu(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + case 50: // TODO: 4.4.4. PACI Packets (p32) + assert(0); + return 0; // packet discard + + case 32: // video parameter set (VPS) + case 33: // sequence parameter set (SPS) + case 34: // picture parameter set (PPS) + case 39: // supplemental enhancement information (SEI) + default: // 4.4.1. Single NAL Unit Packets (p24) + r = unpacker->handler.packet(unpacker->cbparam, ptr, pkt.payloadlen, pkt.rtp.timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + return 0 == r ? 1 : r; // packet handled + } +} + +struct rtp_payload_decode_t *rtp_h265_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_h265_unpack_create, + rtp_h265_unpack_destroy, + rtp_h265_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c new file mode 100755 index 000000000..a5c20608b --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c @@ -0,0 +1,189 @@ +// https://www.ietf.org/archive/id/draft-ietf-avtcore-rtp-vvc-18.html +// +// 4.1. RTP Header Usage (p20) +// The RTP timestamp is set to the sampling timestamp of the content. A 90 kHz clock rate MUST be used. + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz +#define FU_START 0x80 +#define FU_END 0x40 +#define FU_MARK 0x20 + +#define H266_RTP_AP 28 +#define H266_RTP_FU 29 + +#define H266_TYPE(v) (((v) >> 3) & 0x1f) +#define H266_NAL_OPI 12 + +#define N_FU_HEADER 3 + +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param); + +struct rtp_encode_h266_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_h266_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *param) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = param; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_h266_pack_destroy(void *pack) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_h266_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_h266_pack_nalu(struct rtp_encode_h266_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + uint8_t *rtp; + + packer->pkt.payload = nalu; + packer->pkt.payloadlen = bytes; + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // packer->pkt.rtp.m = 1; // set marker flag + packer->pkt.rtp.m = H266_TYPE(nalu[1]) < H266_NAL_OPI ? mark : 0; // VCL only + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + return r; +} + +static int rtp_h266_pack_fu(struct rtp_encode_h266_t *packer, const uint8_t *ptr, int bytes, int mark) +{ + int r, n; + unsigned char *rtp; + uint8_t fu_header; + uint16_t nalu_header; + + if (bytes < 3) + return -1; + + nalu_header = ((uint16_t)ptr[0] << 8) | ((ptr[1] & 0x07) | (H266_RTP_FU << 3)); // replace nalu type with 29(FU) + fu_header = H266_TYPE(ptr[1]); + + r = 0; + ptr += 2; // skip NAL Unit Type byte + bytes -= 2; + assert(bytes > 0); + + // FU-A start + for (fu_header |= FU_START; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + if (bytes + RTP_FIXED_HEADER <= packer->size - N_FU_HEADER) { + assert(0 == (fu_header & FU_START)); + fu_header = FU_END | (mark ? FU_MARK : 0) | (fu_header & 0x1F); // FU end + packer->pkt.payloadlen = bytes; + } else { + packer->pkt.payloadlen = packer->size - RTP_FIXED_HEADER - N_FU_HEADER; + } + + packer->pkt.payload = ptr; + n = RTP_FIXED_HEADER + N_FU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (FU_END & fu_header) ? mark : 0; // set marker flag + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /*header + fu_header*/ + rtp[n + 0] = (uint8_t)(nalu_header >> 8); + rtp[n + 1] = (uint8_t)(nalu_header & 0xFF); + rtp[n + 2] = fu_header; + memcpy(rtp + n + N_FU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + + r = packer->handler.packet(packer->cbparam, rtp, n + N_FU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + bytes -= packer->pkt.payloadlen; + ptr += packer->pkt.payloadlen; + fu_header &= 0x1F; // clear flags + } + + return r; +} + +static int rtp_h266_pack_handler(void *pack, const uint8_t *nalu, int bytes, int last) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; + if (bytes + RTP_FIXED_HEADER <= packer->size) { + // single NAl unit packet + return rtp_h266_pack_nalu(packer, nalu, bytes, last ? 1 : 0); + } else { + return rtp_h266_pack_fu(packer, nalu, bytes, last ? 1 : 0); + } +} + +static int rtp_h266_pack_input(void *pack, const void *h266, int bytes, uint32_t timestamp) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; + // assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)time * KHz; // ms -> 90KHZ + return rtp_h264_annexb_nalu(h266, bytes, rtp_h266_pack_handler, packer); +} + +struct rtp_payload_encode_t *rtp_h266_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_h266_pack_create, + rtp_h266_pack_destroy, + rtp_h266_pack_get_info, + rtp_h266_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c new file mode 100755 index 000000000..144e440e0 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c @@ -0,0 +1,279 @@ +// https://www.rfc-editor.org/rfc/rfc9328.html + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define H266_RTP_AP 28 +#define H266_RTP_FU 29 + +/* ++---------------+---------------+ +|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|F|Z| LayerID | Type | TID | ++---------------+---------------+ +*/ + +#define H266_TYPE(v) (((v) >> 3) & 0x1f) + +/* ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E|P| FuType | ++---------------+ +*/ +#define FU_START(v) ((v)&0x80) +#define FU_END(v) ((v)&0x40) +#define FU_MARK(v) ((v)&0x20) +#define FU_NAL(v) ((v)&0x1F) + +struct rtp_decode_h266_t { + struct rtp_payload_t handler; + void *cbparam; + + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity; + + int flags; + int using_donl_field; +}; + +static void *rtp_h266_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_h266_t *unpacker; + unpacker = (struct rtp_decode_h266_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_h266_unpack_destroy(void *p) +{ + struct rtp_decode_h266_t *unpacker; + unpacker = (struct rtp_decode_h266_t *)p; + + if (unpacker->ptr) + free(unpacker->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +// 4.3.2. Aggregation Packets (APs) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=28) | NALU 1 DONL | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| NALU 1 Data . . . | +| | ++ . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 DOND | NALU 2 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 HDR | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 2 Data | +| | +| . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h266_unpack_ap(struct rtp_decode_h266_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r; + int n; + int len; + // uint16_t donl; + // uint16_t dond; + + // donl = unpacker->using_donl_field ? nbo_r16(ptr + 2) : 0; + ptr += 2; // PayloadHdr + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 2 : 0); + r = 0; + + for (bytes -= 2 /*PayloadHdr*/; 0 == r && bytes > n; bytes -= len + 2) { + bytes -= n - 2; // skip DON + ptr += n - 2; // skip DON + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 3) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + assert(H266_TYPE(ptr[3]) >= 0 && H266_TYPE(ptr[3]) < 31); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2, len, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 1 : 0); + } + + return 0 == r ? 1 : r; // packet handled +} + +// 4.3.3. Fragmentation Units +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=29) | FU header | DONL (cond) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| DONL (cond) | | +|-+-+-+-+-+-+-+-+ | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E| FuType | ++---------------+ +*/ +static int rtp_h266_unpack_fu(struct rtp_decode_h266_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t fuheader; + + r = 0; + n = 1 /*FU header*/ + (unpacker->using_donl_field ? 4 : 2); + if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { + assert(0); + return -EINVAL; + } + + if (unpacker->size + bytes - n + 2 /*NALU*/ > unpacker->capacity) { + void *p = NULL; + int size = unpacker->size + bytes + 2; + size += size / 4 > 128000 ? size / 4 : 128000; + p = realloc(unpacker->ptr, size); + if (!p) { + // set packet lost flag + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -ENOMEM; + } + unpacker->ptr = (uint8_t *)p; + unpacker->capacity = size; + } + + fuheader = ptr[2]; + if (FU_START(fuheader)) { +#if 0 + if (unpacker->size > 0) + { + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; + unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } +#endif + + assert(unpacker->capacity > 2); + unpacker->size = 2; // NAL unit type byte + unpacker->ptr[0] = ptr[0]; + unpacker->ptr[1] = (FU_NAL(fuheader) << 3) | (ptr[1] & 0x07); // replace NAL Unit Type Bits + assert(H266_TYPE(unpacker->ptr[1]) >= 0 && H266_TYPE(unpacker->ptr[1]) <= 31); + } else { + if (0 == unpacker->size) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + return 0; // packet discard + } + assert(unpacker->size > 0); + } + + unpacker->timestamp = timestamp; + if (bytes > n) { + assert(unpacker->capacity >= unpacker->size + bytes - n); + memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); + unpacker->size += bytes - n; + } + + if (FU_END(fuheader)) // FU_MARK(fuheader) ?? + { + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + } + + return 0 == r ? 1 : r; // packet handled +} + +static int rtp_h266_unpack_input(void *p, const void *packet, int bytes) +{ + int r, nal; + const uint8_t *ptr; + struct rtp_packet_t pkt; + struct rtp_decode_h266_t *unpacker; + + unpacker = (struct rtp_decode_h266_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || + pkt.payloadlen < (unpacker->using_donl_field ? 5 : 3)) + return -EINVAL; + + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; // discard previous packets + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + + assert(pkt.payloadlen > 2); + ptr = (const uint8_t *)pkt.payload; + nal = H266_TYPE(ptr[1]); + + if (nal > 31) + return 0; // packet discard, Unsupported (VVC) NAL type + + switch (nal) { + case H266_RTP_AP: // aggregated packet (AP) - with two or more NAL units + return rtp_h266_unpack_ap(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + case H266_RTP_FU: // fragmentation unit (FU) + return rtp_h266_unpack_fu(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + default: // 4.3.1. Single NAL Unit Packets + r = unpacker->handler.packet(unpacker->cbparam, ptr, pkt.payloadlen, pkt.rtp.timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + return 0 == r ? 1 : r; // packet handled + } +} + +struct rtp_payload_decode_t *rtp_h266_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_h266_unpack_create, + rtp_h266_unpack_destroy, + rtp_h266_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c new file mode 100755 index 000000000..62e445d88 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c @@ -0,0 +1,137 @@ +/// RFC3016 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// +/// MPEG-4 Audio streams MUST be formatted LATM (Lowoverhead +/// MPEG-4 Audio Transport Multiplex)[14496 - 3] streams, and the +/// LATM-based streams are then mapped onto RTP packets + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +struct rtp_encode_mp4a_latm_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_mp4a_latm_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_mp4a_latm_pack_destroy(void *pack) +{ + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mp4a_latm_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_mp4a_latm_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + int n, len; + uint8_t *rtp; + uint8_t hd[40]; // 10KB + const uint8_t *ptr; + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)pack; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); // ms -> 90KHZ (RFC2250 section2 p2) + + r = 0; + ptr = (const uint8_t *)data; +#if defined(RTP_MP4A_LATM_SKIP_ADTS) + if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7) { + // skip ADTS header + assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07))); + ptr += 7; + bytes -= 7; + } +#endif + + // ISO/IEC 14496-3:200X(E) + // Table 1.44 - Syntax of PayloadLengthInfo() (p84) + len = bytes / 255 + 1; + if (len > sizeof(hd)) { + assert(0); + return -E2BIG; // invalid packet + } + memset(hd, 255, len - 1); + hd[len - 1] = bytes % 255; + + for (; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = + (bytes + len + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - len - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + len + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker (M) bit: The marker bit indicates audioMuxElement boundaries. + // It is set to 1 to indicate that the RTP packet contains a complete + // audioMuxElement or the last fragment of an audioMuxElement. + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + if (len > 0) + memcpy(rtp + n, hd, len); + memcpy(rtp + n + len, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + len + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, + 0); + packer->handler.free(packer->cbparam, rtp); + len = 0; // write PayloadLengthInfo once only + } + + return r; +} + +struct rtp_payload_encode_t *rtp_mp4a_latm_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mp4a_latm_pack_create, + rtp_mp4a_latm_pack_destroy, + rtp_mp4a_latm_pack_get_info, + rtp_mp4a_latm_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c new file mode 100755 index 000000000..53b5369d3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c @@ -0,0 +1,84 @@ +/// RFC3016 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_mp4a_latm(void *p, const void *packet, int bytes) +{ + int len; + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // save payload + if (0 == helper->size) { + ptr = (const uint8_t *)pkt.payload; + for (pend = ptr + pkt.payloadlen; ptr < pend; ptr += len) { + // ISO/IEC 14496-3:200X(E) + // Table 1.44 - Syntax of PayloadLengthInfo() (p84) + // Table 1.45 - Syntax of PayloadMux() + for (len = 0; ptr < pend; ptr++) { + len += *ptr; + if (255 != *ptr) { + ++ptr; + break; + } + } + + if (ptr + len > pend) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + // TODO: add ADTS/ASC ??? + pkt.payload = ptr; + pkt.payloadlen = len; + rtp_payload_write(helper, &pkt); + + if (ptr + len < pend || pkt.rtp.m) { + rtp_payload_onframe(helper); + } + } + } else { + // RFC6416 6.3. Fragmentation of MPEG-4 Audio Bitstream (p17) + // It is RECOMMENDED to put one audioMuxElement in each RTP packet. If + // the size of an audioMuxElement can be kept small enough that the size + // of the RTP packet containing it does not exceed the size of the Path + // MTU, this will be no problem.If it cannot, the audioMuxElement + // SHALL be fragmented and spread across multiple packets. + rtp_payload_write(helper, &pkt); + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mp4a_latm_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mp4a_latm, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c new file mode 100755 index 000000000..7070e6ae1 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c @@ -0,0 +1,121 @@ +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// +/// 5.1. Use of RTP Header Fields for MPEG-4 Visual (p9) +/// Marker (M) bit: The marker bit is set to 1 to indicate the last RTP +/// packet(or only RTP packet) of a VOP.When multiple VOPs are carried +/// in the same RTP packet, the marker bit is set to 1. +/// +/// 5.2. Fragmentation of MPEG-4 Visual Bitstream +/// A fragmented MPEG-4 Visual bitstream is mapped directly onto the RTP +/// payload without any addition of extra header fields or any removal of +/// Visual syntax elements. +/// +/// 6.2. Use of RTP Header Fields for MPEG-4 Audio (p16) +/// Marker (M) bit: The marker bit indicates audioMuxElement boundaries. +/// It is set to 1 to indicate that the RTP packet contains a complete +/// audioMuxElement or the last fragment of an audioMuxElement +/// +/// 6.3. Fragmentation of MPEG-4 Audio Bitstream +/// It is RECOMMENDED to put one audioMuxElement in each RTP packet. + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz + +struct rtp_encode_mp4v_es_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_mp4v_es_encode_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_mp4v_es_encode_destroy(void *pack) +{ + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mp4v_es_encode_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_mp4v_es_encode_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + const uint8_t *ptr; + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)pack; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); + + r = 0; + for (ptr = (const uint8_t *)data; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +struct rtp_payload_encode_t *rtp_mp4v_es_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mp4v_es_encode_create, + rtp_mp4v_es_encode_destroy, + rtp_mp4v_es_encode_get_info, + rtp_mp4v_es_encode_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c new file mode 100755 index 000000000..c90f32b90 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c @@ -0,0 +1,50 @@ +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + +#include "rtp-packet.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_mp4v_es(void *p, const void *packet, int bytes) +{ + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // save payload + assert(pkt.payloadlen > 0); + if (!helper->lost && pkt.payload && pkt.payloadlen > 0) { + if (0 != rtp_payload_write(helper, &pkt)) + return -ENOMEM; + } + + // 5.1. Use of RTP Header Fields for MPEG-4 Visual (p9) + // Marker (M) bit: The marker bit is set to 1 to indicate the last RTP + // packet(or only RTP packet) of a VOP.When multiple VOPs are carried + // in the same RTP packet, the marker bit is set to 1. + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mp4v_es_decode() +{ + static struct rtp_payload_decode_t decode = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mp4v_es, + }; + + return &decode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c new file mode 100755 index 000000000..6352bbb66 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c @@ -0,0 +1,335 @@ +/// RFC2250 3. Encapsulation of MPEG Elementary Streams (p4) +/// 3.1 MPEG Video elementary streams +/// 1. The MPEG Video_Sequence_Header, when present, will always be at the beginning of an RTP payload +/// 2. An MPEG GOP_header, when present, will always be at the beginning of the RTP payload, or will follow a +/// Video_Sequence_Header +/// 3. An MPEG Picture_Header, when present, will always be at the beginning of a RTP payload, or will follow a +/// GOP_header +/// 4. An implementation based on this encapsulation assumes that the Video_Sequence_Header is repeated periodically in +/// the MPEG bitstream. +/// 5. The beginning of a slice must either be the first data in a packet(after any MPEG ES headers) or must follow +/// after some integral number of slices in a packet. +/// +/// minimum RTP payload size of 261 bytes must be supported to contain the largest single header +/// +/// 3.2 MPEG Audio elementary streams +/// 1. Multiple audio frames may be encapsulated within one RTP packet. +/// +/// 3.3 RTP Fixed Header for MPEG ES encapsulation (p7) +/// 1. M bit: For video, set to 1 on packet containing MPEG frame end code, 0 otherwise. +/// For audio, set to 1 on first packet of a "talk-spurt," 0 otherwise. +/// 2. timestamp: 32 bit 90K Hz timestamp representing the target transmission time for the first byte of the packet + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +// ISO/IEC 13818-2: 1995 (E) Table 6-1 — Start code values (p41) +#define MPEG2VIDEO_PICTURE 0x00 +#define MPEG2VIDEO_SLICE 0x01 // ~0xAF +#define MPEG2VIDEO_SEQUENCE 0xB3 +#define MPEG2VIDEO_GROUP 0xB8 + +#define N_MPEG12_HEADER 4 + +#define KHz 90 // 90000Hz + +struct rtp_encode_mpeg2es_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +struct mpeg2_video_header_t { + unsigned int begin_of_sequence : 1; + // unsigned int begin_of_slice : 1; + // unsigned int end_of_slice : 1; + unsigned int frame_type : 3; // This value is constant for each RTP packet of a given picture. + unsigned int temporal_reference : 10; // This value is constant for all RTP packets of a given picture. + + // Obtained from the most recent picture header, and are + // constant for each RTP packet of a given picture. + unsigned int FBV : 1; + unsigned int BFC : 3; + unsigned int FFV : 1; + unsigned int FFC : 3; +}; + +static void *rtp_mpeg2es_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + assert(RTP_PAYLOAD_MP3 == pt || RTP_PAYLOAD_MPV == pt); + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + packer->pkt.rtp.m = (RTP_PAYLOAD_MP3 == pt) ? 1 : 0; // set to 1 on first packet of a "talk-spurt," 0 otherwise. + return packer; +} + +static void rtp_mpeg2es_pack_destroy(void *pack) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mpeg2es_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +// 3.5 MPEG Audio-specific header +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| MBZ | Frag_offset | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_mpeg2es_pack_audio(struct rtp_encode_mpeg2es_t *packer, const uint8_t *audio, int bytes) +{ + int r, n; + int offset; + uint8_t *rtp; + + for (r = offset = 0; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = audio; + packer->pkt.payloadlen = (bytes + N_MPEG12_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_MPEG12_HEADER - RTP_FIXED_HEADER); + audio += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_MPEG12_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + packer->pkt.rtp.m = 0; // set to 1 on first packet of a "talk-spurt," 0 otherwise. + + /* build fragmented packet */ + rtp[n + 0] = 0; + rtp[n + 1] = 0; + rtp[n + 2] = (uint8_t)(offset >> 8); + rtp[n + 3] = (uint8_t)offset; + memcpy(rtp + n + N_MPEG12_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + offset += packer->pkt.payloadlen; + + r = packer->handler.packet(packer->cbparam, rtp, n + N_MPEG12_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +static const uint8_t *mpeg2_start_code_prefix_find(const uint8_t *p, const uint8_t *end) +{ + int i; + for (i = 0; p + i + 4 < end; i++) { + if (0x00 == p[i] && 0x00 == p[i + 1] && 0x01 == p[i + 2]) + return p + i; + } + return end; +} + +// 3.4 MPEG Video-specific header +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| MBZ |T| TR | |N|S|B|E| P | | BFC | | FFC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + AN FBV FFV +*/ + +// 3.4.1 MPEG-2 Video-specific header extension +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|X|E|f_[0,0]|f_[0,1]|f_[1,0]|f_[1,1]| DC| PS|T|P|C|Q|V|A|R|H|G|D| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_mpeg2es_pack_slice(struct rtp_encode_mpeg2es_t *packer, const uint8_t *video, int bytes, + struct mpeg2_video_header_t *h, int marker) +{ + int r, n; + uint8_t *rtp; + uint8_t begin_of_slice; + uint8_t end_of_slice; + uint8_t begin_of_sequence; + + r = 0; + for (begin_of_slice = 1; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = video; + packer->pkt.payloadlen = (bytes + N_MPEG12_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_MPEG12_HEADER - RTP_FIXED_HEADER); + video += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_MPEG12_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (marker && 0 == bytes) ? 1 : 0; // set to 1 on packet containing MPEG frame end code + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /* build fragmented packet */ + end_of_slice = bytes ? 0 : 1; + begin_of_sequence = (h->begin_of_sequence && begin_of_slice) ? 1 : 0; + rtp[n + 0] = (uint8_t)(h->temporal_reference >> 8) & 0x03; + rtp[n + 1] = (uint8_t)h->temporal_reference; + rtp[n + 2] = (uint8_t)((begin_of_sequence << 5) | (begin_of_slice << 4) | (end_of_slice << 3) | h->frame_type); + rtp[n + 3] = (uint8_t)((h->FBV << 7) | (h->BFC << 4) | (h->FFV << 3) | h->FFC); + memcpy(rtp + n + N_MPEG12_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + begin_of_slice = 0; + + r = packer->handler.packet(packer->cbparam, rtp, n + N_MPEG12_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +static int mpeg2_video_header_parse(struct mpeg2_video_header_t *mpeg2vh, const uint8_t *data, int bytes) +{ + if (bytes < 4) + return -1; + + if (MPEG2VIDEO_PICTURE == data[3]) { + if (bytes < 9) + return -1; + + // ISO/IEC 13818-2: 1995 (E) 6.2.3 Picture header (p47) + /* + picture_header() { + picture_start_code 32 bslbf + temporal_reference 10 uimsbf + picture_coding_type 3 uimsbf + vbv_delay 16 uimsbf + if ( picture_coding_type == 2 || picture_coding_type == 3) { + full_pel_forward_vector 1 bslbf + forward_f_code 3 bslbf + } + if ( picture_coding_type == 3 ) { + full_pel_backward_vector 1 bslbf + backward_f_code 3 bslbf + } + } + */ + mpeg2vh->frame_type = (data[5] >> 3) & 0x07; + mpeg2vh->temporal_reference = (data[4] << 2) | (data[5] >> 6); + + if (2 == mpeg2vh->frame_type) { + mpeg2vh->FFV = (uint8_t)(data[7] >> 2) & 0x01; + mpeg2vh->FFC = (uint8_t)((data[7] & 0x03) << 1) | ((data[8] >> 7) & 0x01); + } else if (3 == mpeg2vh->frame_type) { + mpeg2vh->FFV = (uint8_t)(data[7] >> 2) & 0x01; + mpeg2vh->FFC = (uint8_t)((data[7] & 0x03) << 1) | ((data[8] >> 7) & 0x01); + mpeg2vh->FBV = (uint8_t)(data[8] >> 6) & 0x01; + mpeg2vh->BFC = (uint8_t)(data[8] >> 3) & 0x07; + } + } else if (MPEG2VIDEO_SEQUENCE == data[3] || MPEG2VIDEO_GROUP == data[3]) { + mpeg2vh->begin_of_sequence = 1; + } + + return 0; +} + +static int rtp_mpeg2es_pack_video(struct rtp_encode_mpeg2es_t *packer, const uint8_t *video, int bytes) +{ + int r; + const uint8_t *p, *pnext, *pend; + struct mpeg2_video_header_t mpeg2vh; + memset(&mpeg2vh, 0, sizeof(mpeg2vh)); + + pend = video + bytes; + p = mpeg2_start_code_prefix_find(video, pend); + for (r = 0; p < pend && 0 == r; p = pnext) { + // size_t nalu_size; + + mpeg2vh.begin_of_sequence = 0; + mpeg2_video_header_parse(&mpeg2vh, p, (int)(pend - p)); + + if (pend - p + N_MPEG12_HEADER + RTP_FIXED_HEADER <= packer->size) { + // nalu_size = pend - p; + pnext = pend; + } else { + // current frame end position + pnext = mpeg2_start_code_prefix_find(p + 4, pend); + + // try to put multi-slice into together + while (pnext - p + N_MPEG12_HEADER + RTP_FIXED_HEADER < packer->size) { + const uint8_t *pnextnext; + pnextnext = mpeg2_start_code_prefix_find(pnext + 4, pend); + if (pnextnext - p + N_MPEG12_HEADER + RTP_FIXED_HEADER > packer->size) + break; + + // merge and get information + mpeg2_video_header_parse(&mpeg2vh, pnext, (int)(pend - pnext)); + pnext = pnextnext; + } + } + + r = rtp_mpeg2es_pack_slice(packer, p, (int)(pnext - p), &mpeg2vh, (pnext == pend) ? 1 : 0); + } + + return r; +} + +static int rtp_mpeg2es_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)pack; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); // ms -> 90KHZ (RFC2250 p7) + return RTP_PAYLOAD_MP3 == packer->pkt.rtp.pt ? rtp_mpeg2es_pack_audio(packer, (const uint8_t *)data, bytes) + : rtp_mpeg2es_pack_video(packer, (const uint8_t *)data, bytes); +} + +// MPV/MPA (MPEG-1/MPEG-2 Audio/Video Elementary Stream) +struct rtp_payload_encode_t *rtp_mpeg1or2es_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mpeg2es_pack_create, + rtp_mpeg2es_pack_destroy, + rtp_mpeg2es_pack_get_info, + rtp_mpeg2es_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c new file mode 100755 index 000000000..7c035f59d --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c @@ -0,0 +1,63 @@ +/// RFC2250 3. Encapsulation of MPEG Elementary Streams (p4) + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define MPEG2VIDEO_EXTENSION_HEADER 0x04 + +static int rtp_decode_mpeg2es(void *p, const void *packet, int bytes) +{ + int n; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) + return -EINVAL; + + if (RTP_PAYLOAD_MP3 != pkt.rtp.pt && RTP_PAYLOAD_MPV != pkt.rtp.pt) { + assert(0); + return -EINVAL; + } + + rtp_payload_check(helper, &pkt); + + // save payload + if (!helper->lost) { + n = 4; // skip 3.4 MPEG Video-specific header + if (RTP_PAYLOAD_MPV == pkt.rtp.pt && (((uint8_t *)pkt.payload)[4] & MPEG2VIDEO_EXTENSION_HEADER)) + n += 4; // 3.4.1 MPEG-2 Video-specific header extension + + assert(pkt.payloadlen > 4); + if (pkt.payload && pkt.payloadlen > n) { + pkt.payload = (uint8_t *)pkt.payload + n; + pkt.payloadlen -= n; + rtp_payload_write(helper, &pkt); + } + } + + // M bit: For video, set to 1 on packet containing MPEG frame end code, 0 otherwise. + // For audio, set to 1 on first packet of a "talk-spurt," 0 otherwise. + if (pkt.rtp.m && RTP_PAYLOAD_MPV == pkt.rtp.pt) { + rtp_payload_onframe(helper); + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mpeg1or2es_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mpeg2es, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c new file mode 100755 index 000000000..a83774094 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c @@ -0,0 +1,145 @@ +// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams +// +// Indicates the sampling instant of the first AU contained +// in the RTP payload. This sampling instant is equivalent to the +// CTS in the MPEG-4 time domain. When using SDP, the clock rate of +// the RTP time stamp MUST be expressed using the "rtpmap" attribute. +// If an MPEG-4 audio stream is transported, the rate SHOULD be set +// to the same value as the sampling rate of the audio stream. If an +// MPEG-4 video stream is transported, it is RECOMMENDED that the +// rate be set to 90 kHz. + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz + +#define N_AU_HEADER 4 + +struct rtp_encode_mpeg4_generic_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_mpeg4_generic_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, + struct rtp_payload_t *handler, void *cbparam) +{ + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_mpeg4_generic_pack_destroy(void *pack) +{ + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mpeg4_generic_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_mpeg4_generic_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + int n, size; + uint8_t *rtp; + uint8_t header[4]; + const uint8_t *ptr; + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)pack; + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); + + r = 0; + ptr = (const uint8_t *)data; +#if defined(RTP_MPEG4_GENERIC_SKIP_ADTS) + if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7) { + // skip ADTS header + assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07))); + ptr += 7; + bytes -= 7; + } +#endif + + for (size = bytes; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + // 3.3.6. High Bit-rate AAC + // SDP fmtp: mode=AAC-hbr;sizeLength=13;indexLength=3;indexDeltaLength=3; + header[0] = 0; + header[1] = 16; // 16-bits AU headers-length + // When the AU-size is associated with an AU fragment, the AU size indicates + // the size of the entire AU and not the size of the fragment. + // This can be exploited to determine whether a + // packet contains an entire AU or a fragment, which is particularly + // useful after losing a packet carrying the last fragment of an AU. + header[2] = (uint8_t)(size >> 5); + header[3] = (uint8_t)(size & 0x1f) << 3; + + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + N_AU_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_AU_HEADER - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_AU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker (M) bit: The M bit is set to 1 to indicate that the RTP packet + // payload contains either the final fragment of a fragmented Access + // Unit or one or more complete Access Units + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + memcpy(rtp + n, header, N_AU_HEADER); + memcpy(rtp + n + N_AU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + N_AU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +struct rtp_payload_encode_t *rtp_mpeg4_generic_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mpeg4_generic_pack_create, + rtp_mpeg4_generic_pack_destroy, + rtp_mpeg4_generic_pack_get_info, + rtp_mpeg4_generic_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c new file mode 100755 index 000000000..be0fe4aaf --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c @@ -0,0 +1,95 @@ +// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_mpeg4_generic(void *p, const void *packet, int bytes) +{ + int i, size; + int au_size; + int au_numbers; + int au_header_length; + const uint8_t *ptr, *pau, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // save payload + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // AU-headers-length + au_header_length = (ptr[0] << 8) + ptr[1]; + au_header_length = (au_header_length + 7) / 8; // bit -> byte + + if (ptr + au_header_length /*AU-size*/ > pend || au_header_length < 2) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + // 3.3.6. High Bit-rate AAC + // SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength=3; + au_size = 2; // only AU-size + au_numbers = au_header_length / au_size; + assert(0 == au_header_length % au_size); + ptr += 2; // skip AU headers length section 2-bytes + pau = ptr + au_header_length; // point to Access Unit + + for (i = 0; i < au_numbers; i++) { + size = (ptr[0] << 8) | (ptr[1] & 0xF8); + size = size >> 3; // bit -> byte + if (pau + size > pend) { + // fragment ? + assert(1 == au_numbers); + pkt.payload = pau; + pkt.payloadlen = size; + rtp_payload_write(helper, &pkt); + return 1; + } + + // TODO: add ADTS/ASC ??? + pkt.payload = pau; + pkt.payloadlen = size; + rtp_payload_write(helper, &pkt); + + ptr += au_size; + pau += size; + + if (au_numbers > 1 || pkt.rtp.m) { + if (helper->size != size) { + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + } + rtp_payload_onframe(helper); + } + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mpeg4_generic_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mpeg4_generic, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-pack.c new file mode 100755 index 000000000..2ba2ce4f6 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-pack.c @@ -0,0 +1,104 @@ +// RFC3551 RTP Profile for Audio and Video Conferences with Minimal Control + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +struct rtp_packer_t { + struct rtp_payload_t handler; + void *cbparam; + + struct rtp_packet_t pkt; + int size; +}; + +static void *rtp_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *param) +{ + struct rtp_packer_t *packer; + packer = (struct rtp_packer_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = param; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +void rtp_pack_destroy(void *p) +{ + struct rtp_packer_t *packer; + packer = (struct rtp_packer_t *)p; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +void rtp_pack_get_info(void *p, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_packer_t *packer; + packer = (struct rtp_packer_t *)p; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +int rtp_pack_input(void *p, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + const uint8_t *ptr; + struct rtp_packer_t *packer; + + r = 0; + packer = (struct rtp_packer_t *)p; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; // (uint32_t)time * packer->frequency / 1000; // ms -> 8KHZ + packer->pkt.rtp.m = 0; // marker bit alway 0 + + for (ptr = (const uint8_t *)data; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + // packer->pkt.rtp.timestamp += packer->pkt.payloadlen * packer->frequency / 1000; + } + + return r; +} + +struct rtp_payload_encode_t *rtp_common_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_pack_create, + rtp_pack_destroy, + rtp_pack_get_info, + rtp_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c new file mode 100755 index 000000000..ea439fca0 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c @@ -0,0 +1,130 @@ +#include "rtp-payload-helper.h" +#include "rtp-param.h" +#include +#include +#include +#include + +void *rtp_payload_helper_create(struct rtp_payload_t *handler, void *cbparam) +{ + struct rtp_payload_helper_t *helper; + helper = (struct rtp_payload_helper_t *)calloc(1, sizeof(*helper)); + if (!helper) + return NULL; + + memcpy(&helper->handler, handler, sizeof(helper->handler)); + helper->maxsize = RTP_PAYLOAD_MAX_SIZE; + helper->cbparam = cbparam; + helper->__flags = -1; + return helper; +} + +void rtp_payload_helper_destroy(void *p) +{ + struct rtp_payload_helper_t *helper; + helper = (struct rtp_payload_helper_t *)p; + + if (helper->ptr) + free(helper->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(helper, 0xCC, sizeof(*helper)); +#endif + free(helper); +} + +int rtp_payload_check(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt) +{ + int lost; // next frame lost packet flags + + // first packet only + if (-1 == helper->__flags) { + // TODO: first packet lost ??? + helper->__flags = 0; + helper->seq = (uint16_t)(pkt->rtp.seq - 1); // disable packet lost + helper->timestamp = pkt->rtp.timestamp + 1; // flag for new frame + } + + lost = 0; + // check sequence number + if ((uint16_t)pkt->rtp.seq != (uint16_t)(helper->seq + 1)) { + lost = 1; + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + // helper->timestamp = pkt->rtp.timestamp; + } + helper->seq = (uint16_t)pkt->rtp.seq; + + // check timestamp + if (pkt->rtp.timestamp != helper->timestamp) { + rtp_payload_onframe(helper); + + // lost: + // 0 - packet lost before timestamp change + // 1 - packet lost on timestamp changed, can't known losted packet is at old packet tail or new packet start, so + // two packets mark as packet lost + if (0 != lost) + helper->lost = lost; + } + + helper->timestamp = pkt->rtp.timestamp; + + return 0; +} + +int rtp_payload_write(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt) +{ + int size; + size = helper->size + pkt->payloadlen; + if (size > helper->maxsize || size < 0) + return -EINVAL; + + if (size > helper->capacity) { + void *ptr; + + size += size / 4 > 16000 ? size / 4 : 16000; + ptr = realloc(helper->ptr, size); + if (!ptr) { + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + helper->lost = 1; + // helper->size = 0; + return -ENOMEM; + } + + helper->ptr = (uint8_t *)ptr; + helper->capacity = size; + } + + assert(helper->capacity >= helper->size + pkt->payloadlen); + memcpy(helper->ptr + helper->size, pkt->payload, pkt->payloadlen); + helper->size += pkt->payloadlen; + return 0; +} + +int rtp_payload_onframe(struct rtp_payload_helper_t *helper) +{ + int r; + r = 0; + + if (helper->size > 0 +#if !defined(RTP_ENABLE_COURRUPT_PACKET) + && 0 == helper->lost +#endif + ) { + // previous packet done + r = helper->handler.packet(helper->cbparam, helper->ptr, helper->size, helper->timestamp, + helper->__flags | (helper->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0)); + + // RTP_PAYLOAD_FLAG_PACKET_LOST: miss + helper->__flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST; // clear packet lost flag + } + + // set packet lost flag on next frame + if (helper->lost) + helper->__flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + + // new frame start + helper->lost = 0; + helper->size = 0; + return r; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h new file mode 100755 index 000000000..6fc8deb7d --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h @@ -0,0 +1,29 @@ +#ifndef _rtp_payload_helper_h_ +#define _rtp_payload_helper_h_ + +#include "rtp-packet.h" +#include "rtp-payload.h" + +struct rtp_payload_helper_t { + struct rtp_payload_t handler; + void *cbparam; + + int lost; + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity, maxsize; + int __flags; // internal use only +}; + +void *rtp_payload_helper_create(struct rtp_payload_t *handler, void *cbparam); +void rtp_payload_helper_destroy(void *helper); + +int rtp_payload_check(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt); + +int rtp_payload_write(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt); + +int rtp_payload_onframe(struct rtp_payload_helper_t *helper); + +#endif /* !_rtp_payload_helper_h_ */ diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h b/src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h new file mode 100755 index 000000000..72e3dbc2c --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h @@ -0,0 +1,76 @@ +#ifndef _rtp_payload_internal_h_ +#define _rtp_payload_internal_h_ + +#include "rtp-payload.h" +#include "rtp-packet.h" +#include "rtp-param.h" +#include "rtp-util.h" + +struct rtp_payload_encode_t { + /// create RTP packer + /// @param[in] size maximum RTP packet payload size(don't include RTP header) + /// @param[in] payload RTP header PT filed (see more about rtp-profile.h) + /// @param[in] seq RTP header sequence number filed + /// @param[in] ssrc RTP header SSRC filed + /// @param[in] handler user-defined callback + /// @param[in] cbparam user-defined parameter + /// @return RTP packer + void *(*create)(int size, uint8_t payload, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam); + /// destroy RTP Packer + void (*destroy)(void *packer); + + void (*get_info)(void *packer, uint16_t *seq, uint32_t *timestamp); + + /// PS/H.264 Elementary Stream to RTP Packet + /// @param[in] packer + /// @param[in] data stream data + /// @param[in] bytes stream length in bytes + /// @param[in] time stream UTC time + /// @return 0-ok, ENOMEM-alloc failed, <0-failed + int (*input)(void *packer, const void *data, int bytes, uint32_t time); +}; + +struct rtp_payload_decode_t { + void *(*create)(struct rtp_payload_t *handler, void *param); + void (*destroy)(void *packer); + + /// RTP packet to PS/H.264 Elementary Stream + /// @param[in] decoder RTP packet unpackers + /// @param[in] packet RTP packet + /// @param[in] bytes RTP packet length in bytes + /// @param[in] time stream UTC time + /// @return 1-packet handled, 0-packet discard, <0-failed + int (*input)(void *decoder, const void *packet, int bytes); +}; + +struct rtp_payload_encode_t *rtp_ts_encode(void); +struct rtp_payload_encode_t *rtp_vp8_encode(void); +struct rtp_payload_encode_t *rtp_vp9_encode(void); +struct rtp_payload_encode_t *rtp_av1_encode(void); +struct rtp_payload_encode_t *rtp_h264_encode(void); +struct rtp_payload_encode_t *rtp_h265_encode(void); +struct rtp_payload_encode_t *rtp_h266_encode(void); +struct rtp_payload_encode_t *rtp_common_encode(void); +struct rtp_payload_encode_t *rtp_mp4v_es_encode(void); +struct rtp_payload_encode_t *rtp_mp4a_latm_encode(void); +struct rtp_payload_encode_t *rtp_mpeg4_generic_encode(void); +struct rtp_payload_encode_t *rtp_mpeg1or2es_encode(void); + +struct rtp_payload_decode_t *rtp_ts_decode(void); +struct rtp_payload_decode_t *rtp_ps_decode(void); +struct rtp_payload_decode_t *rtp_vp8_decode(void); +struct rtp_payload_decode_t *rtp_vp9_decode(void); +struct rtp_payload_decode_t *rtp_av1_decode(void); +struct rtp_payload_decode_t *rtp_h264_decode(void); +struct rtp_payload_decode_t *rtp_h265_decode(void); +struct rtp_payload_decode_t *rtp_h266_decode(void); +struct rtp_payload_decode_t *rtp_common_decode(void); +struct rtp_payload_decode_t *rtp_mp4v_es_decode(void); +struct rtp_payload_decode_t *rtp_mp4a_latm_decode(void); +struct rtp_payload_decode_t *rtp_mpeg4_generic_decode(void); +struct rtp_payload_decode_t *rtp_mpeg1or2es_decode(void); + +int rtp_packet_serialize_header(const struct rtp_packet_t *pkt, void *data, int bytes); + +#endif /* !_rtp_payload_internal_h_ */ diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload.c b/src/tuya_p2p/lib_rtp/payload/rtp-payload.c new file mode 100755 index 000000000..854d5c318 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload.c @@ -0,0 +1,223 @@ +#include "rtp-payload.h" +#include "rtp-profile.h" +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include + +#define TS_PACKET_SIZE 188 + +#if defined(OS_WINDOWS) +#define strcasecmp _stricmp +#endif + +struct rtp_payload_delegate_t { + struct rtp_payload_encode_t *encoder; + struct rtp_payload_decode_t *decoder; + void *packer; +}; + +/// @return 0-ok, <0-error +static int rtp_payload_find(int payload, const char *encoding, struct rtp_payload_delegate_t *codec); + +void *rtp_payload_encode_create(int payload, const char *name, uint16_t seq, uint32_t ssrc, + struct rtp_payload_t *handler, void *cbparam) +{ + int size; + struct rtp_payload_delegate_t *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx) { + size = rtp_packet_getsize(); + if (rtp_payload_find(payload, name, ctx) < 0 || + NULL == (ctx->packer = ctx->encoder->create(size, (uint8_t)payload, seq, ssrc, handler, cbparam))) { + free(ctx); + return NULL; + } + } + return ctx; +} + +void rtp_payload_encode_destroy(void *encoder) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)encoder; + ctx->encoder->destroy(ctx->packer); + free(ctx); +} + +void rtp_payload_encode_getinfo(void *encoder, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)encoder; + ctx->encoder->get_info(ctx->packer, seq, timestamp); +} + +int rtp_payload_encode_input(void *encoder, const void *data, int bytes, uint32_t timestamp) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)encoder; + return ctx->encoder->input(ctx->packer, data, bytes, timestamp); +} + +void *rtp_payload_decode_create(int payload, const char *name, struct rtp_payload_t *handler, void *cbparam) +{ + struct rtp_payload_delegate_t *ctx; + ctx = calloc(1, sizeof(*ctx)); + if (ctx) { + if (rtp_payload_find(payload, name, ctx) < 0 || + NULL == (ctx->packer = ctx->decoder->create(handler, cbparam))) { + free(ctx); + return NULL; + } + } + return ctx; +} + +void rtp_payload_decode_destroy(void *decoder) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)decoder; + ctx->decoder->destroy(ctx->packer); + free(ctx); +} + +int rtp_payload_decode_input(void *decoder, const void *packet, int bytes) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)decoder; + return ctx->decoder->input(ctx->packer, packet, bytes); +} + +// Default max packet size (1500, minus allowance for IP, UDP, UMTP headers) +// (Also, make it a multiple of 4 bytes, just in case that matters.) +// static int s_max_packet_size = 1456; // from Live555 MultiFrameRTPSink.cpp RTP_PAYLOAD_MAX_SIZE +// static size_t s_max_packet_size = 576; // UNIX Network Programming by W. Richard Stevens +static int s_max_packet_size = /*1434*/ 1114; // from VLC + +void rtp_packet_setsize(int bytes) +{ + s_max_packet_size = bytes < 564 ? 564 : bytes; +} + +int rtp_packet_getsize() +{ + return s_max_packet_size; +} + +static int rtp_payload_find(int payload, const char *encoding, struct rtp_payload_delegate_t *codec) +{ + assert(payload >= 0 && payload <= 127); + if (payload >= RTP_PAYLOAD_DYNAMIC && encoding) { + if (0 == strcasecmp(encoding, "H264")) { + // H.264 video (MPEG-4 Part 10) (RFC 6184) + codec->encoder = rtp_h264_encode(); + codec->decoder = rtp_h264_decode(); + } else if (0 == strcasecmp(encoding, "H265") || 0 == strcasecmp(encoding, "HEVC")) { + // H.265 video (HEVC) (RFC 7798) + codec->encoder = rtp_h265_encode(); + codec->decoder = rtp_h265_decode(); + } else if (0 == strcasecmp(encoding, "H266")) { + // H.266 video (VVC) + // https://www.ietf.org/archive/id/draft-ietf-avtcore-rtp-vvc-18.html#name-media-type-registration + codec->encoder = rtp_h266_encode(); + codec->decoder = rtp_h266_decode(); + } else if (0 == strcasecmp(encoding, "MP4V-ES") || 0 == strcasecmp(encoding, "MPEG4")) { + // RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + // 5. RTP Packetization of MPEG-4 Visual Bitstreams (p8) + // 7.1 Media Type Registration for MPEG-4 Audio/Visual Streams (p17) + codec->encoder = rtp_mp4v_es_encode(); + codec->decoder = rtp_mp4v_es_decode(); + } else if (0 == strcasecmp(encoding, "MP4A-LATM")) { + // RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + // 6. RTP Packetization of MPEG-4 Audio Bitstreams (p15) + // 7.3 Media Type Registration for MPEG-4 Audio (p21) + codec->encoder = rtp_mp4a_latm_encode(); + codec->decoder = rtp_mp4a_latm_decode(); + } else if (0 == strcasecmp(encoding, "mpeg4-generic")) { + /// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams + /// 4.1. MIME Type Registration (p27) + codec->encoder = rtp_mpeg4_generic_encode(); + codec->decoder = rtp_mpeg4_generic_decode(); + } else if (0 == strcasecmp(encoding, "VP8")) { + /// RFC7741 RTP Payload Format for VP8 Video + /// 6.1. Media Type Definition (p21) + codec->encoder = rtp_vp8_encode(); + codec->decoder = rtp_vp8_decode(); + } else if (0 == strcasecmp(encoding, "VP9")) { + /// RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + /// 6.1. Media Type Definition (p15) + codec->encoder = rtp_vp9_encode(); + codec->decoder = rtp_vp9_decode(); + } else if (0 == strcasecmp(encoding, "AV1")) { + /// https://aomediacodec.github.io/av1-rtp-spec/#7-payload-format-parameters + codec->encoder = rtp_av1_encode(); + codec->decoder = rtp_av1_decode(); + } else if (0 == strcasecmp(encoding, "MP2P") || + 0 == strcasecmp(encoding, "PS")) // MPEG-2 Program Streams video (RFC 2250) + { + codec->encoder = rtp_ts_encode(); + codec->decoder = rtp_ps_decode(); + } else if (0 == strcasecmp(encoding, "MP1S")) // MPEG-1 Systems Streams video (RFC 2250) + { + codec->encoder = rtp_ts_encode(); + codec->decoder = rtp_ts_decode(); + } else if (0 == strcasecmp(encoding, "opus") // RFC7587 RTP Payload Format for the Opus Speech and Audio Codec + || 0 == strcasecmp(encoding, "G726-16") // ITU-T G.726 audio 16 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G726-24") // ITU-T G.726 audio 24 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G726-32") // ITU-T G.726 audio 32 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G726-40") // ITU-T G.726 audio 40 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G7221") || 0 == strcasecmp(encoding, "PCMU") || + 0 == strcasecmp(encoding, "PCMA") || + 0 == strcasecmp(encoding, "PCM")) // RFC5577 RTP Payload Format for ITU-T Recommendation G.722.1 + { + codec->encoder = rtp_common_encode(); + codec->decoder = rtp_common_decode(); + } else { + return -1; + } + } else { +#if defined(_DEBUG) || defined(DEBUG) + const struct rtp_profile_t *profile; + profile = rtp_profile_find(payload); + assert(!profile || !encoding || !*encoding || 0 == strcasecmp(profile->name, encoding)); +#endif + + switch (payload) { + // case 99/*RTP_PCM_PAYLOAD*/: + case RTP_PAYLOAD_PCMU: // ITU-T G.711 PCM u-Law audio 64 kbit/s (RFC 3551) + case RTP_PAYLOAD_PCMA: // ITU-T G.711 PCM A-Law audio 64 kbit/s (RFC 3551) + case RTP_PAYLOAD_G722: // ITU-T G.722 audio 64 kbit/s (RFC 3551) + case RTP_PAYLOAD_G729: // ITU-T G.729 and G.729a audio 8 kbit/s (RFC 3551) + codec->encoder = rtp_common_encode(); + codec->decoder = rtp_common_decode(); + break; + + case RTP_PAYLOAD_MP3: // MPEG-1 or MPEG-2 audio only (RFC 3551, RFC 2250) + case RTP_PAYLOAD_MPV: // MPEG-1 and MPEG-2 video (RFC 2250) + codec->encoder = rtp_mpeg1or2es_encode(); + codec->decoder = rtp_mpeg1or2es_decode(); + break; + + case RTP_PAYLOAD_MP2T: // MPEG-2 transport stream (RFC 2250) + codec->encoder = rtp_ts_encode(); + codec->decoder = rtp_ts_decode(); + break; + + case RTP_PAYLOAD_AV1X: // https://bugs.chromium.org/p/webrtc/issues/detail?id=11042 + codec->encoder = rtp_av1_encode(); + codec->decoder = rtp_av1_decode(); + break; + + case RTP_PAYLOAD_JPEG: + case RTP_PAYLOAD_H263: + return -1; // TODO + + default: + return -1; // not support + } + } + + return 0; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c new file mode 100755 index 000000000..53efe5cb4 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c @@ -0,0 +1,71 @@ +/// RFC2250 2. Encapsulation of MPEG System and Transport Streams (p3) + +#include "rtp-packet.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +// Pack start code +static const uint8_t s_mpeg2_packet_start[] = {0x00, 0x00, 0x01, 0xBA}; + +static int rtp_decode_ps(void *p, const void *packet, int bytes) +{ + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + // if(-1 == helper->flags) + // { + // if(pkt.payloadlen < sizeof(s_mpeg2_packet_start) + 2 || 0 != memcmp(s_mpeg2_packet_start, pkt.payload, + // sizeof(s_mpeg2_packet_start))) + // return 0; // packet discard, wait for first packet + // } + + // 2.1 RTP header usage(p4) + // M bit: Set to 1 whenever the timestamp is discontinuous. (such as + // might happen when a sender switches from one data + // source to another).This allows the receiver and any + // intervening RTP mixers or translators that are synchronizing + // to the flow to ignore the difference between this timestamp + // and any previous timestamp in their clock phase detectors. + // if (pkt.rtp.m) + // { + // //TODO: test + // // new frame start + // helper->size = 0; // discard previous packets + // helper->lost = 0; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; // notify source changed + // helper->seq = (uint16_t)pkt.rtp.seq; + // helper->timestamp = pkt.rtp.timestamp; + // } + // else + { + rtp_payload_check(helper, &pkt); + } + + // ignore RTP M bit + if (pkt.payloadlen > sizeof(s_mpeg2_packet_start) && + 0 == memcmp(s_mpeg2_packet_start, pkt.payload, sizeof(s_mpeg2_packet_start))) + rtp_payload_onframe(helper); // new frame/access start + + rtp_payload_write(helper, &pkt); + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_ps_decode(void) +{ + static struct rtp_payload_decode_t decode = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_ps, + }; + + return &decode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c new file mode 100755 index 000000000..c837238ab --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c @@ -0,0 +1,126 @@ +/// RFC3555 MIME Type Registration of RTP Payload Formats +/// 4.2.11 Registration of MIME media type video/MP2P (p40) +/// +/// RFC2250 2. Encapsulation of MPEG System and Transport Streams (p3) +/// 1. Each RTP packet will contain a timestamp derived from the sender's 90KHz clock reference +/// 2. For MPEG2 Program streams and MPEG1 system streams there are no packetization restrictions; +/// these streams are treated as a packetized stream of bytes. +/// +/// 2.1 RTP header usage (p4) +/// 32 bit 90K Hz timestamp representing the target transmission time for the first byte of the packet + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define TS_PACKET_SIZE 188 + +#define KHz 90 // 90000Hz + +struct rtp_encode_ts_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_ts_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + // assert(pt == RTP_PAYLOAD_MP2T); + if (RTP_PAYLOAD_MP2T == pt) { + size -= RTP_FIXED_HEADER; + size = size / TS_PACKET_SIZE * TS_PACKET_SIZE; + size += RTP_FIXED_HEADER; + if (size < 64) { + free(packer); + return NULL; + } + } + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_ts_pack_destroy(void *pack) +{ + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_ts_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_ts_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + const uint8_t *ptr; + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)pack; + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); // ms -> 90KHZ (RFC2250 section2 p2) + + r = 0; + for (ptr = (const uint8_t *)data; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // M bit: Set to 1 whenever the timestamp is discontinuous + // packer->pkt.rtp.m = (bytes <= packer->size) ? 1 : 0; + packer->pkt.rtp.m = 0; + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +struct rtp_payload_encode_t *rtp_ts_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_ts_pack_create, + rtp_ts_pack_destroy, + rtp_ts_pack_get_info, + rtp_ts_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c new file mode 100755 index 000000000..dccd04c06 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c @@ -0,0 +1,51 @@ +/// RFC2250 2. Encapsulation of MPEG System and Transport Streams (p3) + +#include "rtp-packet.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_ts(void *p, const void *packet, int bytes) +{ + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // 2.1 RTP header usage(p4) + // M bit: Set to 1 whenever the timestamp is discontinuous. (such as + // might happen when a sender switches from one data + // source to another).This allows the receiver and any + // intervening RTP mixers or translators that are synchronizing + // to the flow to ignore the difference between this timestamp + // and any previous timestamp in their clock phase detectors. + if (pkt.rtp.m) { + // TODO: test + // new frame start + // helper->size = 0; // discard previous packets + helper->lost = 1; // notify source changed + rtp_payload_onframe(helper); // clear previous source data + } + + rtp_payload_write(helper, &pkt); + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_ts_decode() +{ + static struct rtp_payload_decode_t decode = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_ts, + }; + + return &decode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-unpack.c new file mode 100755 index 000000000..05d6acc2a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-unpack.c @@ -0,0 +1,38 @@ +// RFC3551 RTP Profile for Audio and Video Conferences with Minimal Control + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +static int rtp_decode_rfc2250(void *p, const void *packet, int bytes) +{ + int r; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + r = 0; + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + assert(pkt.payloadlen >= 0); + if (pkt.payloadlen > 0) + r = helper->handler.packet(helper->cbparam, pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); + return 0 == r ? 1 : r; // packet handled +} + +struct rtp_payload_decode_t *rtp_common_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_rfc2250, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c new file mode 100755 index 000000000..a80d2a6a5 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c @@ -0,0 +1,116 @@ +// RFC7741 RTP Payload Format for VP8 Video +// + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +// Timestamp: The granularity of the clock is 90 kHz +#define KHz 90 // 90000Hz + +#define N_VP8_HEADER 1 + +struct rtp_encode_vp8_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_vp8_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_vp8_pack_destroy(void *pack) +{ + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_vp8_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_vp8_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + uint8_t vp8_payload_descriptor[1]; + const uint8_t *ptr; + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)pack; + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); + + r = 0; + ptr = (const uint8_t *)data; + for (vp8_payload_descriptor[0] = 0x10 /*start of partition*/; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + N_VP8_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_VP8_HEADER - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_VP8_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker bit (M): MUST be set for the very last packet of each encoded + // frame in line with the normal use of the M bit in video formats. + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + memcpy(rtp + n, vp8_payload_descriptor, N_VP8_HEADER); + memcpy(rtp + n + N_VP8_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + N_VP8_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + vp8_payload_descriptor[0] = 0x00; + } + + return r; +} + +struct rtp_payload_encode_t *rtp_vp8_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_vp8_pack_create, + rtp_vp8_pack_destroy, + rtp_vp8_pack_get_info, + rtp_vp8_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c new file mode 100755 index 000000000..8ea7b2d11 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c @@ -0,0 +1,162 @@ +// RFC7741 RTP Payload Format for VP8 Video + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| VP8 payload descriptor (integer #octets) | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : VP8 payload header (3 octets) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| VP8 pyld hdr : | ++-+-+-+-+-+-+-+-+ | +: Octets 4..N of VP8 payload : +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_decode_vp8(void *p, const void *packet, int bytes) +{ + uint8_t extended_control_bits; + uint8_t start_of_vp8_partition; + // uint8_t PID; + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // VP8 payload descriptor + extended_control_bits = ptr[0] & 0x80; + start_of_vp8_partition = ptr[0] & 0x10; + // PID = ptr[0] & 0x0f; + ptr++; + + if (extended_control_bits && ptr < pend) { + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |X|R|N|S|R| PID | (REQUIRED) + +-+-+-+-+-+-+-+-+ + X: |I|L|T|K| RSV | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + I: |M| PictureID | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + | PictureID | + +-+-+-+-+-+-+-+-+ + L: | TL0PICIDX | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + T/K:|TID|Y| KEYIDX | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + */ + uint8_t pictureid_present; + uint8_t tl0picidx_present; + uint8_t tid_present; + uint8_t keyidx_present; + + pictureid_present = ptr[0] & 0x80; + tl0picidx_present = ptr[0] & 0x40; + tid_present = ptr[0] & 0x20; + keyidx_present = ptr[0] & 0x10; + ptr++; + + if (pictureid_present && ptr < pend) { + uint16_t picture_id; + picture_id = ptr[0] & 0x7F; + if ((ptr[0] & 0x80) && ptr + 1 < pend) { + picture_id = (picture_id << 8) | ptr[1]; + ptr++; + } + ptr++; + } + + if (tl0picidx_present && ptr < pend) { + // ignore temporal level zero index + ptr++; + } + + if ((tid_present || keyidx_present) && ptr < pend) { + // ignore KEYIDX + ptr++; + } + } + + if (ptr >= pend) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + // VP8 payload header (3 octets) + if (start_of_vp8_partition) { + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Size0|H| VER |P| + +-+-+-+-+-+-+-+-+ + | Size1 | + +-+-+-+-+-+-+-+-+ + | Size2 | + +-+-+-+-+-+-+-+-+ + */ + // P: Inverse key frame flag. When set to 0, the current frame is a key + // frame. When set to 1, the current frame is an interframe. + // Defined in [RFC6386] + // int keyframe; + // keyframe = ptr[0] & 0x01; // PID == 0 + + // new frame begin + rtp_payload_onframe(helper); + } + + pkt.payload = ptr; + pkt.payloadlen = (int)(pend - ptr); + rtp_payload_write(helper, &pkt); + + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + + return 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_vp8_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_vp8, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c new file mode 100755 index 000000000..3ad3c0bcb --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c @@ -0,0 +1,121 @@ +// RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +// Timestamp: The RTP timestamp indicates the time when the input frame was sampled, at a clock rate of 90 kHz +#define KHz 90 // 90000Hz + +#define N_VP9_HEADER 1 + +struct rtp_encode_vp9_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_vp9_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_vp9_pack_destroy(void *pack) +{ + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_vp9_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_vp9_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + uint8_t vp9_payload_descriptor[1]; + const uint8_t *ptr; + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)pack; + packer->pkt.rtp.timestamp = timestamp; + + r = 0; + ptr = (const uint8_t *)data; + // In non-flexible mode (with the F bit below set to 0), + for (vp9_payload_descriptor[0] = 0x08 /*Start of a layer frame*/; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + N_VP9_HEADER + RTP_FIXED_HEADER) < packer->size + ? bytes + : (packer->size - N_VP9_HEADER - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_VP9_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker bit (M): MUST be set to 1 for the final packet of the highest + // spatial layer frame (the final packet of the super frame), and 0 + // otherwise. Unless spatial scalability is in use for this super + // frame, this will have the same value as the E bit described below. + // Note this bit MUST be set to 1 for the target spatial layer frame + // if a stream is being rewritten to remove higher spatial layers. + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + vp9_payload_descriptor[0] |= (0 == bytes) ? 0x04 : 0; // End of a layer frame. + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + memcpy(rtp + n, vp9_payload_descriptor, N_VP9_HEADER); + memcpy(rtp + n + N_VP9_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + N_VP9_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + vp9_payload_descriptor[0] &= ~0x08; + } + + return r; +} + +struct rtp_payload_encode_t *rtp_vp9_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_vp9_pack_create, + rtp_vp9_pack_destroy, + rtp_vp9_pack_get_info, + rtp_vp9_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c new file mode 100755 index 000000000..5a2db3366 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c @@ -0,0 +1,204 @@ +// RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| VP9 payload descriptor (integer #octets) | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : VP9 pyld hdr | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +| | ++ | +: Bytes 2..N of VP9 payload : +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_decode_vp9(void *p, const void *packet, int bytes) +{ + uint8_t pictureid_present; + uint8_t inter_picture_predicted_layer_frame; + uint8_t layer_indices_preset; + uint8_t flex_mode; + uint8_t start_of_layer_frame; + // uint8_t end_of_layer_frame; + uint8_t scalability_struct_data_present; + + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // VP9 payload descriptor + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |I|P|L|F|B|E|V|-| (REQUIRED) + +-+-+-+-+-+-+-+-+ + */ + pictureid_present = ptr[0] & 0x80; + inter_picture_predicted_layer_frame = ptr[0] & 0x40; + layer_indices_preset = ptr[0] & 0x20; + flex_mode = ptr[0] & 0x10; + start_of_layer_frame = ptr[0] & 0x80; + // end_of_layer_frame = ptr[0] & 0x04; + scalability_struct_data_present = ptr[0] & 0x02; + ptr++; + + if (pictureid_present && ptr < pend) { + // +-+-+-+-+-+-+-+-+ + // I: |M| PICTURE ID | (RECOMMENDED) + // +-+-+-+-+-+-+-+-+ + // M: | EXTENDED PID | (RECOMMENDED) + // +-+-+-+-+-+-+-+-+ + + // uint16_t picture_id; + // picture_id = ptr[0] & 0x7F; + if ((ptr[0] & 0x80) && ptr + 1 < pend) { + // picture_id = (ptr[0] << 8) | ptr[1]; + ptr++; + } + ptr++; + } + + if (layer_indices_preset && ptr < pend) { + // +-+-+-+-+-+-+-+-+ + // L: | T | U | S | D | (CONDITIONALLY RECOMMENDED) + // +-+-+-+-+-+-+-+-+ + // | TL0PICIDX | (CONDITIONALLY REQUIRED) + // +-+-+-+-+-+-+-+-+ + + // ignore Layer indices + if (0 == flex_mode) + ptr++; // TL0PICIDX + ptr++; + } + + if (inter_picture_predicted_layer_frame && flex_mode && ptr < pend) { + // +-+-+-+-+-+-+-+-+ -\ + // P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times + // +-+-+-+-+-+-+-+-+ -/ + + // ignore Reference indices + if (ptr[0] & 0x01) { + if ((ptr[1] & 0x01) && ptr + 1 < pend) + ptr++; + ptr++; + } + ptr++; + } + + if (scalability_struct_data_present && ptr < pend) { + /* + +-+-+-+-+-+-+-+-+ + V: | N_S |Y|G|-|-|-| + +-+-+-+-+-+-+-+-+ -\ + Y: | WIDTH | (OPTIONAL) . + + + . + | | (OPTIONAL) . + +-+-+-+-+-+-+-+-+ . - N_S + 1 times + | HEIGHT | (OPTIONAL) . + + + . + | | (OPTIONAL) . + +-+-+-+-+-+-+-+-+ -/ -\ + G: | N_G | (OPTIONAL) + +-+-+-+-+-+-+-+-+ -\ + N_G:| T |U| R |-|-| (OPTIONAL) . + +-+-+-+-+-+-+-+-+ -\ . - N_G times + | P_DIFF | (OPTIONAL) . - R times . + +-+-+-+-+-+-+-+-+ -/ -/ + */ + uint8_t N_S, Y, G; + N_S = ((ptr[0] >> 5) & 0x07) + 1; + Y = ptr[0] & 0x10; + G = ptr[0] & 0x80; + ptr++; + + if (Y) { + ptr += N_S * 4; + } + + if (G && ptr < pend) { + uint8_t i; + uint8_t N_G = ptr[0]; + ptr++; + + for (i = 0; i < N_G && ptr < pend; i++) { + uint8_t j; + uint8_t R; + + R = (ptr[0] >> 2) & 0x03; + ptr++; + + for (j = 0; j < R && ptr < pend; j++) { + // ignore P_DIFF + ptr++; + } + } + } + } + + if (ptr >= pend) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + if (start_of_layer_frame) { + // new frame begin + rtp_payload_onframe(helper); + } + + pkt.payload = ptr; + pkt.payloadlen = (int)(pend - ptr); + rtp_payload_write(helper, &pkt); + + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + + return 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_vp9_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_vp9, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c new file mode 100755 index 000000000..3a78c93e3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c @@ -0,0 +1,44 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-send-time +/* +Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions +present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we +should see no long term increase in traffic as a result. + +Encoding: Timestamp is in seconds, 24 bit 6.18 fixed point, yielding 64s wraparound and 3.8us resolution (one increment +for each 477 bytes going out on a 1Gbps interface). + +Relation to NTP timestamps: abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff ; NTP timestamp is 32 bits for +whole seconds, 32 bits fraction of second. + +SDP "a= name": "abs-send-time" ; this is also used in client/cloud signaling. +*/ + +int rtp_ext_abs_send_time_parse(const uint8_t *data, int bytes, uint64_t *timestamp) +{ + if (bytes < 3) + return -1; + + *timestamp = ((uint64_t)data[0] << 16) | ((uint64_t)data[1] << 8) | data[2]; + *timestamp = (*timestamp * 1000000) >> 18; + return 0; +} + +int rtp_ext_abs_send_time_write(uint8_t *data, int bytes, uint64_t timestamp) +{ + if (bytes < 3) + return -1; + + timestamp = (timestamp << 18) / 1000000; + data[0] = (uint8_t)(timestamp >> 16); + data[1] = (uint8_t)(timestamp >> 8); + data[2] = (uint8_t)(timestamp >> 0); + return 3; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c new file mode 100755 index 000000000..35cc8e4a7 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c @@ -0,0 +1,107 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time/ +/* +RTP header extension format + +Data layout overview +Data layout of the shortened version of abs-capture-time with a 1-byte header + 8 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=7 | absolute capture timestamp (bit 0-23) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | absolute capture timestamp (bit 24-55) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... (56-63) | + +-+-+-+-+-+-+-+-+ + +Data layout of the extended version of abs-capture-time with a 1-byte header + 16 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=15| absolute capture timestamp (bit 0-23) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | absolute capture timestamp (bit 24-55) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... (56-63) | estimated capture clock offset (bit 0-23) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | estimated capture clock offset (bit 24-55) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... (56-63) | + +-+-+-+-+-+-+-+-+ + +Data layout details + +Absolute capture timestamp +Absolute capture timestamp is the NTP timestamp of when the first frame in a packet was originally captured. This +timestamp MUST be based on the same clock as the clock used to generate NTP timestamps for RTCP sender reports on the +capture system. It's not always possible to do an NTP clock readout at the exact moment of when a media frame is +captured. A capture system MAY postpone the readout until a more convenient time. A capture system SHOULD have known +delays (e.g. from hardware buffers) subtracted from the readout to make the final timestamp as close to the actual +capture time as possible. This field is encoded as a 64-bit unsigned fixed-point number with the high 32 bits for the +timestamp in seconds and low 32 bits for the fractional part. This is also known as the UQ32.32 format and is what the +RTP specification defines as the canonical format to represent NTP timestamps. + +Estimated capture clock offset +Estimated capture clock offset is the sender‘s estimate of the offset between its own NTP clock and the capture system’s +NTP clock. The sender is here defined as the system that owns the NTP clock used to generate the NTP timestamps for the +RTCP sender reports on this stream. The sender system is typically either the capture system or a mixer. This field is +encoded as a 64-bit two’s complement signed fixed-point number with the high 32 bits for the seconds and low 32 bits for +the fractional part. It’s intended to make it easy for a receiver, that knows how to estimate the sender system’s NTP +clock, to also estimate the capture system’s NTP clock: + + Capture NTP Clock = Sender NTP Clock + Capture Clock Offset + +Further details + +Capture system +A receiver MUST treat the first CSRC in the CSRC list of a received packet as if it belongs to the capture system. If +the CSRC list is empty, then the receiver MUST treat the SSRC as if it belongs to the capture system. Mixers SHOULD put +the most prominent CSRC as the first CSRC in a packet’s CSRC list. + +Intermediate systems +An intermediate system (e.g. mixer) MAY adjust these timestamps as needed. It MAY also choose to rewrite the timestamps +completely, using its own NTP clock as reference clock, if it wants to present itself as a capture system for A/V-sync +purposes. + +Timestamp interpolation +A sender SHOULD save bandwidth by not sending abs-capture-time with every RTP packet. It SHOULD still send them at +regular intervals (e.g. every second) to help mitigate the impact of clock drift and packet loss. Mixers SHOULD always +send abs-capture-time with the first RTP packet after changing capture system. A receiver SHOULD memorize the capture +system (i.e. CSRC/SSRC), capture timestamp, and RTP timestamp of the most recently received abs-capture-time packet on +each received stream. It can then use that information, in combination with RTP timestamps of packets without +abs-capture-time, to extrapolate missing capture timestamps. Timestamp interpolation works fine as long as there’s +reasonably low NTP/RTP clock drift. This is not always true. Senders that detect “jumps” between its NTP and RTP clock +mappings SHOULD send abs-capture-time with the first RTP packet after such a thing happening. +*/ + +int rtp_ext_absolute_capture_time_parse(const uint8_t *data, int bytes, struct rtp_ext_absolute_capture_time_t *ext) +{ + assert(bytes == 8 || bytes == 16); + if (bytes != 8 && bytes != 16) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->timestamp = rtp_read_uint64(data); + ext->offset = bytes >= 16 ? rtp_read_uint64(data + 8) : 0; + return 0; +} + +int rtp_ext_absolute_capture_time_write(uint8_t *data, int bytes, const struct rtp_ext_absolute_capture_time_t *ext) +{ + if (bytes < 8) + return -1; + + rtp_write_uint64(data, ext->timestamp); + if (bytes >= 16) + rtp_write_uint64(data + 8, ext->offset); + return bytes >= 16 ? 16 : 8; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c new file mode 100755 index 000000000..b4a769a39 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c @@ -0,0 +1,124 @@ +#include "rtp-ext.h" +#include "rtp-header.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/color-space +/* +Data layout overview + +Data layout without HDR metadata (one-byte RTP header extension) 1-byte header + 4 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L = 3 | primaries | transfer | matrix | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |range+chr.sit. | + +-+-+-+-+-+-+-+-+ + + +Data layout of color space with HDR metadata (two-byte RTP header extension) 2-byte header + 28 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | length=28 | primaries | transfer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | matrix |range+chr.sit. | luminance_max | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | luminance_min | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |primary_r.x and .y | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |primary_g.x and .y | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |primary_b.x and .y | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |white.x and .y | max_content_light_level | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | max_frame_average_light_level | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +Data layout details +The data is written in the following order, Color space information (4 bytes): +- Color primaries value according to ITU-T H.273 Table 2. +- Transfer characteristic value according to ITU-T H.273 Table 3. +- Matrix coefficients value according to ITU-T H.273 Table 4. +- Range and chroma siting as specified at https://www.webmproject.org/docs/container/#colour. Range (range), horizontal +(horz) and vertical (vert) siting are merged to one byte by the operation: (range << 4) + (horz << 2) + vert. + +The extension may optionally include HDR metadata written in the following order, Mastering metadata (20 bytes): +- Luminance max, specified in nits, where 1 nit = 1 cd/m2. (16-bit unsigned integer) +- Luminance min, scaled by a factor of 10000 and specified in the unit 1/10000 nits. (16-bit unsigned integer) +- CIE 1931 xy chromaticity coordinates of the primary red, scaled by a factor of 50000. (2x 16-bit unsigned integers) +- CIE 1931 xy chromaticity coordinates of the primary green, scaled by a factor of 50000. (2x 16-bit unsigned integers) +- CIE 1931 xy chromaticity coordinates of the primary blue, scaled by a factor of 50000. (2x 16-bit unsigned integers) +- CIE 1931 xy chromaticity coordinates of the white point, scaled by a factor of 50000. (2x 16-bit unsigned integers) + +Followed by max light levels (4 bytes): +- Max content light level, specified in nits. (16-bit unsigned integer) +- Max frame average light level, specified in nits. (16-bit unsigned integer) + + +Note, the byte order for all integers is big endian. + +See the standard SMPTE ST 2086 for more information about these entities. + +Notes: Extension should be present only in the last packet of video frames. If attached to other packets it should be +ignored. +*/ + +int rtp_ext_color_space_parse(const uint8_t *data, int bytes, struct rtp_ext_color_space_t *ext) +{ + assert(bytes == 4 || bytes == 28); + if (bytes != 4 && bytes != 28) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->primaries = data[0]; + ext->transfer = data[1]; + ext->matrix = data[2]; + ext->range_chroma_siting = data[3]; + + if (28 == bytes) { + ext->luminance_max = (((uint16_t)data[4]) << 8) | data[5]; + ext->luminance_min = (((uint16_t)data[6]) << 8) | data[7]; + ext->mastering_metadata_primary_red = rtp_read_uint32(data + 8); + ext->mastering_metadata_primary_green = rtp_read_uint32(data + 12); + ext->mastering_metadata_primary_blue = rtp_read_uint32(data + 16); + ext->mastering_metadata_primary_white = rtp_read_uint32(data + 20); + ext->max_content_light_level = rtp_read_uint16(data + 24); + ext->max_frame_average_light_level = rtp_read_uint16(data + 26); + } + + return 0; +} + +int rtp_ext_color_space_write(uint8_t *data, int bytes, const struct rtp_ext_color_space_t *ext) +{ + if (bytes < 4) + return -1; + + data[0] = ext->primaries; + data[1] = ext->transfer; + data[2] = ext->matrix; + data[3] = ext->range_chroma_siting; + + if (bytes >= 28) { + rtp_write_uint16(data + 4, ext->luminance_max); + rtp_write_uint16(data + 6, ext->luminance_min); + rtp_write_uint32(data + 8, ext->mastering_metadata_primary_red); + rtp_write_uint32(data + 12, ext->mastering_metadata_primary_green); + rtp_write_uint32(data + 16, ext->mastering_metadata_primary_blue); + rtp_write_uint32(data + 20, ext->mastering_metadata_primary_white); + rtp_write_uint16(data + 24, ext->max_content_light_level); + rtp_write_uint16(data + 26, ext->max_frame_average_light_level); + } + + return bytes >= 28 ? 28 : 4; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c new file mode 100755 index 000000000..1e98f2c06 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c @@ -0,0 +1,57 @@ +#include "rtp-ext.h" + +// https://datatracker.ietf.org/doc/html/rfc6465 +/* + The audio level header extension carries the level of the audio in + the RTP payload of the packet with which it is associated. This + information is carried in an RTP header extension element as defined + by "A General Mechanism for RTP Header Extensions" [RFC5285]. + + The payload of the audio level header extension element can be + encoded using either the one-byte or two-byte header defined in + [RFC5285]. Figures 2 and 3 show sample audio level encodings with + each of these header formats. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=2 |0| level 1 |0| level 2 |0| level 3 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 2: Sample Audio Level Encoding Using the + One-Byte Header Format + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=3 |0| level 1 |0| level 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| level 3 | 0 (pad) | ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 3: Sample Audio Level Encoding Using the + Two-Byte Header Format +*/ + +int rtp_ext_csrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t levels[], int num) +{ + int i; + if (bytes < 1) + return 0; + + for (i = 0; i < bytes && i < num; i++) + levels[i] = data[0] & 0x7f; + return i; +} + +int rtp_ext_csrc_audio_level_write(uint8_t *data, int bytes, const uint8_t levels[], int num) +{ + int i; + if (bytes < num) + return -1; + + for (i = 0; i < num; i++) + data[i] = levels[i] & 0x7f; + return num; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c new file mode 100755 index 000000000..2db186b4a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c @@ -0,0 +1,67 @@ +#include "rtp-ext.h" +#include +#include +#include + +// https://datatracker.ietf.org/doc/html/draft-ietf-avtext-framemarking-13 +/* +* 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID=? | L=2 |S|E|I|D|B| TID | LID | TL0PICIDX | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + or + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID=? | L=1 |S|E|I|D|B| TID | LID | (TL0PICIDX omitted) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + or + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID=? | L=0 |S|E|I|D|B| TID | (LID and TL0PICIDX omitted) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +int rtp_ext_frame_marking_parse(const uint8_t *data, int bytes, struct rtp_ext_frame_marking_t *ext) +{ + memset(ext, 0, sizeof(*ext)); + + if (bytes-- > 0) { + ext->s = (data[0] >> 7) & 0x01; + ext->e = (data[0] >> 6) & 0x01; + ext->i = (data[0] >> 5) & 0x01; + ext->d = (data[0] >> 4) & 0x01; + ext->b = (data[0] >> 3) & 0x01; + ext->tid = data[0] & 0x07; + } + + if (bytes-- > 0) + ext->lid = data[1]; + + if (bytes > 0) + ext->tl0_pic_idx = data[2]; + + return 0; +} + +int rtp_ext_frame_marking_write(uint8_t *data, int bytes, const struct rtp_ext_frame_marking_t *ext) +{ + int len; + + len = 1 + ((ext->lid || ext->tl0_pic_idx) ? 1 : 0) + (ext->tl0_pic_idx ? 1 : 0); + if (bytes < len) + return -1; + + data[0] = ext->s ? 0x80 : 0x00; + data[0] |= ext->e ? 0x40 : 0x00; + data[0] |= ext->i ? 0x20 : 0x00; + data[0] |= ext->d ? 0x10 : 0x00; + data[0] |= ext->b ? 0x08 : 0x00; + data[0] |= ext->tid; + + if (ext->lid || ext->tl0_pic_idx) + data[1] = (uint8_t)ext->lid; + + if (ext->tl0_pic_idx) + data[2] = (uint8_t)ext->tl0_pic_idx; + + return len; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c new file mode 100755 index 000000000..491420622 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c @@ -0,0 +1,84 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/inband-cn/ +/* +Introduction +Comfort noise (CN) is widely used in real time communication, as it significantly reduces the frequency of RTP packets, +and thus saves the network bandwidth, when participants in the communication are constantly actively speaking. + +One way of deploying CN is through [RFC 3389]. It defines CN as a special payload, which needs to be encoded and decoded +independently from the codec(s) applied to active speech signals. This deployment is referred to as outband CN in this +context. + +Some codecs, for example RFC 6716: Definition of the Opus Audio Codec, implement their own CN schemes. Basically, the +encoder can notify that a CN packet is issued and/or no packet needs to be transmitted. + +Since CN packets have their particularities, cloud and client may need to identify them and treat them differently. +Special treatments on CN packets include but are not limited to + +Upon receiving multiple streams of CN packets, choose only one to relay or mix. +Adapt jitter buffer wisely according to the discontinuous transmission nature of CN packets. +While RTP packets that contain outband CN can be easily identified as they bear a different payload type, inband CN +cannot. Some codecs may be able to extract the information by decoding the packet, but that depends on codec +implementation, not even mentioning that decoding packets is not always feasible. This document proposes using an RTP +header extension to signal the inband CN. + +RTP header extension format +The inband CN extension can be encoded using either the one-byte or two-byte header defined in [RFC 5285]. Figures 1 and +2 show encodings with each of these header formats. + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=0 |N| noise level | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Figure 1. Encoding Using the One-Byte Header Format + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=1 |N| noise level | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Figure 2. Encoding Using the Two-Byte Header Format + +Noise level is an optional data. The bit “N” being 1 indicates that there is a noise level. The noise level is defined +the same way as the audio level in [RFC 6464] and therefore can be used to avoid the Audio Level Header Extension on the +same RTP packet. This also means that this level is defined the same as the noise level in [RFC 3389] and therfore can +be compared against outband CN. + +Further details +The existence of this header extension in an RTP packet indicates that it has inband CN, and therefore it will be used +sparsely, and results in very small transmission cost. + +The end receiver can utilize this RTP header extension to get notified about an upcoming discontinuous transmission. +This can be useful for its jitter buffer management. This RTP header extension signals comfort noise, it can also be +used by audio mixer to mix streams wisely. As an example, it can avoid mixing multiple comfort noises together. + +Cloud may have the benefits of this RTP header extension as an end receiver, if it does transcoding. It may also utilize +this RTP header extension to prioritize RTP packets if it does packet filtering. In both cases, this RTP header +extension should not be encrypted. +*/ + +int rtp_ext_inband_cn_parse(const uint8_t *data, int bytes, uint8_t *level) +{ + assert(1 == bytes); + if (1 != bytes) + return -1; + + *level = data[0] & 0x7f; + return 0; +} + +int rtp_ext_inband_cn_write(uint8_t *data, int bytes, uint8_t level) +{ + if (bytes < 1) + return -1; + data[0] = level & 0x7f; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c new file mode 100755 index 000000000..84b166434 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c @@ -0,0 +1,98 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// SDP “a= name”: “playout-delay” ; this is also used in client/cloud signaling. + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay +/* +Introduction + On WebRTC, the RTP receiver continuously measures inter-packet delay and evaluates packet jitter. Besides this, an +estimated delay for decode and render at the receiver is computed. The jitter buffer, the local time extrapolation and +the predicted render time (based on predicted decode and render time) impact the delay on a frame before it is rendered +at the receiver. + + This document proposes an RTP extension to enable the RTP sender to try and limit the amount of playout delay at the +receiver in a certain range. A minimum and maximum delay from the sender provides guidance on the range over which the +receiver can smooth out rendering. + + Thus, this extension aims to provide the sender’s intent to the receiver on how quickly a frame needs to be rendered. + +The following use cases are addressed by this extension: +- Interactive streaming (gaming, remote access): Interactive streaming is highly sensitive to end-to-end latency and any +delay in render impacts the end-user experience. These use cases prioritize reducing delay over any smoothing done at +the receiver. In these cases, the RTP sender would like to disable all smoothing at receiver (min delay = max delay = 0) +- Movie playback: In some scenarios, the user prefers smooth playback and adaptive delay impacts end-user experience +(audio can speed up and slow down). In these cases the sender would like to have a fixed delay at all times (min delay = +max delay = K) +- Interactive communication: This is the scenarios where the receiver is best suited to adjust the delay adaptively to +minimize latency and at the same time add some smoothing based on jitter prevalent due to network conditions (min delay += K1, max delay = K2) + +MIN and MAX playout delay + The playout delay on a frame represents the amount of delay added to a frame the time it is captured at the sender to +the time it is expected to be rendered at the receiver. Thus playout delay is essentially: + + Playout delay = ExpectedRenderTime(frame) - ExpectedCaptureTime(frame) + + MIN and MAX playout delay in turn represent the minimum and maximum delay that can be seen on a frame. This +restriction range is best effort. The receiver is expected to try and meet the range as best as it can. + + A value of 0 for example is meaningless from the perspective of actually meeting the suggested delay, but it indicates +to the receiver that the frame should be rendered as soon as possible. It is up-to the receiver to decide how to handle +a frame when it arrives too late (i.e., whether to simply drop or hand over for rendering as soon as possible). + +RTP header extension format + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=2 | MIN delay | MAX delay | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +12 bits for Minimum and Maximum delay. This represents a range of 0 - 40950 milliseconds for minimum and maximum (with a +granularity of 10 ms). A granularity of 10 ms is sufficient since we expect the following typical use cases: +- 0 ms: Certain gaming scenarios (likely without audio) where we will want to play the frame as soon as possible. Also, +for remote desktop without audio where rendering a frame asap makes sense +- 100/150/200 ms: These could be the max target latency for interactive streaming use cases depending on the actual +application (gaming, remoting with audio, interactive scenarios) +- 400 ms: Application that want to ensure a network glitch has very little chance of causing a freeze can start with a +minimum delay target that is high enough to deal with network issues. Video streaming is one example. + +The header is attached to the RTP packet by the RTP sender when it needs to change the min and max smoothing delay at +the receiver. Once the sender is informed that at least one RTP packet which has the min and max details is delivered, +it MAY stop providing details on all further RTP packets until another change warrants communicating the details to the +receiver again. This is done as follows: + +RTCP feedback to RTP sender includes the highest sequence number that was seen on the RTP receiver. The RTP sender can +track the sequence number on the packet that first had the playout delay extension and then stop sending the extension +once the received sequence number is greater than the sequence number on the first packet containing the current values +playout delay in this extension. +*/ + +int rtp_ext_playout_delay_parse(const uint8_t *data, int bytes, struct rtp_ext_playout_delay_t *ext) +{ + assert(3 == bytes); + if (3 != bytes) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->min_delay = (((uint16_t)data[0]) << 4) | ((data[1] >> 4) & 0x0F); + ext->max_delay = (((uint16_t)data[1] & 0x0F) << 8) | data[2]; + return 0; +} + +int rtp_ext_playout_delay_write(uint8_t *data, int bytes, const struct rtp_ext_playout_delay_t *ext) +{ + if (bytes < 3) + return -1; + + data[0] = (uint8_t)(ext->min_delay >> 4) & 0xFF; + data[1] = (uint8_t)(ext->min_delay & 0x0F) << 4; + data[2] = (uint8_t)((ext->max_delay >> 8) & 0x0F); + data[3] = (uint8_t)(ext->max_delay & 0xFF); + return 3; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c new file mode 100755 index 000000000..710a75b15 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c @@ -0,0 +1,64 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +int rtp_ext_string_parse(const uint8_t *data, int bytes, char *v, int n) +{ + if (n < bytes + 1) + return -1; + + memcpy(v, data, bytes); + v[bytes] = 0; + return 0; +} + +int rtp_ext_string_write(uint8_t *data, int bytes, const char *v, int n) +{ + if (bytes < n) + return -1; + memcpy(data, v, n); + return n; +} + +// https://datatracker.ietf.org/doc/html/rfc8843#section-15.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | MID=15 | length | identification-tag ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +// int rtp_ext_sdes_mid_parse(const uint8_t* data, int bytes, const char* mid, int len) +//{ +// return rtp_ext_string_parse(data, bytes, mid, len); +// } + +// https://datatracker.ietf.org/doc/html/rfc8852#section-4 +// 1. RtpStreamId and RepairedRtpStreamId are limited to a total of 255 octets in length. +// 2. RtpStreamId and RepairedRtpStreamId are constrained to contain only alphanumeric characters. +// For avoidance of doubt, the only allowed byte values for these IDs are decimal 48 through 57, 65 through 90, and +//97 through 122. +/* + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |RtpStreamId=12 | length | RtpStreamId ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Repaired...=13 | length | RepairRtpStreamId ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +// int rtp_ext_sdes_rtp_stream_id(const uint8_t* data, int bytes, char* id, int len) +//{ +// return rtp_ext_string_parse(data, bytes, id, len); +// } +// +// int rtp_ext_sdes_repaired_rtp_stream_id(const uint8_t* data, int bytes, char* id, int len) +//{ +// return rtp_ext_string_parse(data, bytes, id, len); +// } diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c new file mode 100755 index 000000000..7d9ff9b68 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c @@ -0,0 +1,52 @@ +#include "rtp-ext.h" + +// https://datatracker.ietf.org/doc/html/rfc6464 +/* + The audio level header extension carries the level of the audio in + the RTP [RFC3550] payload of the packet with which it is associated. + This information is carried in an RTP header extension element as + defined by "A General Mechanism for RTP Header Extensions" [RFC5285]. + + The payload of the audio level header extension element can be + encoded using either the one-byte or two-byte header defined in + [RFC5285]. Figures 1 and 2 show sample audio level encodings with + each of these header formats. + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=0 |V| level | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 1: Sample Audio Level Encoding Using the + One-Byte Header Format + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=1 |V| level | 0 (pad) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 2: Sample Audio Level Encoding Using the + Two-Byte Header Format +*/ + +int rtp_ext_ssrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t *activity, uint8_t *level) +{ + if (bytes < 1) + return -1; + + *activity = (data[0] & 0x80) ? 1 : 0; + *level = data[0] & 0x7f; + return 0; +} + +int rtp_ext_ssrc_audio_level_write(uint8_t *data, int bytes, uint8_t activity, uint8_t level) +{ + if (bytes < 1) + return -1; + + data[0] = (activity ? 0x80 : 0) | level; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c new file mode 100755 index 000000000..5dbae4a77 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c @@ -0,0 +1,35 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://datatracker.ietf.org/doc/html/rfc5450 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=2 | transmission offset | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +int rtp_ext_toffset_parse(const uint8_t *data, int bytes, uint32_t *timestamp) +{ + if (bytes < 3) + return -1; + + *timestamp = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2]; + return 0; +} + +int rtp_ext_toffset_write(uint8_t *data, int bytes, uint32_t timestamp) +{ + if (bytes < 3) + return -1; + + data[0] = (uint8_t)(timestamp >> 16); + data[1] = (uint8_t)(timestamp >> 8); + data[2] = (uint8_t)(timestamp >> 0); + return 3; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c new file mode 100755 index 000000000..a2d71158f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c @@ -0,0 +1,69 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/transport-wide-cc-02/ +/* +RTP header extension format +Data layout overview +Data layout of transport-wide sequence number 1-byte header + 2 bytes of data: + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L=1 |transport-wide sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Data layout of transport-wide sequence number and optional feedback request 1-byte header + 4 bytes of data: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L=3 |transport-wide sequence number |T| seq count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |seq count cont.| + +-+-+-+-+-+-+-+-+ + +Data layout details +The data is written in the following order, +- transport-wide sequence number (16-bit unsigned integer) +- feedback request (optional) (16-bit unsigned integer) + If the extension contains two extra bytes for feedback request, this means that a feedback packet should be generated +and sent immediately. The feedback request consists of a one-bit field giving the flag value T and a 15-bit field giving +the sequence count as an unsigned number. + - If the bit T is set the feedback packet must contain timing information. + - seq count specifies how many packets of history that should be included in the feedback packet. If seq count is +zero no feedback should be be generated, which is equivalent of sending the two-byte extension above. This is added as +an option to allow for a fixed packet header size. +*/ + +int rtp_ext_transport_wide_cc_parse(const uint8_t *data, int bytes, struct rtp_ext_transport_wide_cc_t *ext) +{ + assert(bytes == 2 || bytes == 4); + if (bytes < 2) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->seq = rtp_read_uint16(data); + if (bytes >= 4) { + ext->t = (data[2] >> 7) & 0x01; + ext->count = rtp_read_uint16(data + 2) & 0x7FFF; + } + return 0; +} + +int rtp_ext_transport_wide_cc_write(uint8_t *data, int bytes, const struct rtp_ext_transport_wide_cc_t *ext) +{ + if (bytes < 2) + return -1; + + rtp_write_uint16(data, (uint16_t)ext->seq); + if (bytes >= 4) + rtp_write_uint16(data + 2, (uint16_t)(ext->t << 15) | (ext->count & 0x7FFF)); + + return bytes >= 4 ? 4 : 2; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c new file mode 100755 index 000000000..399cf4d03 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c @@ -0,0 +1,40 @@ +#include "rtp-ext.h" +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-content-type +/* +SDP "a= name": "video-content-type" ; this is also used in client/cloud signaling. + +Wire format: 1-byte extension, 1 bytes of data. total 2 bytes extra per packet (plus shared 4 bytes for all extensions +present: 2 byte magic word 0xBEDE, 2 byte # of extensions). + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=0 | Content type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Values: +- 0x00: Unspecified. Default value. Treated the same as an absence of an extension. +- 0x01: Screenshare. Video stream is of a screenshare type. + +Notes: Extension shoud be present only in the last packet of key-frames. If attached to other packets it should be +ignored. If extension is absent, Unspecified value is assumed. +*/ + +int rtp_ext_video_content_type_parse(const uint8_t *data, int bytes, uint8_t *ext) +{ + assert(bytes == 1); + *ext = bytes > 0 ? data[0] : 0; + return 0; +} + +int rtp_ext_video_content_type_write(uint8_t *data, int bytes, uint8_t ext) +{ + if (bytes < 1) + return -1; + + data[0] = ext; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c new file mode 100755 index 000000000..de311ebd4 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c @@ -0,0 +1,33 @@ +#include "rtp-ext.h" +#include "rtp-util.h" + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-frame-tracking-id/ +/* +Data layout overview + 1-byte header + 2 bytes of data: + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L=1 | video-frame-tracking-id | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Notes: The extension shoud be present only in the first packet of each frame. If attached to other packets it can be +ignored. +*/ + +int rtp_ext_video_frame_tracking_id_parse(const uint8_t *data, int bytes, uint16_t *id) +{ + if (bytes < 2) + return -1; + *id = rtp_read_uint16(data); + return 0; +} + +int rtp_ext_video_frame_tracking_id_write(uint8_t *data, int bytes, uint16_t id) +{ + if (bytes < 1) + return -1; + rtp_write_uint16(data, id); + return 2; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c new file mode 100755 index 000000000..b2f1b761a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c @@ -0,0 +1,85 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00/ +/* +In a conference scenario, a video from a single sender may be received by several recipients with different downlink +bandwidth constraints and UI requirements. To allow this, a sender can send video with several scalability layers and a +middle box can choose a layer to relay for each receiver. + +This extension support temporal layers, multiple spatial layers sent on a single rtp stream (SVC), or independent +spatial layers sent on multiple rtp streams (Simulcast). + +RTP header extension format +Data layout +// +-+-+-+-+-+-+-+-+ +// |RID| NS| sl_bm | +// +-+-+-+-+-+-+-+-+ +// Spatial layer bitmask |sl0_bm |sl1_bm | +// up to 2 bytes |---------------| +// when sl_bm == 0 |sl2_bm |sl3_bm | +// +-+-+-+-+-+-+-+-+ +// Number of temporal |#tl|#tl|#tl|#tl| +// layers per spatial layer :---------------: +// up to 4 bytes | ... | +// +-+-+-+-+-+-+-+-+ +// Target bitrate in kpbs | | +// per temporal layer : ... : +// leb128 encoded | | +// +-+-+-+-+-+-+-+-+ +// Resolution and framerate | | +// 5 bytes per spatial layer + width-1 for + +// (optional) | rid=0, sid=0 | +// +---------------+ +// | | +// + height-1 for + +// | rid=0, sid=0 | +// +---------------+ +// | max framerate | +// +-+-+-+-+-+-+-+-+ +// : ... : +// +-+-+-+-+-+-+-+-+ +RID: RTP stream index this allocation is sent on, numbered from 0. 2 bits. + +NS: Number of RTP streams - 1. 2 bits, thus allowing up-to 4 RTP streams. + +sl_bm: BitMask of the active Spatial Layers when same for all RTP streams or 0 otherwise. 4 bits thus allows up to 4 +spatial layers per RTP streams. + +slX_bm: BitMask of the active Spatial Layers for RTP stream with index=X. byte-aligned. When NS < 2, takes one byte, +otherwise uses two bytes. + +#tl: 2-bit value of number of temporal layers-1, thus allowing up-to 4 temporal layer per spatial layer. One per spatial +layer per RTP stream. values are stored in (RTP stream id, spatial id) ascending order. zero-padded to byte alignment. + +Target bitrate in kbps. Values are stored using leb128 encoding. one value per temporal layer. values are stored in (RTP +stream id, spatial id, temporal id) ascending order. All bitrates are total required bitrate to receive the +corresponding layer, i.e. in simulcast mode they include only corresponding spatial layer, in full-svc all lower spatial +layers are included. All lower temporal layers are also included. + +Resolution and framerate. Optional. Presence is inferred from the rtp header extension size. Encoded (width - 1), +16-bit, (height - 1), 16-bit, max frame rate 8-bit per spatial layer per RTP stream. Values are stored in (RTP stream +id, spatial id) ascending order. + +An empty layer allocation (i.e nothing sent on ssrc) is encoded as special case with a single 0 byte. +*/ + +int rtp_ext_video_layers_allocation_parse(const uint8_t *data, int bytes, struct rtp_ext_video_layers_allocation_t *ext) +{ + memset(ext, 0, sizeof(*ext)); + assert(0); + return -1; +} + +int rtp_ext_video_layers_allocation_write(uint8_t *data, int bytes, const struct rtp_ext_video_layers_allocation_t *ext) +{ + if (bytes < 4) + return -1; + return -1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c new file mode 100755 index 000000000..03b8227ed --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c @@ -0,0 +1,89 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://www.arib.or.jp/english/html/overview/doc/STD-T63V12_00/5_Appendix/Rel13/26/26114-d30.pdf +/* +7.4.5 Coordination of Video Orientation +Coordination of Video Orientation consists in signalling of the current orientation of the image captured on the sender +side to the receiver for appropriate rendering and displaying. When CVO is succesfully negotiated it shall be signalled +by the MTSI client. The signalling of the CVO uses RTP Header Extensions as specified in IETF RFC 5285 [95]. The +one-byte form of the header should be used. CVO information for a 2 bit granularity of Rotation (corresponding to +urn:3gpp:video-orientation) is carried as a byte formatted as follows: + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=0 |0 0 0 0 C F R R| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Bit# 7 6 5 4 3 2 1 0(LSB) + Definition 0 0 0 0 C F R1 R0 + +With the following definitions: +C = Camera: indicates the direction of the camera used for this video stream. It can be used by the MTSI client in + receiver to e.g. display the received video differently depending on the source camera. + 0: Front-facing camera, facing the user. If camera direction is unknown by the sending MTSI client in the terminal +then this is the default value used. 1: Back-facing camera, facing away from the user. F = Flip: indicates a horizontal +(left-right flip) mirror operation on the video as sent on the link. 0: No flip operation. If the sending MTSI client in +terminal does not know if a horizontal mirror operation is necessary, then this is the default value used. 1: Horizontal +flip operation R1, R0 = Rotation: indicates the rotation of the video as transmitted on the link. The receiver should +rotate the video to compensate that rotation. E.g. a 90бу Counter Clockwise rotation should be compensated by the +receiver with a 90бу Clockwise rotation prior to displaying. + +Table 7.2: Rotation signalling for 2 bit granularity +R1 R0 Rotation of the video as sent on the link Rotation on the receiver before display +0 0 0бу rotation None +0 1 90бу Counter Clockwise (CCW) rotation or 90бу CW rotation + 270бу Clockwise (CW) rotation +1 0 180бу CCW rotation or 180бу CW rotation 180бу CW rotation +1 1 270бу CCW rotation or 90бу CW rotation 90бу CCW rotation + +CVO information for a higher granularity of Rotation (corresponding to urn:3GPP:video-orientation:6) is carried as a +byte formatted as follows: + Bit# 7 6 5 4 3 2 1 0(LSB) + Definition R5 R4 R3 R2 C F R1 R0 + +where C and F are as defined above and the bits R5,R4,R3,R2,R1,R0 represent the Rotation, which indicates the +rotation of the video as transmitted on the link. Table 7.3 describes the rotation to be applied by the receiver based +on the rotation bits. + +Table 7.3: Rotation signalling for 6 bit granularity +R1 R0 R5 R4 R3 R2 Rotation of the video as Rotation on the receiver + sent on the link before display +0 0 0 0 0 0 0бу rotation None +0 0 0 0 0 1 (360/64)бу Counter Clockwise (360/64)бу CW rotation + (CCW) rotation +0 0 0 0 1 0 (2*360/64)бу CCW rotation (2*360/64)бу CW rotation +. . . . . . . . +. . . . . . . . +. . . . . . . . +1 1 1 1 1 0 (62*360/64)бу CCW rotation (2*360/64)бу CCW rotation +1 1 1 1 1 1 (63*360/64)бу CCW rotation (360/64)бу CCW rotation +*/ + +int rtp_ext_video_orientation_parse(const uint8_t *data, int bytes, struct rtp_ext_video_orientation_t *ext) +{ + assert(1 == bytes); + if (bytes < 1) + return -1; + + ext->camera = (data[0] >> 3) & 0x01; + ext->flip = (data[0] >> 2) & 0x01; + ext->rotaion = (data[0] & 0x03) * 90; + return 0; +} + +int rtp_ext_video_orientation_write(uint8_t *data, int bytes, const struct rtp_ext_video_orientation_t *ext) +{ + if (bytes < 1) + return -1; + + data[0] = ext->camera ? 0x04 : 0; + data[0] |= ext->flip ? 0x03 : 0; + data[0] |= (ext->rotaion / 90) & 0x03; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c new file mode 100755 index 000000000..2814ba8ec --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c @@ -0,0 +1,81 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-timing +/* +SDP "a= name": "video-timing" ; this is also used in client/cloud signaling. + +Wire format: 1-byte extension, 13 bytes of data. Total 14 bytes extra per packet (plus 1-3 padding byte in some cases, +plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=12| flags | encode start ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | encode finish ms delta | packetizer finish ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | pacer exit ms delta | network timestamp ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | network2 timestamp ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +First byte is a flags field. Defined flags: +- 0x01 - extension is set due to timer. +- 0x02 - extension is set because the frame is larger than usual. +Both flags may be set at the same time. All remaining 6 bits are reserved and should be ignored. + +Next, 6 timestamps are stored as 16-bit values in big-endian order, representing delta from the capture time of a packet +in ms. Timestamps are, in order: +- Encode start. +- Encode finish. +- Packetization complete. +- Last packet left the pacer. +- Reserved for network. +- Reserved for network (2). + +Pacer timestamp should be updated inside the RTP packet by pacer component when the last packet (containing the +extension) is sent to the network. Last two, reserved timstamps, are not set by the sender but are reserved in packet +for any in-network RTP stream processor to modify. + +Notes: Extension shoud be present only in the last packet of video frames. If attached to other packets it should be +ignored. +*/ + +int rtp_ext_video_timing_parse(const uint8_t *data, int bytes, struct rtp_ext_video_timing_t *ext) +{ + assert(bytes == 13); + if (bytes < 13) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->flags = data[0]; + ext->encode_start = rtp_read_uint16(data + 1); + ext->encode_finish = rtp_read_uint16(data + 3); + ext->packetization_complete = rtp_read_uint16(data + 5); + ext->last_packet_left_the_pacer = rtp_read_uint16(data + 7); + ext->network_timestamp = rtp_read_uint16(data + 9); + ext->network_timestamp2 = rtp_read_uint16(data + 11); + return 0; +} + +int rtp_ext_video_timing_write(uint8_t *data, int bytes, const struct rtp_ext_video_timing_t *ext) +{ + if (bytes < 13) + return -1; + + data[0] = (uint8_t)ext->flags; + rtp_write_uint16(data + 1, ext->encode_start); + rtp_write_uint16(data + 3, ext->encode_finish); + rtp_write_uint16(data + 5, ext->packetization_complete); + rtp_write_uint16(data + 7, ext->last_packet_left_the_pacer); + rtp_write_uint16(data + 9, ext->network_timestamp); + rtp_write_uint16(data + 11, ext->network_timestamp2); + return 13; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c new file mode 100755 index 000000000..7f1a9620f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c @@ -0,0 +1,260 @@ +#include "rtp-ext.h" +#include "rtp-header.h" +#include +#include +#include +#include + +static const struct rtp_ext_uri_t sc_rtpexts[] = { + {RTP_HDREXT_PADDING, ""}, + + // https://datatracker.ietf.org/doc/html/rfc6464 + {RTP_HDREXT_SSRC_AUDIO_LEVEL_ID, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"}, + // https://datatracker.ietf.org/doc/html/rfc6465 + {RTP_HDREXT_CSRC_AUDIO_LEVEL_ID, "urn:ietf:params:rtp-hdrext:csrc-audio-level"}, + + // https://datatracker.ietf.org/doc/html/draft-ietf-avtext-framemarking-13 + //{RTP_HDREXT_FRAME_MARKING_ID, "http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07"}, + {RTP_HDREXT_FRAME_MARKING_ID, "urn:ietf:params:rtp-hdrext:framemarking"}, + + // https://datatracker.ietf.org/doc/html/rfc8843#section-16.2 + {RTP_HDREXT_SDES_MID_ID, "urn:ietf:params:rtp-hdrext:sdes:mid"}, + + // https://datatracker.ietf.org/doc/html/rfc8852#section-4 + {RTP_HDREXT_SDES_RTP_STREAM_ID, "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"}, + {RTP_HDREXT_SDES_REPAIRED_RTP_STREAM_ID, "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"}, + + // https://datatracker.ietf.org/doc/html/rfc5450 + {RTP_HDREXT_TOFFSET_ID, "urn:ietf:params:rtp-hdrext:toffset"}, + + // https://www.arib.or.jp/english/html/overview/doc/STD-T63V12_00/5_Appendix/Rel13/26/26114-d30.pdf + {RTP_HDREXT_VIDEO_ORIENTATION_ID, "urn:3gpp:video-orientation"}, + + // // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-send-time + {RTP_HDREXT_ABSOLUTE_SEND_TIME_ID, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time/ + {RTP_HDREXT_ABSOLUTE_CAPTURE_TIME_ID, "http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/transport-wide-cc-02/ + {RTP_HDREXT_TRANSPORT_WIDE_CC_ID_01, "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"}, + {RTP_HDREXT_TRANSPORT_WIDE_CC_ID, "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-timing + {RTP_HDREXT_VIDEO_TIMING_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-timing"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay + {RTP_HDREXT_PLAYOUT_DELAY_ID, "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"}, + + {RTP_HDREXT_ONE_BYTE_RESERVED, ""}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/color-space + {RTP_HDREXT_COLOR_SPACE_ID, "http://www.webrtc.org/experiments/rtp-hdrext/color-space"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-content-type + {RTP_HDREXT_VIDEO_CONTENT_TYPE_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/inband-cn/ + {RTP_HDREXT_INBAND_CN_ID, "http://www.webrtc.org/experiments/rtp-hdrext/inband-cn"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-frame-tracking-id/ + {RTP_HDREXT_VIDEO_FRAME_TRACKING_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-frame-tracking-id"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00/ + {RTP_HDREXT_VIDEO_LAYERS_ALLOCATION_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00"}, + + //{RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_00, + //"http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00"}, + //{RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_02, + //"http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-02"}, + + //{RTP_HDREXT_ENCRYPT, "urn:ietf:params:rtp-hdrext:encrypt"}, + + {0, NULL}, +}; + +const struct rtp_ext_uri_t *rtp_ext_list() +{ + return sc_rtpexts; +} + +const struct rtp_ext_uri_t *rtp_ext_find_uri(const char *uri) +{ + int i; + for (i = 0; i < sizeof(sc_rtpexts) / sizeof(sc_rtpexts[0]); i++) { + if (0 == strcmp(sc_rtpexts[i].uri, uri)) + return &sc_rtpexts[i]; + } + return NULL; +} + +static int rtp_ext_read_one_byte(const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]) +{ + int off; + uint8_t id; + uint8_t len; + + for (off = 0; off < bytes; off += len + 1) { + id = data[off] >> 4; + len = (data[off] & 0x0f) + 1; + + if (bytes > 0xFFFF || off + len > bytes || (RTP_HDREXT_PADDING == id && 1 != len)) + return -1; // invalid + + if (RTP_HDREXT_PADDING == data[off]) + continue; + else if (id == RTP_HDREXT_ONE_BYTE_RESERVED) + break; // one-byte header extension reserver id + + exts[id].id = id; + exts[id].len = len; + exts[id].off = off + 1; // data only + } + + return 0; +} + +static int rtp_ext_write_one_byte(const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, uint8_t *data, + int bytes) +{ + int i, off; + for (i = off = 0; i < count && off < bytes; off += exts[i++].len) { + if (RTP_HDREXT_PADDING == exts[i].id) { + assert(exts[i].len == 0); + continue; + } + + // 15: one-byte header extension reserver id + if (exts[i].id >= RTP_HDREXT_ONE_BYTE_RESERVED || exts[i].len < 1 || exts[i].len > 16 || + (int)exts[i].len + off >= bytes) { + assert(0); + return -EINVAL; + } + + data[off] = ((uint8_t)exts[i].id << 4) | ((uint8_t)exts[i].len - 1); + memcpy(data + off + 1, extension + exts[i].off, exts[i].len); + } + + if (i < count || ((off % 4 != 0) && (off + 3) / 4 * 4 >= bytes)) + return -E2BIG; + + while (off % 4 != 0) + data[off++] = RTP_HDREXT_PADDING; + return off; +} + +static int rtp_ext_read_two_byte(const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]) +{ + int off; + uint8_t id; + uint8_t len; + + for (off = 0; off < bytes; off += len) { + id = data[off++]; + if (RTP_HDREXT_PADDING == id) { + len = 0; + continue; + } + + if (off >= bytes) + return -EINVAL; + + len = data[off++]; + if (off + len > bytes) + return -EINVAL; + ; // invalid + + exts[id].id = id; + exts[id].len = len; + exts[id].off = off; // data only + } + + return 0; +} + +static int rtp_ext_write_two_byte(const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, uint8_t *data, + int bytes) +{ + int i, off; + for (i = off = 0; i < count && off < bytes; off += exts[i++].len) { + if (RTP_HDREXT_PADDING == exts[i].id) { + assert(exts[i].len == 0); + continue; + } + + if ((int)exts[i].len + off + 2 >= bytes) { + assert(0); + return -EINVAL; + } + + data[off++] = (uint8_t)exts[i].id; + data[off++] = (uint8_t)exts[i].len; + memcpy(data + off, extension + exts[i].off, exts[i].len); + } + + if (i < count || ((off % 4 != 0) && (off + 3) / 4 * 4 >= bytes)) + return -E2BIG; + + while (off % 4 != 0) + data[off++] = RTP_HDREXT_PADDING; + return off; +} + +int rtp_ext_read(uint16_t profile, const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]) +{ + // caller to do + // memset(exts, 0, sizeof(exts)); + + if (RTP_HDREXT_PROFILE_ONE_BYTE == profile) + return rtp_ext_read_one_byte(data, bytes, exts); + else if (RTP_HDREXT_PROFILE_TWO_BYTE == (profile & RTP_HDREXT_PROFILE_TWO_BYTE_FILTER)) + return rtp_ext_read_two_byte(data, bytes, exts); + + return 0; // ignore +} + +int rtp_ext_write(uint16_t profile, const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, + uint8_t *data, int bytes) +{ + int i; + if (0 == profile) { + profile = RTP_HDREXT_PROFILE_ONE_BYTE; + for (i = 0; i < count; i++) { + // 15: one-byte header extension reserver id + if (exts[i].len >= 16 || RTP_HDREXT_ONE_BYTE_RESERVED == exts[i].id) + profile = RTP_HDREXT_PROFILE_TWO_BYTE; + } + } + + if (RTP_HDREXT_PROFILE_ONE_BYTE == profile) + return rtp_ext_write_one_byte(extension, exts, count, data, bytes); + else if (RTP_HDREXT_PROFILE_TWO_BYTE == (profile & RTP_HDREXT_PROFILE_TWO_BYTE_FILTER)) + return rtp_ext_write_two_byte(extension, exts, count, data, bytes); + + return -1; // ignore +} + +#if defined(DEBUG) || defined(_DEBUG) +static void rtp_ext_read_onebyte_test(void) +{ + const uint8_t data[] = {0x22, 0xca, 0x4e, 0x36, 0x31, 0x00, 0x01, 0x40, 0x30, 0x10, 0xb2, 0x00}; + struct rtp_ext_data_t exts[256]; + int i; + memset(exts, 0, sizeof(exts)); + assert(0 == rtp_ext_read(RTP_HDREXT_PROFILE_ONE_BYTE, data, sizeof(data), exts)); + assert(exts[2].len == 3 && exts[3].len == 2 && exts[4].len == 1 && exts[1].len == 1); + for (i = 0; i < sizeof(exts) / sizeof(exts[0]); i++) { + if (i == 1 || i == 2 || i == 3 || i == 4) + continue; + assert(exts[i].id == 0 && exts[i].len == 0); + } +} + +static void rtp_ext_read_twobyte_test(void) {} + +void rtp_ext_read_test(void) +{ + rtp_ext_read_onebyte_test(); + rtp_ext_read_twobyte_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-app.c b/src/tuya_p2p/lib_rtp/src/rtcp-app.c new file mode 100755 index 000000000..37f6d512e --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-app.c @@ -0,0 +1,54 @@ +// RFC3550 6.7 APP: Application-Defined RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_app_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + struct rtcp_msg_t msg; + // struct rtp_member *member; + + if (bytes < 8) // RTCP header + SSRC + name + { + assert(0); + return; + } + + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_APP; + + // member = rtp_member_fetch(ctx, msg.ssrc); + // if(!member) return; // error + + msg.u.app.subtype = header->rc; + memcpy(msg.u.app.name, ptr + 4, 4); + msg.u.app.data = (void *)(ptr + 8); + msg.u.app.bytes = (int)bytes - 8; + + ctx->handler.on_rtcp(ctx->cbparam, &msg); +} + +int rtcp_app_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes, const char name[4], const void *app, int len) +{ + rtcp_header_t header; + + if (bytes >= 12 + (len + 3) / 4 * 4) { + header.v = 2; + header.p = 0; + header.pt = RTCP_APP; + header.rc = 0; + header.length = (uint16_t)(2 + (len + 3) / 4); + nbo_write_rtcp_header(ptr, &header); + + nbo_w32(ptr + 4, ctx->self->ssrc); + memcpy(ptr + 8, name, 4); + + if (len > 0) { + memcpy(ptr + 12, app, len); + if (0 != len % 4) + memset(ptr + 12 + len, 0, 4 - (len % 4)); + } + } + + return 12 + (len + 3) / 4 * 4; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-bye.c b/src/tuya_p2p/lib_rtp/src/rtcp-bye.c new file mode 100755 index 000000000..db309c447 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-bye.c @@ -0,0 +1,64 @@ +// RFC3550 6.6 BYE: Goodbye RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_bye_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + struct rtcp_msg_t msg; + + assert(bytes >= header->rc * 4); + if (header->rc < 1 || header->rc * 4 > bytes) + return; // A count value of zero is valid, but useless (p43) + + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_BYE; + + rtp_member_list_delete(ctx->members, msg.ssrc); + rtp_member_list_delete(ctx->senders, msg.ssrc); + + if (bytes > header->rc * 4 + 1) { + msg.u.bye.bytes = ptr[header->rc * 4]; + msg.u.bye.reason = ptr + header->rc * 4 + 1; + + if (1 + msg.u.bye.bytes + header->rc * 4 > bytes) { + assert(0); + msg.u.bye.bytes = 0; + msg.u.bye.reason = NULL; + } + } else { + msg.u.bye.bytes = 0; + msg.u.bye.reason = NULL; + } + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + + // other SSRC/CSRC + for (i = 0; i < header->rc /*source count*/; i++) { + msg.ssrc = nbo_r32(ptr + 4 + i * 4); + rtp_member_list_delete(ctx->members, msg.ssrc); + rtp_member_list_delete(ctx->senders, msg.ssrc); + ctx->handler.on_rtcp(ctx->cbparam, &msg); + } +} + +int rtcp_bye_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + rtcp_header_t header; + + if (bytes < 8) + return 8; + + header.v = 2; + header.p = 0; + header.pt = RTCP_BYE; + header.rc = 1; // self only + header.length = 1; + nbo_write_rtcp_header(ptr, &header); + + nbo_w32(ptr + 4, ctx->self->ssrc); + + assert(8 == (header.length + 1) * 4); + return 8; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-interval.c b/src/tuya_p2p/lib_rtp/src/rtcp-interval.c new file mode 100755 index 000000000..efb6dd3f5 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-interval.c @@ -0,0 +1,96 @@ +// RFC3550 A.7 Computing the RTCP Transmission Interval (p74) + +#define _CRT_RAND_S +#include + +#if defined(OS_WINDOWS) +#include +double drand48(void) +{ + unsigned int v = 0; + rand_s(&v); + return (v * 1.0) / UINT_MAX; +} +#endif + +double rtcp_interval(int members, int senders, double rtcp_bw, int we_sent, double avg_rtcp_size, int initial) +{ + /* + * Minimum average time between RTCP packets from this site (in + * seconds). This time prevents the reports from `clumping' when + * sessions are small and the law of large numbers isn't helping + * to smooth out the traffic. It also keeps the report interval + * from becoming ridiculously small during transient outages like + * a network partition. + */ + double const RTCP_MIN_TIME = 5.0; + + /* + * Fraction of the RTCP bandwidth to be shared among active + * senders. (This fraction was chosen so that in a typical + * session with one or two active senders, the computed report + * time would be roughly equal to the minimum report time so that + * we don't unnecessarily slow down receiver reports.) The + * receiver fraction must be 1 - the sender fraction. + */ + double const RTCP_SENDER_BW_FRACTION = 0.25; + double const RTCP_RCVR_BW_FRACTION = (1 - RTCP_SENDER_BW_FRACTION); + + /* + * To compensate for "timer reconsideration" converging to a + * value below the intended average. + */ + double const COMPENSATION = 2.71828 - 1.5; + double t; /* interval */ + double rtcp_min_time = RTCP_MIN_TIME; + int n; /* no. of members for computation */ + + /* + * Very first call at application start-up uses half the min + * delay for quicker notification while still allowing some time + * before reporting for randomization and to learn about other + * sources so the report interval will converge to the correct + * interval more quickly. + */ + if (initial) { + rtcp_min_time /= 2; + } + + /* + * Dedicate a fraction of the RTCP bandwidth to senders unless + * the number of senders is large enough that their share is + * more than that fraction. + */ + n = members; + if (senders <= members * RTCP_SENDER_BW_FRACTION) { + if (we_sent) { + rtcp_bw *= RTCP_SENDER_BW_FRACTION; + n = senders; + } else { + rtcp_bw *= RTCP_RCVR_BW_FRACTION; + n -= senders; + } + } + + /* + * The effective number of sites times the average packet size is + * the total number of octets sent when each site sends a report. + * Dividing this by the effective bandwidth gives the time + * interval over which those packets must be sent in order to + * meet the bandwidth target, with a minimum enforced. In that + * time interval we send one report so this time is also our + * average time between reports. + */ + t = avg_rtcp_size * n / rtcp_bw; + if (t < rtcp_min_time) + t = rtcp_min_time; + + /* + * To avoid traffic bursts from unintended synchronization with + * other sites, we then pick our actual next report interval as a + * random number uniformly distributed between 0.5*t and 1.5*t. + */ + t = t * (drand48() + 0.5); + t = t / COMPENSATION; + return t; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-psfb.c b/src/tuya_p2p/lib_rtp/src/rtcp-psfb.c new file mode 100755 index 000000000..396eacd80 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-psfb.c @@ -0,0 +1,845 @@ +#include "rtp-internal.h" +#include "rtp-util.h" +#include + +static int rtcp_psfb_pli_pack(uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_sli_pack(const rtcp_sli_t *sli, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_rpsi_pack(uint8_t pt, const uint8_t *payload, uint32_t bits, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_fir_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_tstr_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_tstn_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_vbcm_pack(const rtcp_vbcm_t *vbcm, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_pslei_pack(const uint32_t *ssrc, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_lrr_pack(const rtcp_lrr_t *lrr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_remb_pack(const rtcp_remb_t *remb, int count, uint8_t *ptr, uint32_t bytes); + +static int rtcp_psfb_pli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_sli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_rpsi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_fir_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_tstr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_tstn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_vbcm_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_pslei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_lrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_roi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_afb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.1 +static int rtcp_psfb_pli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + // 1. There MUST be exactly one PLI contained in the FCI field. + // 2. PLI does not require parameters. Therefore, the length field MUST be 2, and there MUST NOT be any Feedback + // Control Information. + assert(0 == bytes); + + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header, (void)ptr; + return 0; +} + +static int rtcp_psfb_pli_pack(uint8_t *ptr, uint32_t bytes) +{ + (void)ptr, (void)bytes; + return 0; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | First | Number | PictureID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_sli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_sli_t *sli, sli0[32]; + + if (bytes / 4 > sizeof(sli0) / sizeof(sli0[0])) { + sli = calloc(bytes / 4, sizeof(*sli)); + if (!sli) + return -ENOMEM; + } else { + sli = sli0; + memset(sli, 0, sizeof(sli[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + sli[i].first = (ptr[0] << 5) | (ptr[1] >> 3); + sli[i].number = ((ptr[1] & 0x07) << 10) | (ptr[2] << 2) | (ptr[3] >> 6); + sli[i].picture_id = ptr[3] & 0x3F; + + ptr += 4; + } + + msg->u.psfb.u.sli.sli = sli; + msg->u.psfb.u.sli.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (sli && sli != sli0) + free(sli); + return 0; +} + +static int rtcp_psfb_sli_pack(const rtcp_sli_t *sli, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 4; i++) { + nbo_w32(ptr, ((sli->first & 0x1FFFF) << 19) | ((sli->number & 0x1FFFF) << 6) | (sli->picture_id & 0x3F)); + + bytes -= 4; + ptr += 4; + } + return i * 4; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.3 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PB |0| Payload Type| Native RPSI bit string | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined per codec ... | Padding (0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_rpsi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint8_t pb; + uint8_t pt; + + if (bytes < 4) + return -1; + + pb = ptr[0]; + pt = ptr[1] & 0x7F; + + msg->u.psfb.u.rpsi.pt = pt; + msg->u.psfb.u.rpsi.len = (uint32_t)bytes * 8 - pb; + msg->u.psfb.u.rpsi.payload = (uint8_t *)ptr + 2; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_psfb_rpsi_pack(uint8_t pt, const uint8_t *payload, uint32_t bits, uint8_t *ptr, uint32_t bytes) +{ + uint32_t len; + len = (bits + 7) / 8; + if (bytes < (2 + len + 3) / 4 * 4) + return -1; + + ptr[0] = (uint8_t)((2 + len + 3) / 4 * 4 * 8 - (2 * 8 + bits)); + ptr[1] = pt; + memcpy(ptr + 2, payload, len); + return (2 + len + 3) / 4 * 4; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_fir_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_fir_t *fir, fir0[32]; + + if (bytes / 8 > sizeof(fir0) / sizeof(fir0[0])) { + fir = calloc(bytes / 8, sizeof(*fir)); + if (!fir) + return -ENOMEM; + } else { + fir = fir0; + memset(fir, 0, sizeof(fir[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + fir[i].ssrc = nbo_r32(ptr); + fir[i].sn = ptr[4]; + ptr += 8; + } + + msg->u.psfb.u.fir.fir = fir; + msg->u.psfb.u.fir.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (fir && fir != fir0) + free(fir); + return 0; +} + +static int rtcp_psfb_fir_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 8; i++) { + nbo_w32(ptr, fir[i].ssrc); + nbo_w32(ptr + 4, fir[i].sn << 24); + + bytes -= 8; + ptr += 8; + } + return i * 8; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. | Reserved | Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_tstr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_fir_t *fir, fir0[32]; + + if (bytes / 8 > sizeof(fir0) / sizeof(fir0[0])) { + fir = calloc(bytes / 8, sizeof(*fir)); + if (!fir) + return -ENOMEM; + } else { + fir = fir0; + memset(fir, 0, sizeof(fir[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + fir[i].ssrc = nbo_r32(ptr); + fir[i].sn = ptr[4]; + fir[i].index = ptr[7] & 0x1F; + + ptr += 8; + } + + msg->u.psfb.u.fir.fir = fir; + msg->u.psfb.u.fir.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (fir && fir != fir0) + free(fir); + return 0; +} + +static int rtcp_psfb_tstr_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 8; i++) { + nbo_w32(ptr, fir[i].ssrc); + nbo_w32(ptr + 4, (fir[i].sn << 24) | fir[i].index); + + bytes -= 8; + ptr += 8; + } + return i * 8; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.3 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. | Reserved | Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_tstn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_fir_t *fir, fir0[32]; + + if (bytes / 8 > sizeof(fir0) / sizeof(fir0[0])) { + fir = calloc(bytes / 8, sizeof(*fir)); + if (!fir) + return -ENOMEM; + } else { + fir = fir0; + memset(fir, 0, sizeof(fir[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + fir[i].ssrc = nbo_r32(ptr); + fir[i].sn = ptr[4]; + fir[i].index = ptr[7] & 0x1F; + + ptr += 8; + } + + msg->u.psfb.u.fir.fir = fir; + msg->u.psfb.u.fir.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (fir && fir != fir0) + free(fir); + return 0; +} + +static int rtcp_psfb_tstn_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes) +{ + return rtcp_psfb_tstr_pack(fir, count, ptr, bytes); +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.4 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. |0| Payload Type| Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | VBCM Octet String.... | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_vbcm_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + rtcp_vbcm_t vbcm; + + while (bytes > 8) { + vbcm.ssrc = nbo_r32(ptr); + vbcm.sn = ptr[4]; + vbcm.pt = ptr[5] & 0x7F; + vbcm.len = nbo_r16(ptr + 6); + if (vbcm.len + 8 > bytes) + return -1; + + vbcm.payload = (uint8_t *)ptr + 8; + bytes -= 8 + (vbcm.len + 3) / 4 * 4; + ptr += 8 + (vbcm.len + 3) / 4 * 4; + + memcpy(&msg->u.psfb.u.vbcm, &vbcm, sizeof(vbcm)); + ctx->handler.on_rtcp(ctx->cbparam, msg); + } + + (void)ctx, (void)header; + return 0; +} + +static int rtcp_psfb_vbcm_pack(const rtcp_vbcm_t *vbcm, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 8 + (uint32_t)(vbcm->len + 3) / 4 * 4) + return -1; + + nbo_w32(ptr, vbcm->ssrc); + ptr[4] = (uint8_t)vbcm->sn; + ptr[5] = (uint8_t)vbcm->pt; + nbo_w16(ptr + 6, (uint16_t)vbcm->len); + memcpy(ptr + 8, vbcm->payload, vbcm->len); + return 8 + (vbcm->len + 3) / 4 * 4; +} + +// https://www.rfc-editor.org/rfc/rfc6642.html#section-5.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_pslei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + uint32_t *v, v0[32]; + + if (bytes / 4 > sizeof(v0) / sizeof(v0[0])) { + v = calloc(bytes / 4, sizeof(*v)); + if (!v) + return -ENOMEM; + } else { + v = v0; + memset(v, 0, sizeof(v[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + v[i] = nbo_r32(ptr); + ptr += 4; + } + + msg->u.psfb.u.pslei.ssrc = v; + msg->u.psfb.u.pslei.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (v && v != v0) + free(v); + return 0; +} + +static int rtcp_psfb_pslei_pack(const uint32_t *ssrc, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 4; i++) { + nbo_w32(ptr, ssrc[i]); + + bytes -= 4; + ptr += 4; + } + return i * 4; +} + +// https://datatracker.ietf.org/doc/html/draft-ietf-avtext-lrr-07#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. |C| Payload Type| Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RES | TTID| TLID | RES | CTID| CLID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_lrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_lrr_t *lrr, lrr0[32]; + + if (bytes / 12 > sizeof(lrr0) / sizeof(lrr0[0])) { + lrr = calloc(bytes / 12, sizeof(*lrr)); + if (!lrr) + return -ENOMEM; + } else { + lrr = lrr0; + memset(lrr, 0, sizeof(lrr[0]) * (bytes / 12)); + } + + for (i = 0; i < bytes / 12; i++) { + lrr[i].ssrc = nbo_r32(ptr); + lrr[i].sn = ptr[4]; + lrr[i].c = (ptr[5] >> 7) & 0x01; + lrr[i].payload = ptr[5] & 0x7F; + lrr[i].ttid = ptr[8] & 0x07; + lrr[i].tlid = ptr[9]; + lrr[i].ctid = ptr[10] & 0x07; + lrr[i].clid = ptr[11]; + ptr += 12; + } + + msg->u.psfb.u.lrr.lrr = lrr; + msg->u.psfb.u.lrr.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (lrr && lrr != lrr0) + free(lrr); + return 0; +} + +static int rtcp_psfb_lrr_pack(const rtcp_lrr_t *lrr, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 12; i++) { + nbo_w32(ptr, lrr[i].ssrc); + nbo_w32(ptr, (lrr[i].sn << 24) | ((lrr[i].c & 0x01) << 23) | ((lrr[i].payload & 0x7F) << 16)); + nbo_w32(ptr, ((lrr[i].ttid & 0x07) << 24) | ((lrr[i].tlid & 0xFF) << 16) | ((lrr[i].ctid & 0x07) << 8) | + ((lrr[i].clid & 0xFF) << 0)); + + bytes -= 12; + ptr += 12; + } + return i * 12; +} + +// https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1404 +// 7.3.7 Video Region-of-Interest (ROI) Signaling +/* +Arbitrary ROI + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Position_X (h)| Position_X (l)| Position_Y (h)| Position_Y(l)| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size_X (h) | Size_X (l) | Size_Y (h) | Size_Y(l) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Pre-defined ROI + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| all ones | ROI_ID | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=7 | Position_X (h)| Position_X (l)| Position_Y (h)| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Position_Y (l)| Size_X (h) | Size_X (l) | Size_Y (h) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size_Y (l) | zero padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=0 | ROI_ID | zero padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_roi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + (void)ctx, (void)header, (void)msg, (void)ptr, (void)bytes; + return 0; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.4 +// https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03#section-2.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=206 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unique identifier 'R' 'E' 'M' 'B' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Num SSRC | BR Exp | BR Mantissa | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC feedback | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... | +*/ +static int rtcp_psfb_afb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, n, exp, mantissa; + rtcp_remb_t *remb, remb0[4]; + const uint8_t id[] = {'R', 'E', 'M', 'B'}; + + if (bytes >= 8 && 0 == memcmp(ptr, id, 4)) { + n = ptr[4]; + exp = (ptr[5] >> 2) & 0x3F; + mantissa = ((ptr[5] & 0x3) << 16) | nbo_r16(ptr + 6); + + ptr += 8; + bytes -= 8; + if (n * 4 > bytes) + return -1; + + if (n > sizeof(remb0) / sizeof(remb0[0])) { + remb = calloc(n, sizeof(*remb)); + if (!remb) + return -ENOMEM; + } else { + remb = remb0; + memset(remb, 0, sizeof(remb[0]) * n); + } + + for (i = 0; i < n; i++) { + remb[i].exp = exp; + remb[i].mantissa = mantissa; + remb[i].ssrc = nbo_r32(ptr); + ptr += 4; + } + + msg->u.psfb.u.afb.remb = remb; + msg->u.psfb.u.afb.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + if (remb && remb != remb0) + free(remb); + return 0; + } + + (void)ctx, (void)header; + return 0; +} + +static int rtcp_psfb_remb_pack(const rtcp_remb_t *remb, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + const uint8_t id[] = {'R', 'E', 'M', 'B'}; + + if (count < 1 || count > 255 || (int)bytes < 4 + 4 + count * 4) + return -E2BIG; + + memcpy(ptr, id, sizeof(id)); + nbo_w32(ptr + 4, (count << 24) | (remb[0].exp << 18) | remb[0].mantissa); + + ptr += 8; + for (i = 0; i < count; i++) { + nbo_w32(ptr, remb[i].ssrc); + ptr += 4; + } + return 4 + 4 + count * 4; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3 +/* +* Common Packet Format for Feedback Messages + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : +*/ +void rtcp_psfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + int r; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + if (bytes < 8 /*sizeof(rtcp_fci_t)*/) { + assert(0); + return; + } + + msg.type = RTCP_PSFB | (header->rc << 8); + msg.ssrc = nbo_r32(ptr); + msg.u.psfb.media = nbo_r32(ptr + 4); + + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + assert(sender != ctx->self); + + r = 0; + switch (header->rc) { + case RTCP_PSFB_PLI: + r = rtcp_psfb_pli_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_SLI: + r = rtcp_psfb_sli_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_RPSI: + r = rtcp_psfb_rpsi_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_FIR: + r = rtcp_psfb_fir_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_TSTR: + r = rtcp_psfb_tstr_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_TSTN: + r = rtcp_psfb_tstn_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_VBCM: + r = rtcp_psfb_vbcm_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_PSLEI: + r = rtcp_psfb_pslei_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_ROI: + r = rtcp_psfb_roi_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_LRR: + r = rtcp_psfb_lrr_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_AFB: + r = rtcp_psfb_afb_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + default: + assert(0); + r = 0; // ignore + break; + } + + return; +} + +int rtcp_psfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_psfb_type_t id, const rtcp_psfb_t *psfb) +{ + int r; + rtcp_header_t header; + + (void)ctx; + if (bytes < 4 + 4 + 4) + return 4 + 4 + 4; + + switch (id) { + case RTCP_PSFB_PLI: + r = rtcp_psfb_pli_pack(data + 12, bytes - 12); + break; + + case RTCP_PSFB_SLI: + r = rtcp_psfb_sli_pack(psfb->u.sli.sli, psfb->u.sli.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_RPSI: + r = rtcp_psfb_rpsi_pack(psfb->u.rpsi.pt, psfb->u.rpsi.payload, psfb->u.rpsi.len, data + 12, bytes - 12); + break; + + case RTCP_PSFB_FIR: + r = rtcp_psfb_fir_pack(psfb->u.fir.fir, psfb->u.fir.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_TSTR: + r = rtcp_psfb_tstr_pack(psfb->u.fir.fir, psfb->u.fir.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_TSTN: + r = rtcp_psfb_tstn_pack(psfb->u.fir.fir, psfb->u.fir.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_VBCM: + r = rtcp_psfb_vbcm_pack(&psfb->u.vbcm, data + 12, bytes - 12); + break; + + case RTCP_PSFB_PSLEI: + r = rtcp_psfb_pslei_pack(psfb->u.pslei.ssrc, psfb->u.pslei.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_LRR: + r = rtcp_psfb_lrr_pack(psfb->u.lrr.lrr, psfb->u.lrr.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_REMB: + r = rtcp_psfb_remb_pack(psfb->u.afb.remb, psfb->u.afb.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_ROI: + default: + assert(0); + return -1; + } + + header.v = 2; + header.p = 0; + header.pt = RTCP_PSFB; + header.rc = id; + header.length = (r + 8 + 3) / 4; + nbo_write_rtcp_header(data, &header); + + nbo_w32(data + 4, ctx->self->ssrc); + // nbo_w32(data + 4, psfb->sender); + nbo_w32(data + 8, psfb->media); + + // assert(8 == (header.length + 1) * 4); + return header.length * 4 + 4; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void rtcp_on_psfb_test(void *param, const struct rtcp_msg_t *msg) +{ + int r; + static uint8_t buffer[1400]; + switch (msg->type & 0xFF) { + case RTCP_PSFB: + switch ((msg->type >> 8) & 0xFF) { + case RTCP_PSFB_PLI: + r = rtcp_psfb_pli_pack(buffer, sizeof(buffer)); + assert(0 == r); + break; + + case RTCP_PSFB_FIR: + assert(1 == msg->u.psfb.u.fir.count); + assert(0x23456789 == msg->u.psfb.u.fir.fir[0].ssrc && 13 == msg->u.psfb.u.fir.fir[0].sn); + r = rtcp_psfb_fir_pack(msg->u.psfb.u.fir.fir, msg->u.psfb.u.fir.count, buffer, sizeof(buffer)); + assert(r == 8 && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_PSFB_REMB: + assert(3 == msg->u.psfb.u.afb.count); + assert(0x23456789 == msg->u.psfb.u.afb.remb[0].ssrc && 1 == msg->u.psfb.u.afb.remb[0].exp && + 0x3fb93 == msg->u.psfb.u.afb.remb[0].mantissa); + assert(0x2345678a == msg->u.psfb.u.afb.remb[1].ssrc && 1 == msg->u.psfb.u.afb.remb[1].exp && + 0x3fb93 == msg->u.psfb.u.afb.remb[1].mantissa); + assert(0x2345678b == msg->u.psfb.u.afb.remb[2].ssrc && 1 == msg->u.psfb.u.afb.remb[2].exp && + 0x3fb93 == msg->u.psfb.u.afb.remb[2].mantissa); + r = rtcp_psfb_remb_pack(msg->u.psfb.u.afb.remb, msg->u.psfb.u.afb.count, buffer, sizeof(buffer)); + assert(r == 20 && 0 == memcmp(buffer, param, r)); + break; + + default: + break; + } + break; + + default: + assert(0); + } +} + +static void rtcp_rtpfb_pli_test(void) +{ + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_psfb_test; + rtp.cbparam = NULL; + + msg.type = (RTCP_PSFB_PLI << 8) | RTCP_PSFB; + assert(0 == rtcp_psfb_pli_unpack(&rtp, NULL, &msg, NULL, 0)); +} + +static void rtcp_rtpfb_fir_test(void) +{ + const uint8_t data[] = {0x23, 0x45, 0x67, 0x89, 0x0d, 0x00, 0x00, 0x00}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_psfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_PSFB_FIR << 8) | RTCP_PSFB; + assert(0 == rtcp_psfb_fir_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_remb_test(void) +{ + const uint8_t data[] = {'R', 'E', 'M', 'B', 0x03, 0x07, 0xfb, 0x93, 0x23, 0x45, + 0x67, 0x89, 0x23, 0x45, 0x67, 0x8a, 0x23, 0x45, 0x67, 0x8b}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_psfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_PSFB_REMB << 8) | RTCP_PSFB; + assert(0 == rtcp_psfb_afb_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +void rtcp_psfb_test(void) +{ + rtcp_rtpfb_pli_test(); + rtcp_rtpfb_fir_test(); + rtcp_rtpfb_remb_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-rr.c b/src/tuya_p2p/lib_rtp/src/rtcp-rr.c new file mode 100755 index 000000000..abe5b1552 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-rr.c @@ -0,0 +1,90 @@ +// RFC3550 6.4.2 RR: Receiver Report RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_rr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_rb_t *rb; + struct rtcp_msg_t msg; + struct rtp_member *receiver; + + assert(24 == sizeof(rtcp_rb_t) && 4 == sizeof(rtcp_rr_t)); + if (bytes < 4 /*sizeof(rtcp_rr_t)*/ + header->rc * 24 /*sizeof(rtcp_rb_t)*/) // RR SSRC + Report Block + { + assert(0); + return; + } + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_RR; + + receiver = rtp_member_fetch(ctx, msg.ssrc); + if (!receiver) + return; // error + + assert(receiver != ctx->self); + assert(receiver->rtcp_sr.ssrc == msg.ssrc); + // assert(receiver->rtcp_rb.ssrc == msg.ssrc); + receiver->rtcp_clock = rtpclock(); // last received clock, for keep-alive + + ptr += 4; + // report block + for (i = 0; i < header->rc; i++, ptr += 24 /*sizeof(rtcp_rb_t)*/) { + msg.u.rr.ssrc = nbo_r32(ptr); + // if(msg.u.rr.ssrcssrc != ctx->self->ssrc) + // continue; // ignore + // rb = &receiver->rtcp_rb; + + rb = &msg.u.rr; + rb->fraction = ptr[4]; + rb->cumulative = (((uint32_t)ptr[5]) << 16) | (((uint32_t)ptr[6]) << 8) | ptr[7]; + rb->exthsn = nbo_r32(ptr + 8); + rb->jitter = nbo_r32(ptr + 12); + rb->lsr = nbo_r32(ptr + 16); + rb->dlsr = nbo_r32(ptr + 20); + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + } +} + +int rtcp_rr_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + // RFC3550 6.1 RTCP Packet Format + // An individual RTP participant should send only one compound RTCP packet per report interval + // in order for the RTCP bandwidth per participant to be estimated correctly (see Section 6.2), + // except when the compound RTCP packet is split for partial encryption as described in Section 9.1. + uint32_t i; + rtcp_header_t header; + + assert(4 == sizeof(rtcp_rr_t)); + assert(24 == sizeof(rtcp_rb_t)); + assert(rtp_member_list_count(ctx->senders) < 32); + header.v = 2; + header.p = 0; + header.pt = RTCP_RR; + header.rc = MIN(31, rtp_member_list_count(ctx->senders)); + header.length = (4 /*sizeof(rtcp_rr_t)*/ + header.rc * 24 /*sizeof(rtcp_rb_t)*/) / 4; + + if ((uint32_t)bytes < 4 + header.length * 4) + return 4 + header.length * 4; + + nbo_write_rtcp_header(ptr, &header); + + // receiver SSRC + nbo_w32(ptr + 4, ctx->self->ssrc); + + ptr += 8; + // report block + for (i = 0; i < header.rc; i++) { + struct rtp_member *sender; + + sender = rtp_member_list_get(ctx->senders, i); + if (0 == sender->rtp_packets || sender->ssrc == ctx->self->ssrc) + continue; // don't receive any packet + + ptr += rtcp_report_block(sender, ptr, 24); + } + + return (header.length + 1) * 4; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c b/src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c new file mode 100755 index 000000000..adae01643 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c @@ -0,0 +1,1187 @@ +#include "rtp-internal.h" +#include "rtp-util.h" +#include + +#define TCC01_CLOCK_RESOLUTION 250 // us + +static int rtcp_rtpfb_nack_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tmmbr_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tmmbn_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tllei_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_ps_pack(uint32_t target, uint8_t cmd, uint8_t len, uint16_t id, const uint8_t *payload, + uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_ccfb_pack(uint32_t ssrc, uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, + uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tcc01_pack(uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, uint8_t cc, + uint8_t *ptr, uint32_t bytes); + +static int rtcp_rtpfb_nack_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tmmbr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tmmbn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_srreq_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_rams_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tllei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_ps_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_dbi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_ccfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tcc01_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.2.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_nack_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_nack_t *nack, nack0[32]; + + if (bytes / 4 > sizeof(nack0) / sizeof(nack0[0])) { + nack = calloc(bytes / 4, sizeof(*nack)); + if (!nack) + return -ENOMEM; + } else { + nack = nack0; + memset(nack, 0, sizeof(nack[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + nack[i].pid = nbo_r16(ptr); + nack[i].blp = nbo_r16(ptr + 2); + ptr += 4; + } + + msg->u.rtpfb.u.nack.nack = nack; + msg->u.rtpfb.u.nack.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (nack && nack != nack0) + free(nack); + return 0; +} + +static int rtcp_rtpfb_nack_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 4; i++) { + nbo_w16(ptr, nack[i].pid); + nbo_w16(ptr + 2, nack[i].blp); + + bytes -= 4; + ptr += 4; + } + return i * 4; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | MxTBR Exp | MxTBR Mantissa |Measured Overhead| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_tmmbr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_tmmbr_t *tmmbr, tmmbr0[4]; + + if (bytes / 8 > sizeof(tmmbr0) / sizeof(tmmbr0[0])) { + tmmbr = calloc(bytes / 8, sizeof(*tmmbr)); + if (!tmmbr) + return -ENOMEM; + } else { + tmmbr = tmmbr0; + memset(tmmbr, 0, sizeof(tmmbr[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + tmmbr[i].ssrc = nbo_r32(ptr); + tmmbr[i].exp = (ptr[4] >> 2) & 0x3F; + tmmbr[i].mantissa = ((ptr[4] & 0x03) << 15) | (ptr[5] << 7) | ((ptr[6] >> 1) & 0x7F); + tmmbr[i].overhead = ((ptr[6] & 0x01) << 8) | ptr[7]; + ptr += 8; + } + + msg->u.rtpfb.u.tmmbr.tmmbr = tmmbr; + msg->u.rtpfb.u.tmmbr.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (tmmbr && tmmbr != tmmbr0) + free(tmmbr); + return 0; +} + +static int rtcp_rtpfb_tmmbr_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 8; i++) { + nbo_w32(ptr, tmmbr[i].ssrc); + nbo_w32(ptr + 4, + ((tmmbr[i].exp & 0x3F) << 26) | ((tmmbr[i].mantissa & 0x1FFFF) << 9) | (tmmbr[i].overhead & 0x1FF)); + + bytes -= 8; + ptr += 8; + } + return i * 8; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.2 +static int rtcp_rtpfb_tmmbn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_tmmbr_t *tmmbr, tmmbr0[4]; + + if (bytes / 8 > sizeof(tmmbr0) / sizeof(tmmbr0[0])) { + tmmbr = calloc(bytes / 8, sizeof(*tmmbr)); + if (!tmmbr) + return -ENOMEM; + } else { + tmmbr = tmmbr0; + memset(tmmbr, 0, sizeof(tmmbr[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + tmmbr[i].ssrc = nbo_r32(ptr); + tmmbr[i].exp = (ptr[4] >> 2) & 0x3F; + tmmbr[i].mantissa = ((ptr[4] & 0x03) << 15) | (ptr[5] << 7) | ((ptr[6] >> 1) & 0x7F); + tmmbr[i].overhead = ((ptr[6] & 0x01) << 8) | ptr[7]; + ptr += 8; + } + + msg->u.rtpfb.u.tmmbr.tmmbr = tmmbr; + msg->u.rtpfb.u.tmmbr.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (tmmbr && tmmbr != tmmbr0) + free(tmmbr); + return 0; +} + +static int rtcp_rtpfb_tmmbn_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes) +{ + return rtcp_rtpfb_tmmbr_pack(tmmbr, count, ptr, bytes); +} + +// https://www.rfc-editor.org/rfc/rfc6051.html#section-3.2 +static int rtcp_rtpfb_srreq_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + // The SSRC of the packet sender indicates the member that is unable to synchronise media streams, + // while the SSRC of the media source indicates the sender of the media it is unable to synchronise. + // The length MUST equal 2. + assert(bytes == 0); + (void)ctx, (void)header, (void)msg, (void)ptr; + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc6285.html#section-7 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Reserved | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Value : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 5: Structure of a TLV Element + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SFMT=1 | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Requested Media Sender SSRC(s) : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Optional TLV-encoded Fields (and Padding, if needed) : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 7: FCI Field Syntax for the RAMS Request Message + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SFMT=2 | MSN | Response | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Optional TLV-encoded Fields (and Padding, if needed) : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 8: FCI Field Syntax for the RAMS Information Message +*/ +static int rtcp_rtpfb_rams_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint8_t sfmt; + if (bytes < 4) + return -1; + sfmt = ptr[0]; + (void)ctx, (void)header, (void)msg; + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc6642.html#section-5.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_tllei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_nack_t *nack, nack0[32]; + + if (bytes / 4 > sizeof(nack0) / sizeof(nack0[0])) { + nack = calloc(bytes / 4, sizeof(*nack)); + if (!nack) + return -ENOMEM; + } else { + nack = nack0; + memset(nack, 0, sizeof(nack[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + nack[i].pid = nbo_r16(ptr); + nack[i].blp = nbo_r16(ptr + 2); + ptr += 4; + } + + msg->u.rtpfb.u.nack.nack = nack; + msg->u.rtpfb.u.nack.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (nack && nack != nack0) + free(nack); + return 0; +} + +static int rtcp_rtpfb_tllei_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes) +{ + return rtcp_rtpfb_nack_pack(nack, count, ptr, bytes); +} + +// https://www.rfc-editor.org/rfc/rfc6679.html#section-5.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Extended Highest Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (0) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (1) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECN-CE Counter | not-ECT Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Lost Packets Counter | Duplication Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + rtcp_ecn_t ecn; + if (bytes < 20) + return -1; + ecn.ext_highest_seq = nbo_r32(ptr); + ecn.ect[0] = nbo_r32(ptr + 4); + ecn.ect[1] = nbo_r32(ptr + 8); + ecn.ect_ce_counter = nbo_r16(ptr + 12); + ecn.not_ect_counter = nbo_r16(ptr + 14); + ecn.lost_packets_counter = nbo_r16(ptr + 16); + ecn.duplication_counter = nbo_r16(ptr + 18); + + memcpy(&msg->u.rtpfb.u.ecn, &ecn, sizeof(ecn)); + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_rtpfb_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 20) + return -1; + + nbo_w32(ptr, ecn->ext_highest_seq); + nbo_w32(ptr + 4, ecn->ect[0]); + nbo_w32(ptr + 8, ecn->ect[1]); + nbo_w16(ptr + 12, ecn->ect_ce_counter); + nbo_w16(ptr + 14, ecn->not_ect_counter); + nbo_w16(ptr + 16, ecn->lost_packets_counter); + nbo_w16(ptr + 18, ecn->duplication_counter); + return 20; +} + +// https://www.rfc-editor.org/rfc/rfc7728.html#section-7 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Target SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Res | Parameter Len | PauseID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Type Specific : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_ps_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint16_t id; + uint32_t target; + uint8_t cmd; + uint32_t len; + + while (bytes >= 8) { + target = nbo_r32(ptr); // target ssrc + cmd = ptr[4] >> 4; + len = ptr[5]; + id = nbo_r16(ptr + 6); + + if (len * 4 + 8 > bytes) { + assert(0); + return -1; + } + + msg->u.rtpfb.u.ps.target = target; + msg->u.rtpfb.u.ps.cmd = cmd; + msg->u.rtpfb.u.ps.len = len; + msg->u.rtpfb.u.ps.id = id; + msg->u.rtpfb.u.ps.payload = (uint8_t *)ptr + 8; + ctx->handler.on_rtcp(ctx->cbparam, msg); + + ptr += 8 + len * 4; + bytes -= 8 + len * 4; + } + + (void)ctx, (void)header; + return 0; +} + +static int rtcp_rtpfb_ps_pack(uint32_t target, uint8_t cmd, uint8_t len, uint16_t id, const uint8_t *payload, + uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 8 + (uint32_t)len * 4) + return -1; + + nbo_w32(ptr, target); + nbo_w32(ptr + 4, ((cmd & 0x0F) << 28) | ((len & 0xFF) << 16) | id); + if (len > 0) + memcpy(ptr + 8, payload, len * 4); + return 8 + len * 4; +} + +// https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1404 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| delay |s|q| zero padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_dbi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + if (bytes < 4) + return -1; + + msg->u.rtpfb.u.dbi.delay = nbo_r16(ptr); + msg->u.rtpfb.u.dbi.s = (ptr[2] >> 7) & 0x01; + msg->u.rtpfb.u.dbi.q = (ptr[2] >> 6) & 0x01; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header, (void)ptr; + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc8888.html#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=11 | PT = 205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of RTCP packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of 1st RTP Stream | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | num_reports | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |R|ECN| Arrival time offset | ... . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of nth RTP Stream | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | num_reports | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |R|ECN| Arrival time offset | ... | + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Report Timestamp (32 bits) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_ccfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, num, ssrc; + uint32_t timestamp; + uint16_t begin; + rtcp_ccfb_t *ccfb, ccfb0[32]; + + timestamp = bytes >= 4 ? nbo_r32(ptr + bytes - 4) : 0; // get last report timestamp + + while (bytes >= 8) { + ssrc = nbo_r32(ptr); // target ssrc + begin = nbo_r16(ptr + 4); + num = nbo_r16(ptr + 6); + + ptr += 8; + bytes -= 8; + if ((num * 2 + 3) / 4 * 4 > bytes) // 4-bytes padding + { + assert(0); + return -1; + } + + if (num > sizeof(ccfb0) / sizeof(ccfb0[0])) { + ccfb = calloc(num, sizeof(*ccfb)); + if (!ccfb) + return -ENOMEM; + } else { + ccfb = ccfb0; + memset(ccfb, 0, sizeof(ccfb[0]) * num); + } + + for (i = 0; i < num; i++) { + ccfb[i].seq = begin + i; + ccfb[i].received = (ptr[i * 2] >> 7) & 0x01; + ccfb[i].ecn = (ptr[i * 2] >> 5) & 0x03; + ccfb[i].ato = ((ptr[i * 2] & 0x1F) << 8) | ptr[i * 2 + 1]; + } + + msg->u.rtpfb.u.tcc01.timestamp = 0; // fixme + msg->u.rtpfb.u.tcc01.ssrc = ssrc; + msg->u.rtpfb.u.tcc01.begin = begin; + msg->u.rtpfb.u.tcc01.ccfb = ccfb; + msg->u.rtpfb.u.tcc01.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + + num = (num + 1) / 2 * 2; // 4-bytes padding + assert(bytes >= num * 2); // check again + ptr += num * 2; + bytes -= num * 2; + + if (ccfb && ccfb != ccfb0) + free(ccfb); + } + + if (bytes >= 4) { + timestamp = nbo_r32(ptr); + ptr += 4; + bytes -= 4; + } + + (void)ctx, (void)header; + assert(0 == bytes); + return 0; +} + +static int rtcp_rtpfb_ccfb_pack(uint32_t ssrc, uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, + uint8_t *ptr, uint32_t bytes) +{ + int i; + if (bytes < 8 + (uint32_t)count * 2 + 4 || count > 0xFFFF) + return -1; + + nbo_w32(ptr, ssrc); + nbo_w16(ptr + 4, begin); + nbo_w16(ptr + 6, (uint16_t)count); + ptr += 8; + + for (i = 0; i < count; i++) { + nbo_w16(ptr, (ccfb[i].received ? 0x8000 : 0) | ((ccfb[i].ecn & 0x03) << 13) | (ccfb->ato & 0x1FFF)); + bytes -= 2; + ptr += 2; + } + + nbo_w32(ptr, timestamp); + return 8 + count * 2 + 4; +} + +// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-00#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | fb seq num |r| base sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | base receive time | sequence number ack vector | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recv delta | recv delta | recv delta |...| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recovery base sequence number | recovery vector | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | base sequence number | packet status count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | reference time | fb pkt. count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | packet chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | recv delta | recv delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recv delta | recv delta | zero padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_tcc01_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + int r; + uint16_t i, j; + uint32_t timestamp; + uint16_t seq, num, chunk; + uint8_t cc; + rtcp_ccfb_t *ccfb, ccfb0[32]; + + if (bytes < 8) + return -1; + seq = nbo_r16(ptr); + num = nbo_r16(ptr + 2); + timestamp = (ptr[4] << 16) | (ptr[5] << 8) | ptr[6]; + cc = ptr[7]; + msg->u.rtpfb.u.tcc01.cc = cc; + msg->u.rtpfb.u.tcc01.begin = seq; + msg->u.rtpfb.u.tcc01.timestamp = timestamp | ((timestamp & 0x00800000) ? 0xFF000000 : 0); // signed-24 -> signed-32 + + if (num > sizeof(ccfb0) / sizeof(ccfb0[0])) { + ccfb = calloc(num, sizeof(*ccfb)); + if (!ccfb) + return -ENOMEM; + } else { + ccfb = ccfb0; + memset(ccfb, 0, sizeof(ccfb[0]) * num); + } + + r = 0; + ptr += 8; + bytes -= 8; + for (i = 0; bytes >= 2 && i < num; bytes -= 2, ptr += 2) { + chunk = nbo_r16(ptr); // packet chunk + + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + for (j = 0; j < (uint16_t)(chunk & 0x1FFF) && i < num; j++, i++) { + ccfb[i].seq = seq++; + ccfb[i].ecn = (chunk >> 13) & 0x03; + ccfb[i].received = ccfb[i].ecn ? 1 : 0; + ccfb[i].ato = 0; + } + } else { + // Status Vector Chunk + if (0x4000 & chunk) { + // two bits + for (j = 0; j < 7 && i < num; j++, i++) { + ccfb[i].seq = seq++; + ccfb[i].ecn = (chunk >> (2 * (6 - j))) & 0x03; + ccfb[i].received = ccfb[i].ecn ? 1 : 0; + ccfb[i].ato = 0; + } + } else { + // one bits + for (j = 0; j < 14 && i < num; j++, i++) { + ccfb[i].seq = seq++; + ccfb[i].ecn = (chunk & (1 << (13 - j))) ? 0x01 : 0x00; // small delta + ccfb[i].received = ccfb[i].ecn ? 1 : 0; + ccfb[i].ato = 0; + } + } + } + } + + for (i = 0; i < num && bytes > 0; i++) { + if (!ccfb[i].received) + continue; + + assert(ccfb[i].ecn == 0x01 || ccfb[i].ecn == 0x02); + if (ccfb[i].ecn == 0x01) { + ccfb[i].ato = ptr[0] >> 2; // 250us + bytes -= 1; + ptr += 1; + } else { + if (bytes < 2) { + assert(0); + r = -1; + break; + } + ccfb[i].ato = ((int16_t)nbo_r16(ptr)) >> 2; // 250us -> 1/1024(ms) + bytes -= 2; + ptr += 2; + } + } + + if (0 == r) { + msg->u.rtpfb.u.tcc01.ccfb = ccfb; + msg->u.rtpfb.u.tcc01.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + } + (void)ctx, (void)header; + if (ccfb && ccfb != ccfb0) + free(ccfb); + return r; +} + +static int rtcp_rtpfb_tcc01_pack(uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, uint8_t cc, + uint8_t *ptr, uint32_t bytes) +{ + int i, k, n; + int16_t ato; + uint16_t chunk, two; + uint32_t seq, received; + const uint8_t *p; + if (bytes < 8 || count < 1) + return -1; + + nbo_w16(ptr, begin); + nbo_w16(ptr + 2, (uint16_t)count); // placeholder + nbo_w32(ptr + 4, ((timestamp & 0xFFFFFF) << 8) | cc); + ptr += 8; + p = ptr; // chunk pointer + n = 8; + + for (i = 0; i < count && bytes >= 2; i += k) { + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + two = (ccfb[i].received && (uint16_t)ato > 0xFF) ? 2 : 1; + received = ccfb[i].received; + + // try Run Length Chunk + for (k = 1; i + k < count && ccfb[i + k].received == ccfb[i].received; k++) { + ato = ccfb[i + k].ato << 2; // 1/1024(ms) -> 250us + two = (ccfb[i + k].received && (uint16_t)ato > 0xFF) ? 2 : two; + received |= ccfb[i + k].received; + } + + if (k > 14 / two) { + // Run Length Chunk + chunk = 0x0000 | (received ? (2 == two ? 0x4000 : 0x2000) : 0x0000) | (uint16_t)k; + } else { + two = 1; // re-detect + for (k = 0; k < 14 / two && i + k < count; k++) { + ato = ccfb[i + k].ato << 2; // 1/1024(s) -> 250us + two = (ccfb[i + k].received && (uint16_t)ato > 0xFF) ? 2 : two; + } + + ato = (2 == two || k <= 7) ? 1 : 0; // small space/padding + chunk = 0x8000 | (ato ? 0x4000 : 0x0000); + for (k = 0; k < 14 / two && i + k < count; k++) { + if (ccfb[i + k].received) { + chunk |= ((uint16_t)(ccfb[i + k].ato << 2) > 0xFF ? 2 : 1) << (ato ? (12 - k * 2) : (13 - k)); + } + } + } + + nbo_w16(ptr, chunk); + bytes -= 2; + ptr += 2; + n += 2; + } + + // parse chunk and write delta + for (i = 0; i < count && bytes >= 1; p += 2) { + chunk = nbo_r16(p); // packet chunk + + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + seq = ccfb[i].seq; + for (k = 0; k < (uint16_t)(chunk & 0x1FFF) && i < count; k++) { + assert(seq + k == ccfb[i].seq); + if (seq + k != ccfb[i].seq) + continue; + + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + if (chunk & 0x4000) { + if (bytes < 2) + return -1; + nbo_w16(ptr, ato); + bytes -= 2; + ptr += 2; + n += 2; + } else if (chunk & 0x2000) { + if (bytes < 1) + return -1; + ptr[0] = (uint8_t)ato; + bytes -= 1; + ptr += 1; + n += 1; + } + + i++; + } + } else { + // Status Vector Chunk + if (0x4000 & chunk) { + // two bits + for (k = 0; k < 7 && i < count && bytes >= 2; k++, i++) { + if (!ccfb[i].received) + continue; + + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + two = (chunk >> (2 * (6 - k))) & 0x03; + if (two == 1) { + ptr[0] = (uint8_t)ato; + bytes -= 1; + ptr += 1; + n += 1; + } else { + nbo_w16(ptr, ato); + bytes -= 2; + ptr += 2; + n += 2; + } + } + } else { + // one bits + for (k = 0; k < 14 && i < count && bytes >= 1; k++, i++) { + if (!ccfb[i].received) + continue; + + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + ptr[0] = (uint8_t)ato; + bytes -= 1; + ptr += 1; + n += 1; + } + } + } + } + + // padding + for (k = 0; k < 4 && (n % 4 != 0) && bytes > 0; k++) { + ptr[0] = ((n + 1) % 4 == 0) ? (uint8_t)k + 1 : 0; + bytes -= 1; + ptr += 1; + n += 1; + } + return n; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.2 +/* +* Common Packet Format for Feedback Messages + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : +*/ +void rtcp_rtpfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + int r; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + if (bytes < 8 /*sizeof(rtcp_fci_t)*/) { + assert(0); + return; + } + + msg.type = RTCP_RTPFB | (header->rc << 8); + msg.ssrc = nbo_r32(ptr); + msg.u.rtpfb.media = nbo_r32(ptr + 4); + + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + // assert(sender != ctx->self); + + switch (header->rc) { + case RTCP_RTPFB_NACK: + r = rtcp_rtpfb_nack_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_TMMBR: + r = rtcp_rtpfb_tmmbr_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_TMMBN: + r = rtcp_rtpfb_tmmbn_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_SRREQ: + r = rtcp_rtpfb_srreq_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_RAMS: + r = rtcp_rtpfb_rams_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_TLLEI: + r = rtcp_rtpfb_tllei_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_ECN: + r = rtcp_rtpfb_ecn_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_PS: + r = rtcp_rtpfb_ps_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_DBI: + r = rtcp_rtpfb_dbi_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_CCFB: + r = rtcp_rtpfb_ccfb_unpack(ctx, header, &msg, ptr + 4 /*1st ssrc*/, bytes - 4); + break; + + case RTCP_RTPFB_TCC01: + r = rtcp_rtpfb_tcc01_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + default: + assert(0); + r = 0; + break; + } + + return; +} + +int rtcp_rtpfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_rtpfb_type_t id, + const rtcp_rtpfb_t *rtpfb) +{ + int r; + rtcp_header_t header; + + (void)ctx; + if (bytes < 4 + 4 + 4) + return 4 + 4 + 4; + + switch (id) { + case RTCP_RTPFB_NACK: + r = rtcp_rtpfb_nack_pack(rtpfb->u.nack.nack, rtpfb->u.nack.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TMMBR: + r = rtcp_rtpfb_tmmbr_pack(rtpfb->u.tmmbr.tmmbr, rtpfb->u.tmmbr.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TMMBN: + r = rtcp_rtpfb_tmmbn_pack(rtpfb->u.tmmbr.tmmbr, rtpfb->u.tmmbr.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TLLEI: + r = rtcp_rtpfb_tllei_pack(rtpfb->u.nack.nack, rtpfb->u.nack.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_ECN: + r = rtcp_rtpfb_ecn_pack(&rtpfb->u.ecn, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_PS: + r = rtcp_rtpfb_ps_pack(rtpfb->u.ps.target, (uint8_t)rtpfb->u.ps.cmd, (uint8_t)rtpfb->u.ps.len, + (uint16_t)rtpfb->u.ps.id, (const uint8_t *)rtpfb->u.ps.payload, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_CCFB: + r = rtcp_rtpfb_ccfb_pack(rtpfb->u.tcc01.ssrc, (uint16_t)rtpfb->u.tcc01.begin, rtpfb->u.tcc01.ccfb, + rtpfb->u.tcc01.count, rtpfb->u.tcc01.timestamp, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TCC01: + r = rtcp_rtpfb_tcc01_pack((uint16_t)rtpfb->u.tcc01.begin, rtpfb->u.tcc01.ccfb, rtpfb->u.tcc01.count, + rtpfb->u.tcc01.timestamp, (uint8_t)rtpfb->u.tcc01.cc, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_SRREQ: + case RTCP_RTPFB_RAMS: + case RTCP_RTPFB_DBI: + default: + assert(0); + return -1; + } + + header.v = 2; + header.p = 0; + header.pt = RTCP_RTPFB; + header.rc = id; + header.length = (r + 8 + 3) / 4; + nbo_write_rtcp_header(data, &header); + + nbo_w32(data + 4, ctx->self->ssrc); + // nbo_w32(data + 4, rtpfb->sender); + nbo_w32(data + 8, rtpfb->media); + + // assert(8 == (header.length + 1) * 4); + return header.length * 4 + 4; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void rtcp_on_rtpfb_test(void *param, const struct rtcp_msg_t *msg) +{ + int r; + static uint8_t buffer[1400]; + switch (msg->type & 0xFF) { + case RTCP_RTPFB: + switch ((msg->type >> 8) & 0xFF) { + case RTCP_RTPFB_NACK: + assert(13 == msg->u.rtpfb.u.nack.count); + assert(msg->u.rtpfb.u.nack.nack[0].pid == 631 && msg->u.rtpfb.u.nack.nack[0].blp == 0x8028); + assert(msg->u.rtpfb.u.nack.nack[1].pid == 648 && msg->u.rtpfb.u.nack.nack[1].blp == 0x0021); + assert(msg->u.rtpfb.u.nack.nack[2].pid == 666 && msg->u.rtpfb.u.nack.nack[2].blp == 0x0008); + assert(msg->u.rtpfb.u.nack.nack[3].pid == 690 && msg->u.rtpfb.u.nack.nack[3].blp == 0x2000); + assert(msg->u.rtpfb.u.nack.nack[4].pid == 734 && msg->u.rtpfb.u.nack.nack[4].blp == 0x0025); + assert(msg->u.rtpfb.u.nack.nack[5].pid == 757 && msg->u.rtpfb.u.nack.nack[5].blp == 0x1100); + assert(msg->u.rtpfb.u.nack.nack[6].pid == 777 && msg->u.rtpfb.u.nack.nack[6].blp == 0x0000); + assert(msg->u.rtpfb.u.nack.nack[7].pid == 826 && msg->u.rtpfb.u.nack.nack[7].blp == 0x0002); + assert(msg->u.rtpfb.u.nack.nack[8].pid == 865 && msg->u.rtpfb.u.nack.nack[8].blp == 0x0000); + assert(msg->u.rtpfb.u.nack.nack[9].pid == 882 && msg->u.rtpfb.u.nack.nack[9].blp == 0x0300); + assert(msg->u.rtpfb.u.nack.nack[10].pid == 907 && msg->u.rtpfb.u.nack.nack[10].blp == 0x0400); + assert(msg->u.rtpfb.u.nack.nack[11].pid == 931 && msg->u.rtpfb.u.nack.nack[11].blp == 0x0000); + assert(msg->u.rtpfb.u.nack.nack[12].pid == 963 && msg->u.rtpfb.u.nack.nack[12].blp == 0x006d); + r = rtcp_rtpfb_nack_pack(msg->u.rtpfb.u.nack.nack, msg->u.rtpfb.u.nack.count, buffer, sizeof(buffer)); + assert(r > 0 && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_TMMBR: + assert(1 == msg->u.rtpfb.u.tmmbr.count); + assert(0x23456789 == msg->u.rtpfb.u.tmmbr.tmmbr[0].ssrc && 2 == msg->u.rtpfb.u.tmmbr.tmmbr[0].exp && + 78000 == msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa && + 312000 == (msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa << msg->u.rtpfb.u.tmmbr.tmmbr[0].exp) && + 0x1fe == msg->u.rtpfb.u.tmmbr.tmmbr[0].overhead); + r = rtcp_rtpfb_tmmbr_pack(msg->u.rtpfb.u.tmmbr.tmmbr, msg->u.rtpfb.u.tmmbr.count, buffer, sizeof(buffer)); + assert(8 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_TMMBN: + assert(1 == msg->u.rtpfb.u.tmmbr.count); + assert(0x23456789 == msg->u.rtpfb.u.tmmbr.tmmbr[0].ssrc && 2 == msg->u.rtpfb.u.tmmbr.tmmbr[0].exp && + 78000 == msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa && + 312000 == (msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa << msg->u.rtpfb.u.tmmbr.tmmbr[0].exp) && + 0x1fe == msg->u.rtpfb.u.tmmbr.tmmbr[0].overhead); + r = rtcp_rtpfb_tmmbr_pack(msg->u.rtpfb.u.tmmbr.tmmbr, msg->u.rtpfb.u.tmmbr.count, buffer, sizeof(buffer)); + assert(8 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_TCC01: + assert(1767 == msg->u.rtpfb.u.tcc01.begin && 4114000 == msg->u.rtpfb.u.tcc01.timestamp && + 101 == msg->u.rtpfb.u.tcc01.count && 42 == msg->u.rtpfb.u.tcc01.cc); + assert(msg->u.rtpfb.u.tcc01.ccfb[0].seq == 1767 && msg->u.rtpfb.u.tcc01.ccfb[0].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[1].seq == 1768 && msg->u.rtpfb.u.tcc01.ccfb[1].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[1].ato == 21); + assert(msg->u.rtpfb.u.tcc01.ccfb[2].seq == 1769 && msg->u.rtpfb.u.tcc01.ccfb[2].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[2].ato == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[3].seq == 1770 && msg->u.rtpfb.u.tcc01.ccfb[3].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[4].seq == 1771 && msg->u.rtpfb.u.tcc01.ccfb[4].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[4].ato == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[5].seq == 1772 && msg->u.rtpfb.u.tcc01.ccfb[5].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[6].seq == 1773 && msg->u.rtpfb.u.tcc01.ccfb[6].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[6].ato == 26); + r = rtcp_rtpfb_tcc01_pack((uint16_t)msg->u.rtpfb.u.tcc01.begin, msg->u.rtpfb.u.tcc01.ccfb, + msg->u.rtpfb.u.tcc01.count, msg->u.rtpfb.u.tcc01.timestamp, + (uint8_t)msg->u.rtpfb.u.tcc01.cc, buffer, sizeof(buffer)); + assert(104 == r && 0 == memcmp(buffer, param, r)); + break; + + case 30: // test only + assert(5 == msg->u.rtpfb.u.tcc01.begin && 5 == msg->u.rtpfb.u.tcc01.timestamp && + 7 == msg->u.rtpfb.u.tcc01.count && 0 == msg->u.rtpfb.u.tcc01.cc); + assert(msg->u.rtpfb.u.tcc01.ccfb[0].seq == 5 && msg->u.rtpfb.u.tcc01.ccfb[0].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[1].ato == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[1].seq == 6 && msg->u.rtpfb.u.tcc01.ccfb[1].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[2].seq == 7 && msg->u.rtpfb.u.tcc01.ccfb[2].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[2].ato == 128); + assert(msg->u.rtpfb.u.tcc01.ccfb[3].seq == 8 && msg->u.rtpfb.u.tcc01.ccfb[3].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[3].ato == 64); + assert(msg->u.rtpfb.u.tcc01.ccfb[4].seq == 9 && msg->u.rtpfb.u.tcc01.ccfb[4].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[5].seq == 10 && msg->u.rtpfb.u.tcc01.ccfb[5].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[6].seq == 11 && msg->u.rtpfb.u.tcc01.ccfb[6].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[6].ato == 256); + r = rtcp_rtpfb_tcc01_pack((uint16_t)msg->u.rtpfb.u.tcc01.begin, msg->u.rtpfb.u.tcc01.ccfb, + msg->u.rtpfb.u.tcc01.count, msg->u.rtpfb.u.tcc01.timestamp, + (uint8_t)msg->u.rtpfb.u.tcc01.cc, buffer, sizeof(buffer)); + assert(20 == r && 0 == memcmp(buffer, param, r)); + break; + + case 31: // test only + assert(248 == msg->u.rtpfb.u.tcc01.begin && -5546573 == msg->u.rtpfb.u.tcc01.timestamp && + 128 == msg->u.rtpfb.u.tcc01.count && 1 == msg->u.rtpfb.u.tcc01.cc); + assert(msg->u.rtpfb.u.tcc01.ccfb[0].seq == 248 && msg->u.rtpfb.u.tcc01.ccfb[0].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[0].ato == 33); + assert(msg->u.rtpfb.u.tcc01.ccfb[1].seq == 249 && msg->u.rtpfb.u.tcc01.ccfb[1].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[125].seq == 373 && msg->u.rtpfb.u.tcc01.ccfb[125].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[125].ato == -50); + assert(msg->u.rtpfb.u.tcc01.ccfb[126].seq == 374 && msg->u.rtpfb.u.tcc01.ccfb[126].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[127].seq == 375 && msg->u.rtpfb.u.tcc01.ccfb[127].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[127].ato == 65); + r = rtcp_rtpfb_tcc01_pack((uint16_t)msg->u.rtpfb.u.tcc01.begin, msg->u.rtpfb.u.tcc01.ccfb, + msg->u.rtpfb.u.tcc01.count, msg->u.rtpfb.u.tcc01.timestamp, + (uint8_t)msg->u.rtpfb.u.tcc01.cc, buffer, sizeof(buffer)); + assert(20 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_CCFB: + assert(0); + break; + + default: + break; + } + break; + + default: + assert(0); + } +} + +static void rtcp_rtpfb_nack_test(void) +{ + // rtcp_nack_t* nack; + // const uint8_t data[] = { 0x81, 0xcd, 0x00, 0x08, 0x84, 0x68, 0xc2, 0x4c, 0x84, 0x68, 0xc2, 0x4c, 0x03, 0x27, + // 0x7f, 0xff, 0x03, 0x38, 0xff, 0xf7, 0x03, 0x49, 0xff, 0xff, 0x03, 0x5a, 0xff, 0xff, 0x03, 0x6b, 0x7f, 0xbf, 0x03, + // 0x7c, 0x00, 0x0f }; assert(0 == rtcp_rtpfb_nack_unpack(NULL, NULL, 0, 0, data, sizeof(data))); assert(nack[0].pid + // == 807 && nack[0].blp == 0x7fff); assert(nack[1].pid == 824 && nack[1].blp == 0xfff7); assert(nack[2].pid == 841 + // && nack[2].blp == 0xffff); assert(nack[3].pid == 858 && nack[3].blp == 0xffff); assert(nack[4].pid == 875 && + // nack[4].blp == 0x7fbf); assert(nack[5].pid == 892 && nack[5].blp == 0x000f); + + const uint8_t data[] = {0x02, 0x77, 0x80, 0x28, 0x02, 0x88, 0x00, 0x21, 0x02, 0x9a, 0x00, 0x08, 0x02, + 0xb2, 0x20, 0x00, 0x02, 0xde, 0x00, 0x25, 0x02, 0xf5, 0x11, 0x00, 0x03, 0x09, + 0x00, 0x00, 0x03, 0x3a, 0x00, 0x02, 0x03, 0x61, 0x00, 0x00, 0x03, 0x72, 0x03, + 0x00, 0x03, 0x8b, 0x04, 0x00, 0x03, 0xa3, 0x00, 0x00, 0x03, 0xc3, 0x00, 0x6d}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_RTPFB_NACK << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_nack_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_tmmbr_test(void) +{ + const uint8_t data[] = {0x23, 0x45, 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_RTPFB_TMMBR << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tmmbr_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_tmmbn_test(void) +{ + const uint8_t data[] = {0x23, 0x45, 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_RTPFB_TMMBN << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tmmbn_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_tcc01_test(void) +{ + // rtcp_ccfb_t* ccfb; + // const uint8_t data[] = { 0x8f, 0xcd, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68, 0xc2, 0x49, 0x23, 0x8b, + // 0x01, 0x8b, 0x3e, 0x08, 0x49, 0x09, 0xd9, 0x00, 0x00, 0x0e, 0xa0, 0x00, 0x00, 0x1c, 0xa0, 0x00, 0x00, 0xe6, 0xa0, + // 0x00, 0x00, 0x49, 0x20, 0x01, 0x2c, 0xff, 0xf0, 0x0c, 0x44, 0x94, 0x8c, 0x50, 0x00, 0x00 }; assert(0 == + // rtcp_rtpfb_tcc01_unpack(NULL, NULL, 0, 0, data, sizeof(data))); assert(395 == num && ccfb[0].seq == 9099 && + // ccfb[0].received && ccfb[1].ecn == 0x01 && ccfb[0].ato == 11); assert(cfb[1].seq == 9100 && ccfb[1].received && + // ccfb[1].ecn == 0x02 && ccfb[1].ato == -4); + + const uint8_t data[] = {0x06, 0xe7, 0x00, 0x65, 0x3e, 0xc6, 0x50, 0x2a, 0x9a, 0xff, 0x20, 0x16, 0x97, 0x68, 0xbc, + 0xab, 0xa7, 0xfe, 0x20, 0x12, 0xc1, 0x50, 0x54, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01}; + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + msg.type = (RTCP_RTPFB_TCC01 << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tcc01_unpack(&rtp, NULL, &msg, data, sizeof(data))); + + // 11-768ms, 8-512ms, 7-448ms, 5-320ms + const uint8_t data2[] = {0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x05, 0x00, 0xd2, 0x82, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03}; + rtp.cbparam = (void *)data2; + msg.type = (30 << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tcc01_unpack(&rtp, NULL, &msg, data2, sizeof(data2))); + + // 248-33ms, 373-(-50)ms, 375-65ms, timestamp: -5546573 + const uint8_t data3[] = {0x00, 0xf8, 0x00, 0x80, 0xab, 0x5d, 0xb3, 0x01, 0xa0, 0x00, + 0x00, 0x6f, 0xe2, 0x00, 0x84, 0xff, 0x38, 0x01, 0x04, 0x01}; + rtp.cbparam = (void *)data3; + msg.type = (31 << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tcc01_unpack(&rtp, NULL, &msg, data3, sizeof(data3))); +} + +void rtcp_rtpfb_test(void) +{ + rtcp_rtpfb_nack_test(); + rtcp_rtpfb_tmmbr_test(); + rtcp_rtpfb_tmmbn_test(); + rtcp_rtpfb_tcc01_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-sdec.c b/src/tuya_p2p/lib_rtp/src/rtcp-sdec.c new file mode 100755 index 000000000..cbfb59e75 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-sdec.c @@ -0,0 +1,123 @@ +// RFC3550 6.5 SDES: Source Description RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_sdes_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + struct rtcp_msg_t msg; + struct rtp_member *member; + const unsigned char *p, *end; + + p = ptr; + end = ptr + bytes; + assert(header->length >= header->rc); + + for (i = 0; i < header->rc && p + 8 /*4-ssrc + 1-PT*/ <= end; i++) { + msg.ssrc = nbo_r32(p); + member = rtp_member_fetch(ctx, msg.ssrc); + if (!member) { + // continue; + } + + p += 4; + while (p + 2 <= end && RTCP_SDES_END != p[0] /*PT*/) { + msg.u.sdes.pt = p[0]; + msg.u.sdes.len = p[1]; + msg.u.sdes.data = (unsigned char *)(p + 2); + if (p + 2 + msg.u.sdes.len > end) { + assert(0); + return; // error + } + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + + switch (msg.u.sdes.pt) { + case RTCP_SDES_CNAME: + case RTCP_SDES_NAME: + case RTCP_SDES_EMAIL: + case RTCP_SDES_PHONE: + case RTCP_SDES_LOC: + case RTCP_SDES_TOOL: + case RTCP_SDES_NOTE: + rtp_member_setvalue(member, msg.u.sdes.pt, msg.u.sdes.data, msg.u.sdes.len); + break; + + case RTCP_SDES_PRIVATE: + // assert(0); + break; + + default: + // assert(0); + break; + } + + // RFC3550 6.5 SDES: Source Description RTCP Packet + // Items are contiguous, i.e., items are not individually padded to a 32-bit boundary. + // Text is not null terminated because some multi-octet encodings include null octets. + p += 2 + msg.u.sdes.len; + } + + // RFC3550 6.5 SDES: Source Description RTCP Packet + // The list of items in each chunk must be terminated by one or more null octets, + // the first of which is interpreted as an item type of zero to denote the end of the list. + // No length octet follows the null item type octet, + // but additional null octets must be included if needed to pad until the next 32-bit boundary. + // offset sizeof(SSRC) + sizeof(chunk type) + sizeof(chunk length) + p = (const unsigned char *)((p - (const unsigned char *)0 + 3) / 4 * 4); + } +} + +static size_t rtcp_sdes_append_item(unsigned char *ptr, size_t bytes, rtcp_sdes_item_t *sdes) +{ + assert(sdes->data); + if (bytes >= (size_t)sdes->len + 2) { + ptr[0] = sdes->pt; + ptr[1] = sdes->len; + memcpy(ptr + 2, sdes->data, sdes->len); + } + + return sdes->len + 2; +} + +int rtcp_sdes_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + int n; + rtcp_header_t header; + + // must have CNAME + if (!ctx->self->sdes[RTCP_SDES_CNAME].data) + return 0; + + header.v = 2; + header.p = 0; + header.pt = RTCP_SDES; + header.rc = 1; // self only + header.length = 0; + + n = (int)rtcp_sdes_append_item(ptr + 8, bytes - 8, &ctx->self->sdes[RTCP_SDES_CNAME]); + if (bytes < 8 + n) + return 8 + n; + + // RFC3550 6.3.9 Allocation of Source Description Bandwidth (p29) + // Every third interval (15 seconds), one extra item would be included in the SDES packet + if (0 == ctx->rtcp_cycle % 3 && ctx->rtcp_cycle / 3 > 0) // skip CNAME + { + assert(ctx->rtcp_cycle / 3 < RTCP_SDES_PRIVATE); + if (ctx->self->sdes[ctx->rtcp_cycle / 3 + 1].data) // skip RTCP_SDES_END + { + n += (int)rtcp_sdes_append_item(ptr + 8 + n, bytes - n - 8, &ctx->self->sdes[ctx->rtcp_cycle / 3 + 1]); + if (n + 8 > bytes) + return n + 8; + } + } + + ctx->rtcp_cycle = (ctx->rtcp_cycle + 1) % 24; // 3 * SDES item number + + header.length = (uint16_t)((n + 4 + 3) / 4); // see 6.4.1 SR: Sender Report RTCP Packet + nbo_write_rtcp_header(ptr, &header); + nbo_w32(ptr + 4, ctx->self->ssrc); + + return (header.length + 1) * 4; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-sr.c b/src/tuya_p2p/lib_rtp/src/rtcp-sr.c new file mode 100755 index 000000000..32e1d25ed --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-sr.c @@ -0,0 +1,168 @@ +// RFC3550 6.4.1 SR: Sender Report RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_sr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_sr_t *sr; + rtcp_rb_t *rb; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + assert(24 == sizeof(rtcp_sr_t)); + assert(24 == sizeof(rtcp_rb_t)); + if (bytes < 24 /*sizeof(rtcp_sr_t)*/ + header->rc * 24 /*sizeof(rtcp_rb_t)*/) { + assert(0); + return; + } + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_RR; + + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + + assert(sender != ctx->self); + assert(sender->rtcp_sr.ssrc == msg.ssrc); + // assert(sender->rtcp_rb.ssrc == msg.ssrc); + sender->rtcp_clock = rtpclock(); + + // update sender information + sr = &sender->rtcp_sr; + sr->ntpmsw = nbo_r32(ptr + 4); + sr->ntplsw = nbo_r32(ptr + 8); + sr->rtpts = nbo_r32(ptr + 12); + sr->spc = nbo_r32(ptr + 16); + sr->soc = nbo_r32(ptr + 20); + + ptr += 24; + // report block + for (i = 0; i < header->rc; i++, ptr += 24 /*sizeof(rtcp_rb_t)*/) { + msg.u.sr.ssrc = nbo_r32(ptr); + // if(msg.u.rr.ssrc != ctx->self->ssrc) + // continue; // ignore + // rb = &sender->rtcp_rb; + + rb = &msg.u.sr; + rb->fraction = ptr[4]; + rb->cumulative = (((uint32_t)ptr[5]) << 16) | (((uint32_t)ptr[6]) << 8) | ptr[7]; + rb->exthsn = nbo_r32(ptr + 8); + rb->jitter = nbo_r32(ptr + 12); + rb->lsr = nbo_r32(ptr + 16); + rb->dlsr = nbo_r32(ptr + 20); + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + } +} + +int rtcp_sr_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + uint32_t i, timestamp; + uint64_t ntp; + rtcp_header_t header; + + assert(24 == sizeof(rtcp_sr_t)); + assert(24 == sizeof(rtcp_rb_t)); + assert(rtp_member_list_count(ctx->senders) < 32); + header.v = 2; + header.p = 0; + header.pt = RTCP_SR; + header.rc = MIN(31, rtp_member_list_count(ctx->senders)); + header.length = (24 /*sizeof(rtcp_sr_t)*/ + header.rc * 24 /*sizeof(rtcp_rb_t)*/) / + 4; // see 6.4.1 SR: Sender Report RTCP Packet + + if ((uint32_t)bytes < (header.length + 1) * 4) + return (header.length + 1) * 4; + + nbo_write_rtcp_header(ptr, &header); + + // RFC3550 6.4.1 SR: Sender Report RTCP Packet (p32) + // Note that in most cases this timestamp will not be equal to the RTP + // timestamp in any adjacent data packet. Rather, it must be calculated from the corresponding + // NTP timestamp using the relationship between the RTP timestamp counter and real time as + // maintained by periodically checking the wallclock time at a sampling instant. + ntp = rtpclock(); + if (0 == ctx->self->rtp_packets) + ctx->self->rtp_clock = ntp; + timestamp = (uint32_t)((ntp - ctx->self->rtp_clock) * ctx->frequence / 1000000) + ctx->self->rtp_timestamp; + + ntp = clock2ntp(ntp); + nbo_w32(ptr + 4, ctx->self->ssrc); + nbo_w32(ptr + 8, (uint32_t)(ntp >> 32)); + nbo_w32(ptr + 12, (uint32_t)(ntp & 0xFFFFFFFF)); + nbo_w32(ptr + 16, timestamp); + nbo_w32(ptr + 20, ctx->self->rtp_packets); // send packets + nbo_w32(ptr + 24, (uint32_t)ctx->self->rtp_bytes); // send bytes + + ptr += 28; + // report block + for (i = 0; i < header.rc; i++) { + struct rtp_member *sender; + + sender = rtp_member_list_get(ctx->senders, i); + if (0 == sender->rtp_packets || sender->ssrc == ctx->self->ssrc) + continue; // don't receive any packet + + ptr += rtcp_report_block(sender, ptr, 24); + } + + return (header.length + 1) * 4; +} + +int rtcp_report_block(struct rtp_member *sender, uint8_t *ptr, int bytes) +{ + uint64_t delay; + int lost_interval; + int lost; + uint32_t fraction; + uint32_t expected, extseq; + uint32_t expected_interval; + uint32_t received_interval; + uint32_t lsr, dlsr; + + if (bytes < 24) + return 0; + + extseq = sender->rtp_seq_cycles + sender->rtp_seq; // 32-bits sequence number + assert(extseq >= sender->rtp_seq_base); + expected = extseq - sender->rtp_seq_base + 1; + expected_interval = expected - sender->rtp_expected0; + received_interval = sender->rtp_packets - sender->rtp_packets0; + lost_interval = (int)(expected_interval - received_interval); + if (lost_interval < 0 || 0 == expected_interval) + fraction = 0; + else + fraction = (lost_interval << 8) / expected_interval; + + lost = expected - sender->rtp_packets; + if (lost > 0x007FFFFF) { + lost = 0x007FFFFF; + } else if (lost < 0) { + // 'Clamp' this loss number to a 24-bit signed value: + // live555 RTCP.cpp RTCPInstance::enqueueReportBlock line:799 + lost = 0; + } + + delay = rtpclock() - sender->rtcp_clock; // now - Last SR time + lsr = ((sender->rtcp_sr.ntpmsw & 0xFFFF) << 16) | ((sender->rtcp_sr.ntplsw >> 16) & 0xFFFF); + // in units of 1/65536 seconds + // 65536/1000000 == 1024/15625 + dlsr = (uint32_t)(delay / 1000000.0f * 65536); + + nbo_w32(ptr, sender->ssrc); + ptr[4] = (unsigned char)fraction; + ptr[5] = (unsigned char)((lost >> 16) & 0xFF); + ptr[6] = (unsigned char)((lost >> 8) & 0xFF); + ptr[7] = (unsigned char)(lost & 0xFF); + nbo_w32(ptr + 8, extseq); + nbo_w32(ptr + 12, (uint32_t)sender->jitter); + nbo_w32(ptr + 16, lsr); + nbo_w32(ptr + 20, 0 == lsr ? 0 : dlsr); + + sender->rtp_expected0 = expected; // update source prior data + sender->rtp_packets0 = sender->rtp_packets; + + return 24; /*sizeof(rtcp_rb_t)*/ +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-xr.c b/src/tuya_p2p/lib_rtp/src/rtcp-xr.c new file mode 100755 index 000000000..5b2880170 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-xr.c @@ -0,0 +1,753 @@ +#include "rtp-internal.h" +#include "rtp-util.h" +#include + +// https://www.iana.org/assignments/rtcp-xr-block-types/rtcp-xr-block-types.xhtml +/* + BT Name Reference + 1 Loss RLE Report Block [RFC3611] + 2 Duplicate RLE Report Block [RFC3611] + 3 Packet Receipt Times Report Block [RFC3611] + 4 Receiver Reference Time Report Block [RFC3611] + 5 DLRR Report Block [RFC3611] + 6 Statistics Summary Report Block [RFC3611] + 7 VoIP Metrics Report Block [RFC3611] + 8 RTCP XR [RFC5093] + 9 Texas Instruments Extended VoIP Quality Block + [http://focus.ti.com/general/docs/bcg/bcgdoccenter.tsp?templateId=6116&navigationId=12078#42][David_Lide] 10 + Post-repair Loss RLE Report Block [RFC5725] 11 Multicast Acquisition Report Block [RFC6332] 12 + IDMS Report Block [RFC7272] 13 ECN Summary Report [RFC6679] 14 Measurement + Information Block [RFC6776] 15 Packet Delay Variation Metrics Block [RFC6798] 16 + Delay Metrics Block [RFC6843] 17 Burst/Gap Loss Summary Statistics Block + [RFC7004] 18 Burst/Gap Discard Summary Statistics Block [RFC7004] 19 Frame Impairment Statistics + Summary [RFC7004] 20 Burst/Gap Loss Metrics Block [RFC6958] + 21 Burst/Gap Discard Metrics Block [RFC7003][RFC Errata 3735] + 22 MPEG2 Transport Stream PSI-Independent Decodability Statistics Metrics Block [RFC6990] + 23 De-Jitter Buffer Metrics Block [RFC7005] + 24 Discard Count Metrics Block [RFC7002] + 25 DRLE (Discard RLE Report) [RFC7097] + 26 BDR (Bytes Discarded Report) [RFC7243] + 27 RFISD (RTP Flows Initial Synchronization Delay) [RFC7244] + 28 RFSO (RTP Flows Synchronization Offset Metrics Block) [RFC7244] + 29 MOS Metrics Block [RFC7266] + 30 LCB (Loss Concealment Metrics Block) [RFC7294, Section 4.1] + 31 CSB (Concealed Seconds Metrics Block) [RFC7294, Section 4.1] + 32 MPEG2 Transport Stream PSI Decodability Statistics Metrics Block [RFC7380] + 33 Post-Repair Loss Count Metrics Report Block [RFC7509] + 34 Video Loss Concealment Metric Report Block [RFC7867] + 35 Independent Burst/Gap Discard Metrics Block [RFC8015] + 36-254 Unassigned + 255 Reserved for future extensions [RFC3611] +*/ + +/* +Parameter Reference +pkt-loss-rle [RFC3611] +pkt-dup-rle [RFC3611] +pkt-rcpt-times [RFC3611] +stat-summary [RFC3611] +voip-metrics [RFC3611] +rcvr-rtt [RFC3611] +post-repair-loss-rle [RFC5725] +grp-sync [http://www.etsi.org/deliver/etsi_ts/183000_183099/183063/][ETSI 183 063][Miguel_Angel_Reina_Ortega] +multicast-acq [RFC6332] +ecn-sum [RFC6679] +pkt-dly-var [RFC6798] +delay [RFC6843] +burst-gap-loss-stat [RFC7004] +burst-gap-discard-stat [RFC7004] +frame-impairment-stat [RFC7004] +burst-gap-loss [RFC6958] +burst-gap-discard [RFC7003] +ts-psi-indep-decodability [RFC6990] +de-jitter-buffer [RFC7005] +pkt-discard-count [RFC7002] +discard-rle [RFC7097] +discard-bytes [RFC7243] +rtp-flow-init-syn-delay [RFC7244] +rtp-flow-syn-offset [RFC7244] +mos-metric [RFC7266] +loss-conceal [RFC7294] +conc-sec [RFC7294] +ts-psi-decodability [RFC7380] +post-repair-loss-count [RFC7509] +video-loss-concealment [RFC7867] +ind-burst-gap-discard [RFC8015] +*/ + +static int rtcp_xr_rrt_pack(uint64_t ntp, uint8_t *ptr, uint32_t bytes); +static int rtcp_xr_dlrr_pack(const rtcp_dlrr_t *dlrr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_xr_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes); + +static int rtcp_xr_lrle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_drle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_prt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_rrt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_dlrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=1 | rsvd. | T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk 1 | chunk 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk n-1 | chunk n | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_lrle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, j; + uint32_t source; + uint32_t len, num; + uint16_t seq, end; + uint32_t chunk; + uint8_t *v, v0[32]; + + len = nbo_r16(ptr + 2); + + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + len *= 4; // to bytes + if (len < 8) + return 0; + + source = nbo_r32(ptr + 4); + seq = nbo_r16(ptr + 8); + end = nbo_r16(ptr + 10); + num = end - seq; + + if ((num + 7) / 8 > sizeof(v0)) { + v = calloc((num + 7) / 8, sizeof(*v)); + if (!v) + return -ENOMEM; + } else { + v = v0; + memset(v, 0, (num + 7) / 8 * sizeof(v[0])); + } + + ptr += 8; + len -= 8; + for (i = 0; len > 2; ptr += 2, len -= 2) { + chunk = nbo_r16(ptr); + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + for (j = 0; j < (chunk & 0x3FFF) && i < num; j++, i++) { + if (0x4000 & chunk) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } else { + // Bit Vector Chunk + for (j = 0; j < 15 && i < num; j++, i++) { + if (chunk & (1 << (14 - j))) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } + } + + msg->u.xr.u.rle.source = source; + msg->u.xr.u.rle.begin = seq; + msg->u.xr.u.rle.end = end; + msg->u.xr.u.rle.chunk = v; + msg->u.xr.u.rle.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (v && v != v0) + free(v); + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=2 | rsvd. | T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk 1 | chunk 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk n-1 | chunk n | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_drle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, j; + uint32_t source; + uint32_t len, num; + uint16_t seq, end; + uint32_t chunk; + uint8_t *v, v0[32]; + + len = nbo_r16(ptr + 2); + + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + len *= 4; // to bytes + if (len < 8) + return 0; + + source = nbo_r32(ptr + 4); + seq = nbo_r16(ptr + 8); + end = nbo_r16(ptr + 10); + num = end - seq; + + if ((num + 7) / 8 > sizeof(v0)) { + v = calloc((num + 7) / 8, sizeof(*v)); + if (!v) + return -ENOMEM; + } else { + v = v0; + memset(v, 0, (num + 7) / 8 * sizeof(v[0])); + } + + ptr += 8; + len -= 8; + for (i = 0; len > 2; ptr += 2, len -= 2) { + chunk = nbo_r16(ptr); + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + for (j = 0; j < (chunk & 0x3FFF) && i < num; j++, i++) { + if (0x4000 & chunk) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } else { + // Bit Vector Chunk + for (j = 0; j < 15 && i < num; j++, i++) { + if (chunk & (1 << (14 - j))) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } + } + + msg->u.xr.u.rle.source = source; + msg->u.xr.u.rle.begin = seq; + msg->u.xr.u.rle.end = end; + msg->u.xr.u.rle.chunk = v; + msg->u.xr.u.rle.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (v && v != v0) + free(v); + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.3 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=3 | rsvd. | T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Receipt time of packet begin_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Receipt time of packet (begin_seq + 1) mod 65536 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Receipt time of packet (end_seq - 1) mod 65536 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_prt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + uint32_t source; + uint32_t len, num; + uint16_t seq, end; + uint32_t *timestamp, timestamp0[32]; + + len = nbo_r16(ptr + 2); + + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + len *= 4; // to bytes + if (len < 8) + return 0; + + source = nbo_r32(ptr + 4); + seq = nbo_r16(ptr + 8); + end = nbo_r16(ptr + 10); + num = end - seq; + + if (num > sizeof(timestamp0) / sizeof(timestamp0[0])) { + timestamp = calloc(num, sizeof(*timestamp)); + if (!timestamp) + return -ENOMEM; + } else { + timestamp = timestamp0; + memset(timestamp, 0, num * sizeof(timestamp[0])); + } + + ptr += 8; + len -= 8; + for (i = 0; len > 4; ptr += 4, len -= 4) { + timestamp[i] = nbo_r32(ptr); + } + + msg->u.xr.u.prt.source = source; + msg->u.xr.u.prt.begin = seq; + msg->u.xr.u.prt.end = end; + msg->u.xr.u.prt.timestamp = timestamp; + msg->u.xr.u.prt.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (timestamp && timestamp != timestamp0) + free(timestamp); + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.4 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=4 | reserved | block length = 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, most significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_rrt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t len; + uint64_t ntp; + + len = nbo_r16(ptr + 2); + + if (bytes < 12 || (len + 1) * 4 > bytes) + return -1; + + ntp = nbo_r32(ptr + 4); + ntp = (ntp << 32) | nbo_r32(ptr + 8); + + msg->u.xr.u.rrt = ntp; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_xr_rrt_pack(uint64_t ntp, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 12) + return -1; + + nbo_w32(ptr, (RTCP_XR_RRT << 24) | 2); + nbo_w32(ptr + 4, (uint32_t)(ntp >> 32)); + nbo_w32(ptr + 8, (uint32_t)ntp); + return 12; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.5 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=5 | reserved | block length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_1 (SSRC of first receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + | last RR (LRR) | 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last RR (DLRR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_2 (SSRC of second receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + : ... : 2 + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +*/ +static int rtcp_xr_dlrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + uint32_t len, num; + rtcp_dlrr_t *dlrr, dlrr0[32]; + + len = nbo_r16(ptr + 2); + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + num = len / 3; + if (num > sizeof(dlrr0)) { + dlrr = calloc(num, sizeof(*dlrr)); + if (!dlrr) + return -ENOMEM; + } else { + dlrr = dlrr0; + memset(dlrr, 0, num * sizeof(dlrr[0])); + } + + ptr += 4; + for (i = 0; i < num; i++, ptr += 12) { + dlrr[i].ssrc = nbo_r32(ptr + 0); + dlrr[i].lrr = nbo_r32(ptr + 4); + dlrr[i].dlrr = nbo_r32(ptr + 8); + } + + msg->u.xr.u.dlrr.dlrr = dlrr; + msg->u.xr.u.dlrr.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (dlrr && dlrr != dlrr0) + free(dlrr); + return 0; +} + +static int rtcp_xr_dlrr_pack(const rtcp_dlrr_t *dlrr, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + if ((int)bytes < 4 + count * 12) + return -1; + + nbo_w32(ptr, (RTCP_XR_DLRR << 24) | (count * 3)); + bytes -= 4; + ptr += 4; + + for (i = 0; i < count && bytes >= 12; i++) { + nbo_w32(ptr, dlrr[i].ssrc); + nbo_w32(ptr + 4, dlrr[i].lrr); + nbo_w32(ptr + 8, dlrr[i].dlrr); + + bytes -= 12; + ptr += 12; + } + return 4 + i * 12; +} + +// https://www.rfc-editor.org/rfc/rfc7097.html#section-5 +// rtcp-xr: discard-rle +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=25 |rsvd |E| T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk 1 | chunk 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk n-1 | chunk n | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// https://datatracker.ietf.org/doc/html/rfc7243#section-5 +// rtcp-xr: discard-bytes +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=26 | I |E|Reserved | Block length=2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Number of RTP payload bytes discarded | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// https://www.rfc-editor.org/rfc/rfc6679.html#section-5.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=13 | Reserved | Block Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of Media Sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (0) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (1) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECN-CE Counter | not-ECT Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Lost Packets Counter | Duplication Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 4: RTCP XR ECN Summary Report +*/ +static int rtcp_xr_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t len; + rtcp_ecn_t ecn; + + len = nbo_r16(ptr + 2); + if (bytes < 24 || (len + 1) * 4 > bytes) + return -1; + + ecn.ext_highest_seq = nbo_r32(ptr); // ssrc + ecn.ect[0] = nbo_r32(ptr + 4); + ecn.ect[1] = nbo_r32(ptr + 8); + ecn.ect_ce_counter = nbo_r16(ptr + 12); + ecn.not_ect_counter = nbo_r16(ptr + 14); + ecn.lost_packets_counter = nbo_r16(ptr + 16); + ecn.duplication_counter = nbo_r16(ptr + 18); + + memcpy(&msg->u.xr.u.ecn, &ecn, sizeof(msg->u.xr.u.ecn)); + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_xr_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 24) + return -1; + + nbo_w32(ptr, (RTCP_XR_ECN << 24) | 5); + nbo_w32(ptr + 4, ecn->ext_highest_seq); + nbo_w32(ptr + 8, ecn->ect[0]); + nbo_w32(ptr + 12, ecn->ect[1]); + nbo_w16(ptr + 16, ecn->ect_ce_counter); + nbo_w16(ptr + 18, ecn->not_ect_counter); + nbo_w16(ptr + 20, ecn->lost_packets_counter); + nbo_w16(ptr + 22, ecn->duplication_counter); + return 24; +} + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|reserved | PT=XR=207 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : report blocks : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT | type-specific | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : type-specific block contents : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +void rtcp_xr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + int r; + size_t len; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + if (bytes < 8 /*sizeof(rtcp_xr_t)*/) { + assert(0); + return; + } + + msg.ssrc = nbo_r32(ptr); + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + assert(sender != ctx->self); + + r = 0; + ptr += 4; + bytes -= 4; + while (bytes >= 4) { + len = nbo_r16(ptr + 2); + if (len * 4 > bytes - 4) + break; // invalid + + msg.type = RTCP_XR | (ptr[0] << 8); + switch (ptr[0]) { + case RTCP_XR_LRLE: + r = rtcp_xr_lrle_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_DRLE: + r = rtcp_xr_drle_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_PRT: + r = rtcp_xr_prt_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_RRT: + r = rtcp_xr_rrt_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_DLRR: + r = rtcp_xr_dlrr_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_ECN: + r = rtcp_xr_ecn_unpack(ctx, header, &msg, ptr, bytes); + break; + + default: + // assert(0); + r = 0; // ignore + break; + } + + ptr += len; + bytes -= len; + } + + return; +} + +int rtcp_xr_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr) +{ + int r; + rtcp_header_t header; + + (void)ctx; + if (bytes < 4 + 4) + return 4 + 4; + + switch (id) { + case RTCP_XR_RRT: + r = rtcp_xr_rrt_pack(xr->u.rrt, data + 8, bytes - 8); + break; + + case RTCP_XR_DLRR: + r = rtcp_xr_dlrr_pack(xr->u.dlrr.dlrr, xr->u.dlrr.count, data + 8, bytes - 8); + break; + + case RTCP_XR_ECN: + r = rtcp_xr_ecn_pack(&xr->u.ecn, data + 8, bytes - 8); + break; + + case RTCP_XR_LRLE: + case RTCP_XR_DRLE: + case RTCP_XR_PRT: + default: + assert(0); + return -1; + } + + header.v = 2; + header.p = 0; + header.pt = RTCP_XR; + header.rc = id; + header.length = (r + 4 + 3) / 4; + nbo_write_rtcp_header(data, &header); + + nbo_w32(data + 4, ctx->self->ssrc); + // nbo_w32(data + 4, xr->sender); + + // assert(8 == (header.length + 1) * 4); + return header.length * 4 + 4; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void rtcp_on_xr_test(void *param, const struct rtcp_msg_t *msg) +{ + int r; + static uint8_t buffer[1400]; + switch (msg->type & 0xFF) { + case RTCP_XR: + switch ((msg->type >> 8) & 0xFF) { + case RTCP_XR_RRT: + assert(0x1234567823456789 == msg->u.xr.u.rrt); + r = rtcp_xr_rrt_pack(msg->u.xr.u.rrt, buffer, sizeof(buffer)); + assert(12 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_XR_DLRR: + assert(1 == msg->u.xr.u.dlrr.count); + assert(0x12345678 == msg->u.xr.u.dlrr.dlrr[0].ssrc && 0x23344556 == msg->u.xr.u.dlrr.dlrr[0].lrr && + 0x33343536 == msg->u.xr.u.dlrr.dlrr[0].dlrr); + r = rtcp_xr_dlrr_pack(msg->u.xr.u.dlrr.dlrr, msg->u.xr.u.dlrr.count, buffer, sizeof(buffer)); + assert(16 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_XR_ECN: + r = rtcp_xr_ecn_pack(&msg->u.xr.u.ecn, buffer, sizeof(buffer)); + assert(r > 0 && 0 == memcmp(buffer, param, r)); + + default: + break; + } + break; + + default: + assert(0); + } +} + +static void rtcp_rtpfb_rrt_test(void) +{ + const uint8_t data[] = {0x04, 0x00, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_xr_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_XR_RRT << 8) | RTCP_XR; + assert(0 == rtcp_xr_rrt_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_dlrr_test(void) +{ + const uint8_t data[] = {0x05, 0x00, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + 0x23, 0x34, 0x45, 0x56, 0x33, 0x34, 0x35, 0x36}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_xr_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_XR_DLRR << 8) | RTCP_XR; + assert(0 == rtcp_xr_dlrr_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_ecn_test(void) +{ + const uint8_t data[] = {0x00}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_xr_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_XR_ECN << 8) | RTCP_XR; + assert(0 == rtcp_xr_ecn_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +void rtcp_xr_test(void) +{ + rtcp_rtpfb_rrt_test(); + rtcp_rtpfb_dlrr_test(); + // rtcp_rtpfb_ecn_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp.c b/src/tuya_p2p/lib_rtp/src/rtcp.c new file mode 100755 index 000000000..54230dede --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp.c @@ -0,0 +1,235 @@ +#include "rtp-internal.h" +#include "rtp-packet.h" +#include "rtp-util.h" +#include +#include +#include + +// enum { +// RTCP_MSG_MEMBER, /// new member(re-calculate RTCP Transmission Interval) +// RTCP_MSG_EXPIRED, /// member leave(re-calculate RTCP Transmission Interval) +// }; + +static void rtp_seq_init(struct rtp_member *sender, uint16_t seq) +{ + sender->rtp_seq = seq; + sender->rtp_seq_bad = (1 << 16) + 1; /* so seq == bad_seq is false */ + sender->rtp_seq_base = seq; + sender->rtp_seq_cycles = 0; + sender->rtp_packets0 = 0; + sender->rtp_expected0 = 0; + sender->rtp_bytes = 0; + sender->rtp_packets = 0; + sender->rtp_probation = 0; + sender->jitter = 0.0; +} + +static int rtp_seq_update(struct rtp_member *sender, uint16_t seq) +{ + uint16_t delta; + delta = seq - sender->rtp_seq; + + if (sender->rtp_probation > 0) { + if (sender->rtp_seq + 1 == seq) { + sender->rtp_seq = seq; + if (0 == --sender->rtp_probation) { + rtp_seq_init(sender, seq); + return 1; + } + } else { + sender->rtp_probation = RTP_PROBATION; + sender->rtp_seq = seq; + } + return 0; + } else if (delta < RTP_DROPOUT) { + // in order, with permissible gap + if (seq < sender->rtp_seq) { + // sequence number wrapped + sender->rtp_seq_cycles += (1 << 16); + } + + sender->rtp_seq = seq; + } else if (delta <= (1 << 16) - RTP_MISORDER) { + /* the sequence number made a very large jump */ + if (sender->rtp_seq_bad + 1 == seq) { + rtp_seq_init(sender, seq); + } else { + sender->rtp_seq_bad = seq; + return 0; + } + } else { + // duplicate or reordered packet + } + + return 1; +} + +struct rtp_member *rtp_member_fetch(struct rtp_context *ctx, uint32_t ssrc) +{ + struct rtp_member *p; + p = rtp_member_list_find(ctx->members, ssrc); + if (!p) { + // exist in sender list? + assert(!rtp_member_list_find(ctx->senders, ssrc)); + + p = rtp_member_create(ssrc); + if (p) { + struct rtcp_msg_t msg; + + // update members list + rtp_member_list_add(ctx->members, p); + rtp_member_release(p); + + // msg.type = RTCP_MSG_MEMBER; + // msg.u.member.ssrc = ssrc; + // ctx->handler.on_rtcp(ctx->cbparam, &msg); + } + } + return p; +} + +struct rtp_member *rtp_sender_fetch(struct rtp_context *ctx, uint32_t ssrc) +{ + struct rtp_member *p; + p = rtp_member_list_find(ctx->senders, ssrc); + if (!p) { + p = rtp_member_fetch(ctx, ssrc); + if (p) { + // update senders list + rtp_member_list_add(ctx->senders, p); + } + } + return p; +} + +static int rtcp_parse(struct rtp_context *ctx, const unsigned char *data, size_t bytes) +{ + int n; + uint32_t rtcphd; + rtcp_header_t header; + + assert(bytes >= sizeof(rtcphd)); + rtcphd = nbo_r32(data); + + header.v = RTCP_V(rtcphd); + header.p = RTCP_P(rtcphd); + header.rc = RTCP_RC(rtcphd); + header.pt = RTCP_PT(rtcphd); + header.length = RTCP_LEN(rtcphd); + n = header.length * 4 + 4; + + // 1. RTP version field must equal 2 (p69) + // 2. The payload type filed of the first RTCP packet in a compound packet must be SR or RR (p69) + // 3. padding only valid at the last packet + if (n > bytes || 2 != header.v || (header.p && (header.length < 1 || header.length * 4 < data[n - 1]))) { + assert(0); + return -1; + } + + if (header.p) + n -= data[n - 1]; + + switch (header.pt) { + case RTCP_SR: + rtcp_sr_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_RR: + rtcp_rr_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_SDES: + rtcp_sdes_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_BYE: + rtcp_bye_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_APP: + rtcp_app_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_RTPFB: + rtcp_rtpfb_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_PSFB: + rtcp_psfb_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_XR: + rtcp_xr_unpack(ctx, &header, data + 4, n - 4); + break; + + default: + assert(0); + } + + return (RTCP_LEN(rtcphd) + 1) * 4; +} + +int rtcp_input_rtcp(struct rtp_context *ctx, const void *data, int bytes) +{ + int r; + const unsigned char *p; + + // RFC3550 6.1 RTCP Packet Format + // 1. The first RTCP packet in the compound packet must always be a report packet to facilitate header validation + // 2. An SDES packet containing a CNAME item must be included in each compound RTCP packet + // 3. BYE should be the last packet sent with a given SSRC/CSRC. + p = (const unsigned char *)data; + while (bytes > 4) { + // compound RTCP packet + r = rtcp_parse(ctx, p, bytes); + if (r <= 0) + break; + + // RFC3550 6.3.3 Receiving an RTP or Non-BYE RTCP Packet (p26) + ctx->avg_rtcp_size = (int)(ctx->avg_rtcp_size * 1.0 / 16 + r * 15.0 / 16); + + p += r; + bytes -= r; + } + return 0; +} + +int rtcp_input_rtp(struct rtp_context *ctx, const void *data, int bytes) +{ + uint64_t clock; + struct rtp_packet_t pkt; + struct rtp_member *sender; + + if (0 != rtp_packet_deserialize(&pkt, data, bytes)) + return -1; // packet error + + assert(2 == pkt.rtp.v); + sender = rtp_sender_fetch(ctx, pkt.rtp.ssrc); + if (!sender) + return -1; // memory error + + clock = rtpclock(); + + // RFC3550 A.1 RTP Data Header Validity Checks + if (0 == rtp_seq_update(sender, (uint16_t)pkt.rtp.seq)) + return 0; // disorder(need more data) + + // RFC3550 A.8 Estimating the Interarrival Jitter + // the jitter estimate is updated: + if (0 != sender->rtp_packets) { + int D; + D = (int)((unsigned int)((clock - sender->rtp_clock) * ctx->frequence / 1000000) - + (pkt.rtp.timestamp - sender->rtp_timestamp)); + if (D < 0) + D = -D; + sender->jitter += (D - sender->jitter) / 16.0; + } else { + sender->jitter = 0.0; + } + + sender->rtp_clock = clock; + sender->rtp_timestamp = pkt.rtp.timestamp; + sender->rtp_bytes += pkt.payloadlen; + sender->rtp_packets += 1; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-demuxer.c b/src/tuya_p2p/lib_rtp/src/rtp-demuxer.c new file mode 100755 index 000000000..2a6b48660 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-demuxer.c @@ -0,0 +1,247 @@ +#include "rtp-demuxer.h" +#include "rtp-internal.h" +#include "rtp-payload.h" +#include "rtp-packet.h" +#include "rtp-queue.h" +#include "rtp-param.h" +#include "rtp.h" +#include "rtcp-header.h" +#include +#include +#include +#include +#include + +struct rtp_demuxer_t { + uint32_t ssrc; + uint64_t clock; // rtcp clock + + uint8_t *ptr; + int cap, max; + + rtp_queue_t *queue; + void *payload; + void *rtp; + + rtp_demuxer_onpacket onpkt; + void *param; +}; + +static int rtp_onpacket(void *param, const void *packet, int bytes, uint32_t timestamp, int flags) +{ + struct rtp_demuxer_t *rtp; + rtp = (struct rtp_demuxer_t *)param; + + // TODO: rtp timestamp -> pts/dts + + return rtp->onpkt ? rtp->onpkt(rtp->param, packet, bytes, timestamp, flags) : -1; +} + +static void rtp_on_rtcp(void *param, const struct rtcp_msg_t *msg) +{ + // struct rtp_demuxer_t* rtp; + // rtp = (struct rtp_demuxer_t*)param; + if (RTCP_BYE == msg->type) { + printf("finished: %p\n", param); + // rtp->onpkt(rtp->param, NULL, 0, 0, 0); + } +} + +static struct rtp_packet_t *rtp_demuxer_alloc(struct rtp_demuxer_t *rtp, const void *data, int bytes) +{ + int r; + uint8_t *ptr; + struct rtp_packet_t *pkt; + + if (rtp->cap < bytes + (int)sizeof(struct rtp_packet_t) + (int)sizeof(int) /*bytes*/) { + r = bytes + sizeof(struct rtp_packet_t) + sizeof(int); + r = r > 1500 ? r : 1500; + ptr = (uint8_t *)realloc(rtp->ptr, r + sizeof(int) /*cap*/); + if (!ptr) + return NULL; + + rtp->cap = r; + rtp->ptr = ptr; + *(int *)ptr = r; /*cap*/ + } + + *((int *)rtp->ptr + 1) = bytes; /*bytes*/ + pkt = (struct rtp_packet_t *)(rtp->ptr + sizeof(int) /*cap*/ + sizeof(int) /*bytes*/); + memcpy(pkt + 1, data, bytes); + + r = rtp_packet_deserialize(pkt, pkt + 1, bytes); + if (0 != r) + return NULL; + + rtp->cap = 0; // need more memory + rtp->ptr = NULL; + return pkt; +} + +static void rtp_demuxer_freepkt(void *param, struct rtp_packet_t *pkt) +{ + int cap; + uint8_t *ptr; + struct rtp_demuxer_t *rtp; + rtp = (struct rtp_demuxer_t *)param; + ptr = (uint8_t *)pkt - sizeof(int) /*cap*/ - sizeof(int) /*bytes*/; + cap = *(int *)ptr; + + if (cap <= rtp->cap) { + free(ptr); + return; + } + + if (rtp->cap > 0 && rtp->ptr) + free(rtp->ptr); + rtp->cap = cap; + rtp->ptr = ptr; +} + +static int rtp_demuxer_init(struct rtp_demuxer_t *rtp, int jitter, int frequency, int payload, const char *encoding) +{ + uint32_t timestamp; + struct rtp_event_t evthandler; + struct rtp_payload_t handler; + // const struct rtp_profile_t* profile; + // profile = rtp_profile_find(payload); + // frequency = profile ? profile->frequency : 90000; + + memset(&handler, 0, sizeof(handler)); + handler.alloc = NULL; + handler.free = NULL; + handler.packet = rtp_onpacket; + rtp->payload = rtp_payload_decode_create(payload, encoding, &handler, rtp); + + timestamp = (uint32_t)rtpclock(); + evthandler.on_rtcp = rtp_on_rtcp; + rtp->rtp = rtp_create(&evthandler, rtp, rtp->ssrc, timestamp, frequency ? frequency : 90000, 2 * 1024 * 1024, 0); + + rtp->queue = rtp_queue_create(jitter, frequency, rtp_demuxer_freepkt, rtp); + + return rtp->payload && rtp->rtp && rtp->queue ? 0 : -1; +} + +struct rtp_demuxer_t *rtp_demuxer_create(int jitter, int frequency, int payload, const char *encoding, + rtp_demuxer_onpacket onpkt, void *param) +{ + struct rtp_demuxer_t *rtp; + rtp = (struct rtp_demuxer_t *)calloc(1, sizeof(*rtp)); + if (!rtp) + return NULL; + + if (0 != rtp_demuxer_init(rtp, jitter, frequency, payload, encoding)) { + rtp_demuxer_destroy(&rtp); + return NULL; + } + + rtp->onpkt = onpkt; + rtp->param = param; + rtp->clock = rtpclock(); + rtp->ssrc = rtp_ssrc(); + rtp->max = RTP_PAYLOAD_MAX_SIZE; + return rtp; +} + +int rtp_demuxer_destroy(struct rtp_demuxer_t **pprtp) +{ + struct rtp_demuxer_t *rtp; + if (pprtp && *pprtp) { + rtp = *pprtp; + if (rtp->rtp) + rtp_destroy(rtp->rtp); + + if (rtp->payload) + rtp_payload_decode_destroy(rtp->payload); + + if (rtp->queue) + rtp_queue_destroy(rtp->queue); + + if (rtp->ptr) + free(rtp->ptr); + free(rtp); + } + + return 0; +} + +int rtp_demuxer_input(struct rtp_demuxer_t *rtp, const void *data, int bytes) +{ + int r; + uint8_t pt; + struct rtp_packet_t *pkt; + + if (bytes < 12 || bytes > rtp->max) + return -EINVAL; + + pt = ((uint8_t *)data)[1]; + // RFC7983 SRTP: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes + // http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4 + // RFC 5761 (RTCP-mux) states this range for secure RTCP/RTP detection. + // RTCP packet types in the ranges 1-191 and 224-254 SHOULD only be used when other values have been exhausted. + if (pt < RTCP_FIR || pt > RTCP_LIMIT) { + pkt = rtp_demuxer_alloc(rtp, data, bytes); + if (!pkt) + return -ENOMEM; + + r = rtp_queue_write(rtp->queue, pkt); + if (r <= 0) // 0-discard packet(duplicate/too late) + { + rtp_demuxer_freepkt(rtp, pkt); + return r; + } + + // re-order packet + pkt = rtp_queue_read(rtp->queue); + while (pkt) { + bytes = *(int *)((uint8_t *)pkt - sizeof(int) /*bytes*/); + + r = rtp_onreceived(rtp->rtp, pkt + 1, bytes); + r = rtp_payload_decode_input(rtp->payload, pkt + 1, bytes); + rtp_demuxer_freepkt(rtp, pkt); + if (r < 0) + return r; + + pkt = rtp_queue_read(rtp->queue); + } + } else { + r = rtp_onreceived_rtcp(rtp->rtp, data, bytes); + (void)r; // ignore rtcp handler + + return pt; // rtcp message type + } + + return 0; +} + +int rtp_demuxer_rtcp(struct rtp_demuxer_t *rtp, void *buf, int len) +{ + int r; + int interval; + uint64_t clock; + + r = 0; + clock = rtpclock(); + interval = rtp_rtcp_interval(rtp->rtp); + if (rtp->clock + (uint64_t)interval * 1000 < clock) { + // RTCP report + r = rtp_rtcp_report(rtp->rtp, buf, len); + rtp->clock = clock; + } + + return r; +} + +void rtp_demuxer_stats(struct rtp_demuxer_t *rtp, int *lost, int *late, int *misorder, int *duplicate) +{ + struct rtp_queue_stats_t stats; + rtp_queue_stats(rtp->queue, &stats); + if (lost) + *lost = stats.lost; + if (late) + *late = stats.late; + if (misorder) + *misorder = stats.reorder; + if (duplicate) + *duplicate = stats.duplicate; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-member-list.c b/src/tuya_p2p/lib_rtp/src/rtp-member-list.c new file mode 100755 index 000000000..e47cd049e --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-member-list.c @@ -0,0 +1,129 @@ +#include "rtp-member-list.h" +#include +#include +#include +#include + +#define N_SOURCE 2 // unicast(1S + 1R) + +struct rtp_member_list { + struct rtp_member *members[N_SOURCE]; + struct rtp_member **ptr; + int count; + int capacity; +}; + +void *rtp_member_list_create() +{ + return (struct rtp_member_list *)calloc(1, sizeof(struct rtp_member_list)); +} + +void rtp_member_list_destroy(void *members) +{ + int i; + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + for (i = 0; i < p->count; i++) { + rtp_member_release(i >= N_SOURCE ? p->ptr[i - N_SOURCE] : p->members[i]); + } + + if (p->ptr) { + assert(p->capacity > 0); + free(p->ptr); + } + + free(p); +} + +int rtp_member_list_count(void *members) +{ + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + return p->count; +} + +struct rtp_member *rtp_member_list_get(void *members, int index) +{ + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + if (index >= p->count || index < 0) + return NULL; + + return index >= N_SOURCE ? p->ptr[index - N_SOURCE] : p->members[index]; +} + +struct rtp_member *rtp_member_list_find(void *members, uint32_t ssrc) +{ + int i; + struct rtp_member *s; + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + for (i = 0; i < p->count; i++) { + s = i < N_SOURCE ? p->members[i] : p->ptr[i - N_SOURCE]; + if (s->ssrc == ssrc) + return s; + } + return NULL; +} + +int rtp_member_list_add(void *members, struct rtp_member *s) +{ + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + if (p->count >= N_SOURCE) { + if (p->count - N_SOURCE >= p->capacity) { + void *ptr; + ptr = (struct rtp_member **)realloc(p->ptr, (p->capacity + 8) * sizeof(struct rtp_member *)); + if (!ptr) + return -ENOMEM; + p->ptr = ptr; + p->capacity += 8; + } + p->ptr[p->count - N_SOURCE] = s; + } else { + p->members[p->count] = s; + } + + rtp_member_addref(s); + p->count++; + return 0; +} + +int rtp_member_list_delete(void *members, uint32_t ssrc) +{ + int i; + struct rtp_member *s; + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + for (i = 0; i < p->count; i++) { + s = i < N_SOURCE ? p->members[i] : p->ptr[i - N_SOURCE]; + if (s->ssrc != ssrc) + continue; + + if (i < N_SOURCE) { + if (i + 1 < N_SOURCE) { + memmove(p->members + i, p->members + i + 1, (N_SOURCE - i - 1) * sizeof(struct rtp_member *)); + } + + if (p->count > N_SOURCE) { + p->members[N_SOURCE - 1] = p->ptr[0]; + memmove(p->ptr, p->ptr + 1, (p->count - N_SOURCE - 1) * sizeof(struct rtp_member *)); + } + } else { + if (i + 1 < p->count) { + memmove(p->ptr + i - N_SOURCE, p->ptr + i + 1 - N_SOURCE, + (p->count - i - 1) * sizeof(struct rtp_member *)); + } + } + + rtp_member_release(s); + p->count--; + return 0; + } + + return -1; // NOT_FOUND +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-member.c b/src/tuya_p2p/lib_rtp/src/rtp-member.c new file mode 100755 index 000000000..8a911d097 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-member.c @@ -0,0 +1,68 @@ +#include "rtp-member.h" +#include +#include +#include +#include + +struct rtp_member *rtp_member_create(uint32_t ssrc) +{ + struct rtp_member *p; + p = (struct rtp_member *)calloc(1, sizeof(struct rtp_member)); + if (!p) + return NULL; + + p->ref = 1; + p->ssrc = ssrc; + p->jitter = 0.0; + p->rtp_probation = RTP_PROBATION; + p->rtcp_sr.ssrc = ssrc; + // p->rtcp_rb.ssrc = ssrc; + return p; +} + +void rtp_member_addref(struct rtp_member *member) +{ + assert(member->ref > 0); + ++member->ref; +} + +void rtp_member_release(struct rtp_member *member) +{ + size_t i; + assert(member->ref > 0); + if (0 == --member->ref) { + for (i = 0; i < sizeof(member->sdes) / sizeof(member->sdes[0]); i++) { + if (member->sdes[i].data) { + assert(member->sdes[i].pt == i); + assert(member->sdes[i].len > 0); + free(member->sdes[i].data); + } + } + + free(member); + } +} + +int rtp_member_setvalue(struct rtp_member *member, int item, const uint8_t *data, int bytes) +{ + rtcp_sdes_item_t *sdes; + assert(RTCP_SDES_CNAME <= item && item <= RTCP_SDES_PRIVATE); + if (item >= sizeof(member->sdes) / sizeof(member->sdes[0]) || bytes > 255) + return -1; + + sdes = &member->sdes[item]; + + if (bytes > sdes->len) { + void *p = realloc(sdes->data, bytes); + if (!p) + return -1; // no memory + sdes->data = p; + } + + assert(bytes <= 255); + if (bytes > 0) + memcpy(sdes->data, data, bytes); + sdes->pt = (uint8_t)item; + sdes->len = (uint8_t)bytes; + return 0; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-packet.c b/src/tuya_p2p/lib_rtp/src/rtp-packet.c new file mode 100755 index 000000000..2507a40be --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-packet.c @@ -0,0 +1,138 @@ +#include "rtp-packet.h" +#include "rtp-util.h" +#include +#include + +// RFC3550 RTP: A Transport Protocol for Real-Time Applications +// 5.1 RTP Fixed Header Fields (p12) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +int rtp_packet_deserialize(struct rtp_packet_t *pkt, const void *data, int bytes) +{ + uint32_t i, v; + int hdrlen; + const uint8_t *ptr; + + if (bytes < RTP_FIXED_HEADER) // RFC3550 5.1 RTP Fixed Header Fields(p12) + return -1; + ptr = (const unsigned char *)data; + memset(pkt, 0, sizeof(struct rtp_packet_t)); + + // pkt header + v = nbo_r32(ptr); + pkt->rtp.v = RTP_V(v); + pkt->rtp.p = RTP_P(v); + pkt->rtp.x = RTP_X(v); + pkt->rtp.cc = RTP_CC(v); + pkt->rtp.m = RTP_M(v); + pkt->rtp.pt = RTP_PT(v); + pkt->rtp.seq = RTP_SEQ(v); + pkt->rtp.timestamp = nbo_r32(ptr + 4); + pkt->rtp.ssrc = nbo_r32(ptr + 8); + assert(RTP_VERSION == pkt->rtp.v); + + hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4; + if (RTP_VERSION != pkt->rtp.v || bytes < hdrlen + (pkt->rtp.x ? 4 : 0) + (pkt->rtp.p ? 1 : 0)) + return -1; + + // pkt contributing source + for (i = 0; i < pkt->rtp.cc; i++) { + pkt->csrc[i] = nbo_r32(ptr + 12 + i * 4); + } + + assert(bytes >= hdrlen); + pkt->payload = (uint8_t *)ptr + hdrlen; + pkt->payloadlen = bytes - hdrlen; + + // pkt header extension + if (1 == pkt->rtp.x) { + const uint8_t *rtpext = ptr + hdrlen; + assert(pkt->payloadlen >= 4); + pkt->extension = rtpext + 4; + pkt->extprofile = nbo_r16(rtpext); + pkt->extlen = nbo_r16(rtpext + 2) * 4; + if (pkt->extlen + 4 > pkt->payloadlen) { + assert(0); + return -1; + } else { + pkt->payload = rtpext + pkt->extlen + 4; + pkt->payloadlen -= pkt->extlen + 4; + } + } + + // padding + if (1 == pkt->rtp.p) { + uint8_t padding = ptr[bytes - 1]; + if (pkt->payloadlen < padding) { + assert(0); + return -1; + } else { + pkt->payloadlen -= padding; + } + } + + return 0; +} + +int rtp_packet_serialize_header(const struct rtp_packet_t *pkt, void *data, int bytes) +{ + int hdrlen; + uint32_t i; + uint8_t *ptr; + + if (RTP_VERSION != pkt->rtp.v || 0 != (pkt->extlen % 4)) { + assert(0); // RTP version field must equal 2 (p66) + return -1; + } + + // RFC3550 5.1 RTP Fixed Header Fields(p12) + hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4 + (pkt->rtp.x ? 4 : 0); + if (bytes < hdrlen + pkt->extlen) + return -1; + + ptr = (uint8_t *)data; + nbo_write_rtp_header(ptr, &pkt->rtp); + ptr += RTP_FIXED_HEADER; + + // pkt contributing source + for (i = 0; i < pkt->rtp.cc; i++, ptr += 4) { + nbo_w32(ptr, pkt->csrc[i]); + } + + // pkt header extension + if (1 == pkt->rtp.x) { + // 5.3.1 RTP Header Extension + assert(0 == (pkt->extlen % 4)); + nbo_w16(ptr, pkt->extprofile); + nbo_w16(ptr + 2, pkt->extlen / 4); + memcpy(ptr + 4, pkt->extension, pkt->extlen); + ptr += pkt->extlen + 4; + } + + return hdrlen + pkt->extlen; +} + +int rtp_packet_serialize(const struct rtp_packet_t *pkt, void *data, int bytes) +{ + int hdrlen; + + hdrlen = rtp_packet_serialize_header(pkt, data, bytes); + if (hdrlen < RTP_FIXED_HEADER || hdrlen + pkt->payloadlen > bytes) + return -1; + + memcpy(((uint8_t *)data) + hdrlen, pkt->payload, pkt->payloadlen); + return hdrlen + pkt->payloadlen; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-profile.c b/src/tuya_p2p/lib_rtp/src/rtp-profile.c new file mode 100755 index 000000000..26da0aa3c --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-profile.c @@ -0,0 +1,68 @@ +#include "rtp-profile.h" + +static struct rtp_profile_t s_profiles[] = { + // audio + {0, RTP_TYPE_AUDIO, 1, 8000, "PCMU"}, // G711 mu-law + {1, RTP_TYPE_UNKNOWN, 1, 8000, "FS-1016 CELP"}, // reserved + {2, RTP_TYPE_UNKNOWN, 1, 8000, "G721"}, // reserved + {3, RTP_TYPE_AUDIO, 1, 8000, "GSM"}, + {4, RTP_TYPE_AUDIO, 1, 8000, "G723"}, + {5, RTP_TYPE_AUDIO, 1, 8000, "DVI4"}, + {6, RTP_TYPE_AUDIO, 1, 16000, "DVI4"}, + {7, RTP_TYPE_AUDIO, 1, 8000, "LPC"}, + {8, RTP_TYPE_AUDIO, 1, 8000, "PCMA"}, // G711 A-law + {9, RTP_TYPE_AUDIO, 1, 8000, "G722"}, + {10, RTP_TYPE_AUDIO, 2, 44100, "L16"}, // PCM S16BE + {11, RTP_TYPE_AUDIO, 1, 44100, "L16"}, // PCM S16BE + {12, RTP_TYPE_AUDIO, 1, 8000, "QCELP"}, + {13, RTP_TYPE_AUDIO, 1, 8000, "CN"}, + {14, RTP_TYPE_AUDIO, 2, 90000, "MPA"}, // MPEG-1/MPEG-2 audio 1/2 channels + {15, RTP_TYPE_AUDIO, 1, 8000, "G728"}, + {16, RTP_TYPE_AUDIO, 1, 11025, "DVI4"}, + {17, RTP_TYPE_AUDIO, 1, 22050, "DVI4"}, + {18, RTP_TYPE_AUDIO, 1, 8000, "G729"}, + {19, RTP_TYPE_UNKNOWN, 0, 0, "CN"}, // reserved + {20, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {21, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {22, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {23, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {24, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + //{ 0, "G726-40", 8000, 1 }, + //{ 0, "G726-32", 8000, 1 }, + //{ 0, "G726-24", 8000, 1 }, + //{ 0, "G726-16", 8000, 1 }, + //{ 0, "G729-D", 8000, 1 }, + //{ 0, "G729-E", 8000, 1 }, + //{ 0, "GSM-EFR", 8000, 1 }, + //{ 0, "L8", var, 1 }, + + // video + {25, RTP_TYPE_VIDEO, 0, 90000, "CELB"}, // SUN CELL-B + {26, RTP_TYPE_VIDEO, 0, 90000, "JPEG"}, // MJPEG + {27, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {28, RTP_TYPE_VIDEO, 0, 90000, "nv"}, + {29, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {30, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {31, RTP_TYPE_VIDEO, 0, 90000, "H261"}, + {32, RTP_TYPE_VIDEO, 0, 90000, "MPV"}, // MPEG-1/MPEG-2 video + {33, RTP_TYPE_SYSTEM, 0, 90000, "MP2T"}, // MPEG-2 TS + {34, RTP_TYPE_VIDEO, 0, 90000, "H263"}, + //{ 0, "H263-1998",90000, 0 }, + + // 35-71 unassigned + // 72-76 reserved + // 77-95 unassigned + // 96-127 dynamic + //{ 96,RTP_TYPE_VIDEO, 0, 90000, "MPG4" }, // RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary + //Streams + //{ 97,RTP_TYPE_SYSTEM, 0, 90000, "MP2P" }, // RFC3555 4.2.11 Registration of MIME media type video/MP2P + //{ 98,RTP_TYPE_VIDEO, 0, 90000, "H264" }, // RFC6184 RTP Payload Format for H.264 Video +}; + +const struct rtp_profile_t *rtp_profile_find(int payload) +{ + if (payload < 0 || payload >= 35) + return 0; + + return RTP_TYPE_UNKNOWN == s_profiles[payload].avtype ? 0 : &s_profiles[payload]; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-queue.c b/src/tuya_p2p/lib_rtp/src/rtp-queue.c new file mode 100755 index 000000000..e645a66f3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-queue.c @@ -0,0 +1,467 @@ +// RFC2326 A.1 RTP Data Header Validity Checks + +#include "rtp-queue.h" +#include +#include +#include +#include + +#define MAX_PACKET 3000 + +#define RTP_MISORDER 300 +#define RTP_DROPOUT 1000 +#define RTP_SEQUENTIAL 3 +#define RTP_SEQMOD (1 << 16) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +struct rtp_item_t { + struct rtp_packet_t *pkt; + // uint64_t clock; +}; + +struct rtp_queue_t { + struct rtp_item_t *items; + int capacity; + int size; + int pos; // ring buffer read position + + int probation; + int cycles; + uint16_t last_seq; + uint16_t first_seq; + + int bad_count; + uint16_t bad_seq; + struct rtp_item_t bad_items[RTP_SEQUENTIAL + 1]; + + int threshold; + int frequency; + void (*free)(void *, struct rtp_packet_t *); + void *param; + + struct rtp_queue_stats_t stats; +}; + +static void rtp_queue_reset(struct rtp_queue_t *q); +static int rtp_queue_find(struct rtp_queue_t *q, uint16_t seq); +static int rtp_queue_insert(struct rtp_queue_t *q, int position, struct rtp_packet_t *pkt); + +struct rtp_queue_t *rtp_queue_create(int threshold, int frequency, void (*freepkt)(void *, struct rtp_packet_t *), + void *param) +{ + struct rtp_queue_t *q; + q = (struct rtp_queue_t *)calloc(1, sizeof(*q)); + if (!q) + return NULL; + + rtp_queue_reset(q); + q->probation = 1; + q->threshold = threshold; + q->frequency = frequency; + q->free = freepkt; + q->param = param; + return q; +} + +int rtp_queue_destroy(struct rtp_queue_t *q) +{ + rtp_queue_reset(q); + + if (q->items) { + assert(q->capacity > 0); + free(q->items); + q->items = 0; + } + free(q); + return 0; +} + +static inline void rtp_queue_reset_bad_items(struct rtp_queue_t *q) +{ + int i; + struct rtp_packet_t *pkt; + + for (i = 0; i < q->bad_count; i++) { + pkt = q->bad_items[i].pkt; + q->free(q->param, pkt); + } + + q->bad_seq = 0; + q->bad_count = 0; +} + +static void rtp_queue_reset(struct rtp_queue_t *q) +{ + int i; + struct rtp_packet_t *pkt; + + rtp_queue_reset_bad_items(q); + + for (i = 0; i < q->size; i++) { + pkt = q->items[(q->pos + i) % q->capacity].pkt; + q->free(q->param, pkt); + } + + q->pos = 0; + q->size = 0; + q->probation = RTP_SEQUENTIAL; +} + +static int rtp_queue_find(struct rtp_queue_t *q, uint16_t seq) +{ + uint16_t v; + uint16_t vi; + int l, r, i; + + l = q->pos; + r = q->pos + q->size; + v = q->last_seq - seq; + while (l < r) { + i = (l + r) / 2; + vi = (uint16_t)q->last_seq - (uint16_t)q->items[i % q->capacity].pkt->rtp.seq; + if (vi == v) { + return -1; // duplicate + } else if (vi < v) { + r = i; + } else { + assert(vi > v); + l = i + 1; + } + } + + return l; // insert position +} + +static int rtp_queue_insert(struct rtp_queue_t *q, int position, struct rtp_packet_t *pkt) +{ + void *p; + int i, capacity; + + assert(position >= q->pos && position <= q->pos + q->size); + + if (q->size >= q->capacity) { + if (q->size + 1 > MAX_PACKET) + return -E2BIG; + + capacity = q->capacity + 250; + p = realloc(q->items, capacity * sizeof(struct rtp_item_t)); + if (NULL == p) + return -ENOMEM; + + q->items = (struct rtp_item_t *)p; + if (q->pos + q->size > q->capacity) { + // move to tail + assert(q->pos < q->capacity); + memmove(&q->items[q->pos + capacity - q->capacity], &q->items[q->pos], + (q->capacity - q->pos) * sizeof(struct rtp_item_t)); + q->pos += capacity - q->capacity; + position += capacity - q->capacity; + } + + q->capacity = capacity; + } + + // move items + for (i = q->pos + q->size; i > position; i--) + memcpy(&q->items[i % q->capacity], &q->items[(i - 1) % q->capacity], sizeof(struct rtp_item_t)); + + q->items[position % q->capacity].pkt = pkt; + // q->items[position % q->capacity].clock = 0; + q->size++; + return 1; +} + +/* + first last + ^ ^ +---too late---|------------------|----max drop---|-----another sequential--- +--------------|------queue-------|--------------------------------------------> +*/ +int rtp_queue_write(struct rtp_queue_t *q, struct rtp_packet_t *pkt) +{ + int i, idx; + uint16_t delta; + + q->stats.total++; + if (q->probation) { + if (q->size > 0 && (uint16_t)pkt->rtp.seq == q->last_seq + 1) { + if (0 == --q->probation) + q->first_seq = (uint16_t)q->items[q->pos].pkt->rtp.seq; + } else if (q->size == 0 && q->probation == 1) { + // init + q->first_seq = (uint16_t)pkt->rtp.seq; + --q->probation; + } else { + rtp_queue_reset(q); + } + + q->last_seq = (uint16_t)pkt->rtp.seq; + return rtp_queue_insert(q, q->pos + q->size, pkt); + } else { + delta = (uint16_t)(pkt->rtp.seq - q->last_seq); + if (delta > 0 && delta < RTP_DROPOUT) { + if (pkt->rtp.seq < q->last_seq) + q->cycles += RTP_SEQMOD; + + rtp_queue_reset_bad_items(q); + q->last_seq = (uint16_t)pkt->rtp.seq; + return rtp_queue_insert(q, q->pos + q->size, pkt); + } else if ((int16_t)delta <= 0 && (int16_t)delta >= (int16_t)(q->first_seq - q->last_seq)) { + // pkt->rtp.seq - q->first_seq < q->last_seq - q->first_seq + + // duplicate or reordered packet + idx = rtp_queue_find(q, (uint16_t)pkt->rtp.seq); + if (-1 == idx) { + ++q->stats.duplicate; + return -1; + } + + ++q->stats.reorder; + rtp_queue_reset_bad_items(q); + return rtp_queue_insert(q, idx, pkt); + } else if ((uint16_t)(q->first_seq - pkt->rtp.seq) < RTP_MISORDER) { + // too late: pkt->req.seq < q->first_seq + ++q->stats.late; + return -1; + } else { + if (q->bad_count > 0 && q->bad_seq == pkt->rtp.seq) { + if (q->bad_count >= RTP_SEQUENTIAL) { + // Two sequential packets -- assume that the other side + // restarted without telling us so just re-sync + // (i.e., pretend this was the first packet). + + // rtp_queue_reset(q); + + // copy saved items + for (i = 0; i < q->bad_count; i++) + rtp_queue_insert(q, q->pos + q->size, q->bad_items[i].pkt); + + q->bad_count = 0; + q->last_seq = (uint16_t)pkt->rtp.seq; + return rtp_queue_insert(q, q->pos + q->size, pkt); + } + } else { + q->stats.bad++; + rtp_queue_reset_bad_items(q); + } + + q->bad_seq = (pkt->rtp.seq + 1) % (RTP_SEQMOD - 1); + q->bad_items[q->bad_count++].pkt = pkt; + return 1; + } + } + + // for safety + assert(0); + return -1; +} + +struct rtp_packet_t *rtp_queue_read(struct rtp_queue_t *q) +{ + uint32_t threshold; + struct rtp_packet_t *pkt; + if (q->size < 1 || q->probation) + return NULL; + + assert(q->pos < q->capacity); + pkt = q->items[q->pos].pkt; + if (q->first_seq == pkt->rtp.seq) { + q->first_seq++; + q->size--; + q->pos = (q->pos + 1) % q->capacity; + return pkt; + } else { + threshold = (q->items[(q->pos + q->size - 1) % q->capacity].pkt->rtp.timestamp - pkt->rtp.timestamp); + threshold = + (int32_t)threshold < 0 ? (uint32_t)(-(int32_t)threshold) : threshold; // fix h.264 b-frames pts order + threshold = (uint32_t)(((uint64_t)threshold) * 1000 / (uint64_t)q->frequency); + if (threshold < (uint32_t)q->threshold && q->size + 5 < MIN(RTP_DROPOUT, MAX_PACKET)) + return NULL; + + q->stats.lost += pkt->rtp.seq - q->first_seq; + q->first_seq = (uint16_t)(pkt->rtp.seq + 1); + q->size--; + q->pos = (q->pos + 1) % q->capacity; + return pkt; + } +} + +void rtp_queue_stats(struct rtp_queue_t *q, struct rtp_queue_stats_t *stats) +{ + memcpy(stats, &q->stats, sizeof(*stats)); +} + +#if defined(_DEBUG) || defined(DEBUG) +#include +static void rtp_queue_dump(struct rtp_queue_t *q) +{ + int i; + printf("[%02d/%02d]: ", q->pos, q->size); + for (i = 0; i < q->size; i++) { + printf("%u\t", (unsigned int)q->items[(i + q->pos) % q->capacity].pkt->rtp.seq); + } + printf("\n"); +} + +static void rtp_packet_free(void *param, struct rtp_packet_t *pkt) +{ + free(pkt); + (void)param; +} + +static int rtp_queue_packet(rtp_queue_t *q, uint16_t seq) +{ + struct rtp_packet_t *pkt; + pkt = (struct rtp_packet_t *)malloc(sizeof(*pkt)); + if (pkt) { + memset(pkt, 0, sizeof(*pkt)); + pkt->rtp.seq = seq; + if (0 == rtp_queue_write(q, pkt)) + free(pkt); + } + return 0; +} + +static void rtp_queue_test2(void) +{ + int i; + uint16_t seq; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + static uint16_t s_seq[1000]; + + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) + s_seq[i] = (uint16_t)(45000 + i); + + // 45460, 45461, 45462, 45464, 45465, 45466, ..., + // 45490, 45491, 45492, 45503, 45504, 45505, 45463, + // 45506, 45507, 45493, 45494, 45495, 45496, 45497, + // 45498, 45499, 45500, 45501, 45502, 45508, 45509, ... + memmove(s_seq + 463, s_seq + 464, sizeof(s_seq[0]) * (509 - 464)); // lost 45463 + s_seq[492] = 45503; + s_seq[493] = 45504; + s_seq[494] = 45505; + s_seq[495] = 45463; + s_seq[496] = 45506; + s_seq[497] = 45507; + s_seq[498] = 45493; + s_seq[499] = 45494; + s_seq[500] = 45495; + s_seq[501] = 45496; + s_seq[502] = 45497; + s_seq[503] = 45498; + s_seq[504] = 45499; + s_seq[505] = 45500; + s_seq[506] = 45501; + s_seq[507] = 45502; + s_seq[508] = 45508; + + seq = s_seq[0]; + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + pkt = rtp_queue_read(q); + if (pkt) { + // printf("%u ", pkt->rtp.seq); + assert(0 == pkt->rtp.seq - seq++); + free(pkt); + } + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.reorder == 11 && q->stats.lost == 0 && + q->stats.bad == 0 && q->stats.duplicate == 0 && q->stats.late == 0); + rtp_queue_destroy(q); +} + +static void rtp_queue_test3(void) +{ + int i; + uint16_t seq; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + static uint16_t s_seq[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 23, 24, 25, 26, 27, 28, 29, 30, 46, 47, 48, 49, 50}; + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + seq = s_seq[0]; + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + pkt = rtp_queue_read(q); + if (pkt) { + // printf("%u ", pkt->rtp.seq); + assert(0 == pkt->rtp.seq - seq++); + free(pkt); + } + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.reorder == 8 && q->stats.lost == 0 && + q->stats.bad == 0 && q->stats.duplicate == 0 && q->stats.late == 0); + rtp_queue_destroy(q); +} + +static void rtp_queue_test4(void) +{ + int i; + uint16_t seq; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + // first packet + static uint16_t s_seq[] = {1, 17, 18, 19, 20, 21, 22, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50}; + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + seq = s_seq[0]; + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + pkt = rtp_queue_read(q); + if (pkt) { + // printf("%u ", pkt->rtp.seq); + assert(0 == pkt->rtp.seq - seq++); + free(pkt); + } + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.reorder == 15 && q->stats.lost == 0 && + q->stats.bad == 0 && q->stats.duplicate == 0 && q->stats.late == 0); + rtp_queue_destroy(q); +} + +void rtp_queue_test(void) +{ + int i; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + static uint16_t s_seq[] = { + 836, 837, 859, 860, 822, 823, 824, 825, 826, 822, 830, 827, 831, 828, 829, 830, + 832, 833, 834, 6000, 840, 841, 842, 843, 835, 836, 837, 838, 838, 844, 859, 811, + }; + + rtp_queue_test2(); + rtp_queue_test3(); + rtp_queue_test4(); + + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + rtp_queue_dump(q); + pkt = rtp_queue_read(q); + if (pkt) + free(pkt); + rtp_queue_dump(q); + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.lost == 0 && q->stats.bad == 1 && + q->stats.duplicate == 1 && q->stats.late == 20 && q->stats.reorder == 6); + rtp_queue_destroy(q); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtp-ssrc.c b/src/tuya_p2p/lib_rtp/src/rtp-ssrc.c new file mode 100755 index 000000000..48c78ff97 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-ssrc.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#if defined(OS_WINDOWS) || defined(_WIN32) || defined(_WIN64) +#include +#include + +uint32_t rtp_ssrc(void) +{ + uint32_t seed; + HCRYPTPROV provider; + + seed = (uint32_t)rand(); + if (CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { + CryptGenRandom(provider, sizeof(seed), (PBYTE)&seed); + CryptReleaseContext(provider, 0); + } + return seed; +} + +#elif defined(OS_LINUX) || defined(OS_MAC) +#include +#include +#include + +static int read_random(uint32_t *dst, const char *file) +{ + int fd = open(file, O_RDONLY); + int err = -1; + if (fd == -1) + return -1; + err = (int)read(fd, dst, sizeof(*dst)); + close(fd); + return err; +} +uint32_t rtp_ssrc(void) +{ + uint32_t seed; + if (read_random(&seed, "/dev/urandom") == sizeof(seed)) + return seed; + if (read_random(&seed, "/dev/random") == sizeof(seed)) + return seed; + return (uint32_t)rand(); +} +#else + +uint32_t rtp_ssrc(void) +{ + return rand(); +} + +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtp-time.c b/src/tuya_p2p/lib_rtp/src/rtp-time.c new file mode 100755 index 000000000..b8d9616b8 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-time.c @@ -0,0 +1,88 @@ +#include +#include +#include + +#if defined(OS_WINDOWS) +#include +#else +#include + +#if defined(OS_MAC) +#include +#include +#include +#endif + +#endif + +/// same as system_time except ms -> us +/// @return microseconds since the Epoch(1970-01-01 00:00:00 +0000 (UTC)) +uint64_t rtpclock() +{ +#if defined(OS_WINDOWS) + uint64_t t; + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + t = (uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime; + return t / 10 - 11644473600000000; /* Jan 1, 1601 */ +#elif defined(OS_MAC) + uint64_t tick; + mach_timebase_info_data_t timebase; + tick = mach_absolute_time(); + mach_timebase_info(&timebase); + return tick * timebase.numer / timebase.denom / 1000; +#else + // POSIX.1-2008 marks gettimeofday() as obsolete, recommending the use of clock_gettime(2) instead. + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec; +#endif +} + +/// us(microsecond) -> ntp +uint64_t clock2ntp(uint64_t clock) +{ + uint64_t ntp; + + // high 32 bits in seconds + ntp = ((clock / 1000000) + 0x83AA7E80) << 32; // 1/1/1970 -> 1/1/1900 + + // low 32 bits in picosecond + // us * 2^32 / 10^6 + // 10^6 = 2^6 * 15625 + // => us * 2^26 / 15625 + ntp |= (uint32_t)(((clock % 1000000) << 26) / 15625); + + return ntp; +} + +/// ntp -> us(microsecond) +uint64_t ntp2clock(uint64_t ntp) +{ + uint64_t clock; + + // high 32 bits in seconds + clock = ((uint64_t)((unsigned int)(ntp >> 32) - 0x83AA7E80)) * 1000000; // 1/1/1900 -> 1/1/1970 + + // low 32 bits in picosecond + clock += ((ntp & 0xFFFFFFFF) * 15625) >> 26; + + return clock; +} + +#if defined(_DEBUG) || defined(DEBUG) +void rtp_time_test(void) +{ + const uint64_t ntp = 0xe2e1d897e9c38b05ULL; + uint64_t clock; + struct tm *tm; + time_t t; + + clock = ntp2clock(ntp); + t = (time_t)(clock / 1000000); + tm = gmtime(&t); + assert(tm->tm_year + 1900 == 2020 && tm->tm_mon == 7 && tm->tm_mday == 15 && tm->tm_hour == 3 && tm->tm_min == 44 && + tm->tm_sec == 23); + assert(clock2ntp(clock) == 0xe2e1d897e9c38b04ULL); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtp.c b/src/tuya_p2p/lib_rtp/src/rtp.c new file mode 100755 index 000000000..00c3a0721 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp.c @@ -0,0 +1,180 @@ +#include "rtp-param.h" +#include "rtp-internal.h" +#include "rtp-packet.h" + +enum { + RTP_SENDER = 1, /// send RTP packet + RTP_RECEIVER = 2, /// receive RTP packet +}; + +double rtcp_interval(int members, int senders, double rtcp_bw, int we_sent, double avg_rtcp_size, int initial); + +void *rtp_create(struct rtp_event_t *handler, void *param, uint32_t ssrc, uint32_t timestamp, int frequence, + int bandwidth, int sender) +{ + struct rtp_context *ctx; + + ctx = (struct rtp_context *)calloc(1, sizeof(*ctx)); + if (!ctx) + return NULL; + + ctx->self = rtp_member_create(ssrc); + ctx->members = rtp_member_list_create(); + ctx->senders = rtp_member_list_create(); + if (!ctx->self || !ctx->members || !ctx->senders) { + rtp_destroy(ctx); + return NULL; + } + + ctx->self->rtp_clock = rtpclock(); + ctx->self->rtp_timestamp = timestamp; + rtp_member_list_add(ctx->members, ctx->self); + + memcpy(&ctx->handler, handler, sizeof(ctx->handler)); + ctx->cbparam = param; + ctx->rtcp_bw = (int)(bandwidth * RTCP_BANDWIDTH_FRACTION); + ctx->avg_rtcp_size = 0; + ctx->frequence = frequence; + ctx->role = sender ? RTP_SENDER : RTP_RECEIVER; + ctx->init = 1; + return ctx; +} + +int rtp_destroy(void *rtp) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + + if (ctx->members) + rtp_member_list_destroy(ctx->members); + if (ctx->senders) + rtp_member_list_destroy(ctx->senders); + if (ctx->self) + rtp_member_release(ctx->self); + free(ctx); + return 0; +} + +int rtp_onsend(void *rtp, const void *data, int bytes) +{ + // time64_t ntp; + struct rtp_packet_t pkt; + struct rtp_context *ctx = (struct rtp_context *)rtp; + + assert(RTP_SENDER == ctx->role); + ctx->role = RTP_SENDER; + // don't need add self to sender list + // rtp_member_list_add(ctx->senders, ctx->self); + + if (0 != rtp_packet_deserialize(&pkt, data, bytes)) + return -1; // packet error + + ctx->self->rtp_clock = rtpclock(); + ctx->self->rtp_timestamp = pkt.rtp.timestamp; // RTP timestamp + ctx->self->rtp_bytes += pkt.payloadlen; + ctx->self->rtp_packets += 1; + return 0; +} + +int rtp_onreceived(void *rtp, const void *data, int bytes) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_input_rtp(ctx, data, bytes); +} + +int rtp_onreceived_rtcp(void *rtp, const void *rtcp, int bytes) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_input_rtcp(ctx, rtcp, bytes); +} + +int rtp_rtcp_report(void *rtp, void *data, int bytes) +{ + int n; + struct rtp_context *ctx = (struct rtp_context *)rtp; + +//#pragma message("update we_sent flag") + // don't send packet in 2T + // ctx->role = RTP_RECEIVER + + if (RTP_SENDER == ctx->role) { + // send RTP in 2T + n = rtcp_sr_pack(ctx, (uint8_t *)data, bytes); + } else { + assert(RTP_RECEIVER == ctx->role); + n = rtcp_rr_pack(ctx, (uint8_t *)data, bytes); + } + + // compound RTCP Packet + if (n < bytes) { + n += rtcp_sdes_pack(ctx, (uint8_t *)data + n, bytes - n); + } + + ctx->init = 0; + return n; +} + +int rtp_rtcp_bye(void *rtp, void *data, int bytes) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_bye_pack(ctx, (uint8_t *)data, bytes); +} + +int rtp_rtcp_app(void *rtp, void *data, int bytes, const char name[4], const void *app, int len) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_app_pack(ctx, (uint8_t *)data, bytes, name, app, len); +} + +int rtp_rtcp_rtpfb(void *rtp, void *data, int bytes, enum rtcp_rtpfb_type_t id, const rtcp_rtpfb_t *rtpfb) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_rtpfb_pack(ctx, (uint8_t *)data, bytes, id, rtpfb); +} + +int rtp_rtcp_psfb(void *rtp, void *data, int bytes, enum rtcp_psfb_type_t id, const rtcp_psfb_t *psfb) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_psfb_pack(ctx, (uint8_t *)data, bytes, id, psfb); +} + +int rtp_rtcp_xr(void *rtp, void *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_xr_pack(ctx, (uint8_t *)data, bytes, id, xr); +} + +int rtp_rtcp_interval(void *rtp) +{ + double interval; + struct rtp_context *ctx = (struct rtp_context *)rtp; + interval = rtcp_interval(rtp_member_list_count(ctx->members), + rtp_member_list_count(ctx->senders) + ((RTP_SENDER == ctx->role) ? 1 : 0), ctx->rtcp_bw, + (ctx->self->rtp_clock + 2 * RTCP_REPORT_INTERVAL * 1000 > rtpclock()) ? 1 : 0, + ctx->avg_rtcp_size, ctx->init); + + return (int)(interval * 1000); +} + +const char *rtp_get_cname(void *rtp, uint32_t ssrc) +{ + struct rtp_member *member; + struct rtp_context *ctx = (struct rtp_context *)rtp; + member = rtp_member_list_find(ctx->members, ssrc); + return member ? (char *)member->sdes[RTCP_SDES_CNAME].data : NULL; +} + +const char *rtp_get_name(void *rtp, uint32_t ssrc) +{ + struct rtp_member *member; + struct rtp_context *ctx = (struct rtp_context *)rtp; + member = rtp_member_list_find(ctx->members, ssrc); + return member ? (char *)member->sdes[RTCP_SDES_NAME].data : NULL; +} + +int rtp_set_info(void *rtp, const char *cname, const char *name) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + rtp_member_setvalue(ctx->self, RTCP_SDES_CNAME, (const uint8_t *)cname, (int)strlen(cname)); + rtp_member_setvalue(ctx->self, RTCP_SDES_NAME, (const uint8_t *)name, (int)strlen(name)); + return 0; +} diff --git a/src/tuya_p2p/pjproject/CMakeLists.txt b/src/tuya_p2p/pjproject/CMakeLists.txt new file mode 100755 index 000000000..b1ae5071e --- /dev/null +++ b/src/tuya_p2p/pjproject/CMakeLists.txt @@ -0,0 +1,68 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/pjlib/include + ${MODULE_PATH}/pjlib-util/include + ${MODULE_PATH}/pjmedia/include + ${MODULE_PATH}/pjnath/include) + +# set(NIMBLE nimble) +file(GLOB_RECURSE PJPROJECT_SRCS + "${MODULE_PATH}/pjlib/src/*.c" + "${MODULE_PATH}/pjlib-util/src/*.c" + "${MODULE_PATH}/pjmedia/src/*.c" + "${MODULE_PATH}/pjnath/src/*.c") +list(REMOVE_ITEM PJPROJECT_SRCS "${MODULE_PATH}/pjlib/src/pj/os_rwmutex.c" "${MODULE_PATH}/pjlib/src/pj/ioqueue_common_abs.c") +list(REMOVE_ITEM PJPROJECT_SRCS "${MODULE_PATH}/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c" "${MODULE_PATH}/pjlib-util/src/pjlib-util/scanner_cis_uint.c") +message("PJPROJECT_SRCS: ${PJPROJECT_SRCS}") +list(APPEND LIB_SRCS ${PJPROJECT_SRCS}) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h new file mode 100755 index 000000000..367d8361f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_H__ +#define __PJLIB_UTIL_H__ + +/** + * @file pjlib-util.h + * @brief pjlib-util.h + */ + +/* Base */ +#include +#include + +/* Getopt */ +#include + +/* Crypto */ +#include +#include +#include +#include +#include +#include + +/* DNS and resolver */ +#include +#include +#include + +/* Simple DNS server */ +#include + +/* Text scanner and utilities */ +#include +#include + +/* XML */ +#include + +/* JSON */ +#include + +/* Old STUN */ +#include + +/* PCAP */ +#include + +/* HTTP */ +#include + +/** CLI **/ +#include +#include +#include + +#endif /* __PJLIB_UTIL_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h new file mode 100755 index 000000000..8c56eb8c9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_BASE64_H__ +#define __PJLIB_UTIL_BASE64_H__ + +/** + * @file base64.h + * @brief Base64 encoding and decoding + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_BASE64 Base64 Encoding/Decoding + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * This module implements base64 encoding and decoding. + */ + +/** + * Helper macro to calculate the approximate length required for base256 to + * base64 conversion. + */ +#define PJ_BASE256_TO_BASE64_LEN(len) (len * 4 / 3 + 3) + +/** + * Helper macro to calculate the approximage length required for base64 to + * base256 conversion. + */ +#define PJ_BASE64_TO_BASE256_LEN(len) (len * 3 / 4) + +/** + * Encode a buffer into base64 encoding. + * + * @param input The input buffer. + * @param in_len Size of the input buffer. + * @param output Output buffer. Caller must allocate this buffer with + * the appropriate size. + * @param out_len On entry, it specifies the length of the output buffer. + * Upon return, this will be filled with the actual + * length of the output buffer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_base64_encode(const pj_uint8_t *input, int in_len, char *output, int *out_len); + +/** + * Decode base64 string. + * + * @param input Input string. + * @param out Buffer to store the output. Caller must allocate + * this buffer with the appropriate size. + * @param out_len On entry, it specifies the length of the output buffer. + * Upon return, this will be filled with the actual + * length of the output. + */ +PJ_DECL(pj_status_t) pj_base64_decode(const pj_str_t *input, pj_uint8_t *out, int *out_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_BASE64_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h new file mode 100755 index 000000000..704723d96 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_H__ +#define __PJLIB_UTIL_CLI_H__ + +/** + * @file cli.h + * @brief Command Line Interface + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_CLI Command Line Interface Framework + * @{ + * A CLI framework features an interface for defining command specification, + * parsing, and executing a command. + * It also features an interface to communicate with various front-ends, + * such as console, telnet. + * Application normally needs only one CLI instance to be created. + * On special cases, application could also create multiple CLI + * instances, with each instance has specific command structure. + * +\verbatim +| vid help Show this help screen | +| vid enable|disable Enable or disable video in next offer/answer | +| vid call add Add video stream for current call | +| vid call cap N ID Set capture dev ID for stream #N in current call | +| disable_codec g711|g722 Show this help screen | + + + + + + + + + + + + + + + + + + +\endverbatim + */ + +/** + * This opaque structure represents a CLI application. A CLI application is + * the root placeholder of other CLI objects. In an application, one (and + * normally only one) CLI application instance needs to be created. + */ +typedef struct pj_cli_t pj_cli_t; + +/** + * Type of command id. + */ +typedef int pj_cli_cmd_id; + +/** + * This describes the parameters to be specified when creating a CLI + * application with pj_cli_create(). Application MUST initialize this + * structure by calling pj_cli_cfg_default() before using it. + */ +typedef struct pj_cli_cfg { + /** + * The application name, which will be used in places such as logs. + * This field is mandatory. + */ + pj_str_t name; + + /** + * Optional application title, which will be used in places such as + * window title. If not specified, the application name will be used + * as the title. + */ + pj_str_t title; + + /** + * The pool factory where all memory allocations will be taken from. + * This field is mandatory. + */ + pj_pool_factory *pf; + +} pj_cli_cfg; + +/** + * Type of argument id. + */ +typedef int pj_cli_arg_id; + +/** + * Forward declaration of pj_cli_cmd_spec structure. + */ +typedef struct pj_cli_cmd_spec pj_cli_cmd_spec; + +/** + * Forward declaration for pj_cli_sess, which will be declared in cli_imp.h. + */ +typedef struct pj_cli_sess pj_cli_sess; + +/** + * Forward declaration for CLI front-end. + */ +typedef struct pj_cli_front_end pj_cli_front_end; + +/** + * Forward declaration for CLI argument spec structure. + */ +typedef struct pj_cli_arg_spec pj_cli_arg_spec; + +/** + * This structure contains the command to be executed by command handler. + */ +typedef struct pj_cli_cmd_val { + /** The session on which the command was executed on. */ + pj_cli_sess *sess; + + /** The command specification being executed. */ + const pj_cli_cmd_spec *cmd; + + /** Number of argvs. */ + int argc; + + /** Array of args, with argv[0] specifies the name of the cmd. */ + pj_str_t argv[PJ_CLI_MAX_ARGS]; + +} pj_cli_cmd_val; + +/** + * This structure contains the hints information for the end user. + * This structure could contain either command or argument information. + * The front-end will format the information and present it to the user. + */ +typedef struct pj_cli_hint_info { + /** + * The hint value. + */ + pj_str_t name; + + /** + * The hint type. + */ + pj_str_t type; + + /** + * Helpful description of the hint value. + */ + pj_str_t desc; + +} pj_cli_hint_info; + +/** + * This structure contains extra information returned by pj_cli_sess_exec()/ + * pj_cli_sess_parse(). + * Upon return from the function, various other fields in this structure will + * be set by the function. + */ +typedef struct pj_cli_exec_info { + /** + * If command parsing failed, on return this will point to the location + * where the failure occurs, otherwise the value will be set to -1. + */ + int err_pos; + + /** + * If a command matching the command in the cmdline was found, on return + * this will be set to the command id of the command, otherwise it will be + * set to PJ_CLI_INVALID_CMD_ID. + */ + pj_cli_cmd_id cmd_id; + + /** + * If a command was executed, on return this will be set to the return + * value of the command, otherwise it will contain PJ_SUCCESS. + */ + pj_status_t cmd_ret; + + /** + * The number of hint elements + **/ + unsigned hint_cnt; + + /** + * If pj_cli_sess_parse() fails because of a missing argument or ambigous + * command/argument, the function returned PJ_CLI_EMISSINGARG or + * PJ_CLI_EAMBIGUOUS error. + * This field will contain the hint information. This is useful to give + * helpful information to the end_user. + */ + pj_cli_hint_info hint[PJ_CLI_MAX_HINTS]; + +} pj_cli_exec_info; + +/** + * This structure contains the information returned from the dynamic + * argument callback. + */ +typedef struct pj_cli_arg_choice_val { + /** + * The argument choice value + */ + pj_str_t value; + + /** + * Helpful description of the choice value. This text will be used when + * displaying the help texts for the choice value + */ + pj_str_t desc; + +} pj_cli_arg_choice_val; + +/** + * This structure contains the parameters for pj_cli_get_dyn_choice + */ +typedef struct pj_cli_dyn_choice_param { + /** + * The session on which the command was executed on. + */ + pj_cli_sess *sess; + + /** + * The command being processed. + */ + pj_cli_cmd_spec *cmd; + + /** + * The argument id. + */ + pj_cli_arg_id arg_id; + + /** + * The maximum number of values that the choice can hold. + */ + unsigned max_cnt; + + /** + * The pool to allocate memory from. + */ + pj_pool_t *pool; + + /** + * The choice values count. + */ + unsigned cnt; + + /** + * Array containing the valid choice values. + */ + pj_cli_arg_choice_val choice[PJ_CLI_MAX_CHOICE_VAL]; +} pj_cli_dyn_choice_param; + +/** + * This specifies the callback type for argument handlers, which will be + * called to get the valid values of the choice type arguments. + */ +typedef void (*pj_cli_get_dyn_choice)(pj_cli_dyn_choice_param *param); + +/** + * This specifies the callback type for command handlers, which will be + * executed when the specified command is invoked. + * + * @param cval The command that is specified by the user. + * + * @return Return the status of the command execution. + */ +typedef pj_status_t (*pj_cli_cmd_handler)(pj_cli_cmd_val *cval); + +/** + * Write a log message to the CLI application. The CLI application + * will send the log message to all the registered front-ends. + * + * @param cli The CLI application instance. + * @param level Verbosity level of this message message. + * @param buffer The message itself. + * @param len Length of this message. + */ +PJ_DECL(void) pj_cli_write_log(pj_cli_t *cli, int level, const char *buffer, int len); + +/** + * Create a new CLI application instance. + * + * @param cfg CLI application creation parameters. + * @param p_cli Pointer to receive the returned instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, pj_cli_t **p_cli); + +/** + * This specifies the function to get the id of the specified command + * + * @param cmd The specified command. + * + * @return The command id + */ +PJ_DECL(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd); + +/** + * Get the internal parameter of the CLI instance. + * + * @param cli The CLI application instance. + * + * @return CLI parameter instance. + */ +PJ_DECL(pj_cli_cfg *) pj_cli_get_param(pj_cli_t *cli); + +/** + * Call this to signal application shutdown. Typically application would + * call this from it's "Quit" menu or similar command to quit the + * application. + * + * See also pj_cli_sess_end_session() to end a session instead of quitting the + * whole application. + * + * @param cli The CLI application instance. + * @param req The session on which the shutdown request is + * received. + * @param restart Indicate whether application restart is wanted. + */ +PJ_DECL(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, pj_bool_t restart); +/** + * Check if application shutdown or restart has been requested. + * + * @param cli The CLI application instance. + * + * @return PJ_TRUE if pj_cli_quit() has been called. + */ +PJ_DECL(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli); + +/** + * Check if application restart has been requested. + * + * @param cli The CLI application instance. + * + * @return PJ_TRUE if pj_cli_quit() has been called with + * restart parameter set. + */ +PJ_DECL(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli); + +/** + * Destroy a CLI application instance. This would also close all sessions + * currently running for this CLI application. + * + * @param cli The CLI application. + */ +PJ_DECL(void) pj_cli_destroy(pj_cli_t *cli); + +/** + * Initialize a pj_cli_cfg with its default values. + * + * @param param The instance to be initialized. + */ +PJ_DECL(void) pj_cli_cfg_default(pj_cli_cfg *param); + +/** + * Register a front end to the CLI application. + * + * @param cli The CLI application. + * @param fe The CLI front end to be registered. + */ +PJ_DECL(void) pj_cli_register_front_end(pj_cli_t *cli, pj_cli_front_end *fe); + +/** + * Create a new complete command specification from an XML node text and + * register it to the CLI application. + * + * Note that the input string MUST be NULL terminated. + * + * @param cli The CLI application. + * @param group Optional group to which this command will be added + * to, or specify NULL if this command is a root + * command. + * @param xml Input string containing XML node text for the + * command, MUST be NULL terminated. + * @param handler Function handler for the command. This must be NULL + * if the command specifies a command group. + * @param p_cmd Optional pointer to store the newly created + * specification. + * @param get_choice Function handler for the argument. Specify this for + * dynamic choice type arguments. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_cli_add_cmd_from_xml(pj_cli_t *cli, pj_cli_cmd_spec *group, const pj_str_t *xml, pj_cli_cmd_handler handler, + pj_cli_cmd_spec **p_cmd, pj_cli_get_dyn_choice get_choice); +/** + * Initialize pj_cli_exec_info with its default values. + * + * @param param The param to be initialized. + */ +PJ_DECL(void) pj_cli_exec_info_default(pj_cli_exec_info *param); + +/** + * Write a log message to the specific CLI session. + * + * @param sess The CLI active session. + * @param buffer The message itself. + * @param len Length of this message. + */ +PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess, const char *buffer, pj_size_t len); + +/** + * Parse an input cmdline string. The first word of the command line is the + * command itself, which will be matched against command specifications + * registered in the CLI application. + * + * Zero or more arguments follow the command name. Arguments are separated by + * one or more whitespaces. Argument may be placed inside a pair of quotes, + * double quotes, '{' and '}', or '[' and ']' pairs. This is useful when the + * argument itself contains whitespaces or other restricted characters. If + * the quote character itself is to appear in the argument, the argument then + * must be quoted with different quote characters. There is no character + * escaping facility provided by this function (such as the use of backslash + * '\' character). + * + * The cmdline may be followed by an extra newline (LF or CR-LF characters), + * which will be removed by the function. However any more characters + * following this newline will cause an error to be returned. + * + * @param sess The CLI session. + * @param cmdline The command line string to be parsed. + * @param val Structure to store the parsing result. + * @param pool The pool to allocate memory from. + * @param info Additional info to be returned regarding the parsing. + * + * @return This function returns the status of the parsing, + * which can be one of the following : + * - PJ_SUCCESS: a command was executed successfully. + * - PJ_EINVAL: invalid parameter to this function. + * - PJ_ENOTFOUND: command is not found. + * - PJ_CLI_EAMBIGUOUS: command/argument is ambiguous. + * - PJ_CLI_EMISSINGARG: missing argument. + * - PJ_CLI_EINVARG: invalid command argument. + * - PJ_CLI_EEXIT: "exit" has been called to end + * the current session. This is a signal for the + * application to end it's main loop. + */ +PJ_DECL(pj_status_t) +pj_cli_sess_parse(pj_cli_sess *sess, char *cmdline, pj_cli_cmd_val *val, pj_pool_t *pool, pj_cli_exec_info *info); + +/** + * End the specified session, and destroy it to release all resources used + * by the session. + * + * See also pj_cli_sess and pj_cli_front_end for more info regarding the + * creation process. + * See also pj_cli_quit() to quit the whole application instead. + * + * @param sess The CLI session to be destroyed. + */ +PJ_DECL(void) pj_cli_sess_end_session(pj_cli_sess *sess); + +/** + * Execute a command line. This function will parse the input string to find + * the appropriate command and verify whether the string matches the command + * specifications. If matches, the command will be executed, and the return + * value of the command will be set in the \a cmd_ret field of the \a info + * argument, if specified. + * + * See also pj_cli_sess_parse() for more info regarding the cmdline format. + * + * @param sess The CLI session. + * @param cmdline The command line string to be executed. + * @param pool The pool to allocate memory from. + * @param info Optional pointer to receive additional information + * related to the execution of the command (such as + * the command return value). + * + * @return This function returns the status of the command + * parsing and execution (note that the return value + * of the handler itself will be returned in \a info + * argument, if specified). Please see the return value + * of pj_cli_sess_parse() for possible return values. + */ +PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, char *cmdline, pj_pool_t *pool, pj_cli_exec_info *info); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h new file mode 100755 index 000000000..d81040171 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_CONSOLE_H__ +#define __PJLIB_UTIL_CLI_CONSOLE_H__ + +/** + * @file cli_console.h + * @brief Command Line Interface Console Front End API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @ingroup PJLIB_UTIL_CLI_IMP + * @{ + * + */ + +/** + * This structure contains various options for CLI console front-end. + * Application must call pj_cli_console_cfg_default() to initialize + * this structure with its default values. + */ +typedef struct pj_cli_console_cfg { + /** + * Default log verbosity level for the session. + * + * Default value: PJ_CLI_CONSOLE_LOG_LEVEL + */ + int log_level; + + /** + * Specify text message as a prompt string to user. + * + * Default: empty + */ + pj_str_t prompt_str; + + /** + * Specify the command to quit the application. + * + * Default: empty + */ + pj_str_t quit_command; + +} pj_cli_console_cfg; + +/** + * Initialize pj_cli_console_cfg with its default values. + * + * @param param The structure to be initialized. + */ +PJ_DECL(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param); + +/** + * Create a console front-end for the specified CLI application, and return + * the only session instance for the console front end. On Windows operating + * system, this may open a new console window. + * + * @param cli The CLI application instance. + * @param param Optional console CLI parameters. If this value is + * NULL, default parameters will be used. + * @param p_sess Pointer to receive the session instance for the + * console front end. + * @param p_fe Optional pointer to receive the front-end instance + * of the console front-end just created. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_cli_console_create(pj_cli_t *cli, const pj_cli_console_cfg *param, pj_cli_sess **p_sess, pj_cli_front_end **p_fe); + +/** + * Retrieve a cmdline from console stdin and process the input accordingly. + * + * @param sess The CLI session. + * @param buf Pointer to receive the buffer. + * @param maxlen Maximum length to read. + * + * @return PJ_SUCCESS if an input was read + */ +PJ_DECL(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, char *buf, unsigned maxlen); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_CONSOLE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h new file mode 100755 index 000000000..408af3a95 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_IMP_H__ +#define __PJLIB_UTIL_CLI_IMP_H__ + +/** + * @file cli_imp.h + * @brief Command Line Interface Implementor's API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_CLI_IMP Command Line Interface Implementor's API + * @ingroup PJLIB_UTIL_CLI + * @{ + * + */ + +/** + * Default log level for console sessions. + */ +#ifndef PJ_CLI_CONSOLE_LOG_LEVEL +#define PJ_CLI_CONSOLE_LOG_LEVEL PJ_LOG_MAX_LEVEL +#endif + +/** + * Default log level for telnet sessions. + */ +#ifndef PJ_CLI_TELNET_LOG_LEVEL +#define PJ_CLI_TELNET_LOG_LEVEL 4 +#endif + +/** + * Default port number for telnet daemon. + */ +#ifndef PJ_CLI_TELNET_PORT +#define PJ_CLI_TELNET_PORT 0 +#endif + +/** + * This enumeration specifies front end types. + */ +typedef enum pj_cli_front_end_type { + PJ_CLI_CONSOLE_FRONT_END, /**< Console front end. */ + PJ_CLI_TELNET_FRONT_END, /**< Telnet front end. */ + PJ_CLI_HTTP_FRONT_END, /**< HTTP front end. */ + PJ_CLI_GUI_FRONT_END /**< GUI front end. */ +} pj_cli_front_end_type; + +/** + * Front end operations. Only the CLI should call these functions + * directly. + */ +typedef struct pj_cli_front_end_op { + /** + * Callback to write a log message to the appropriate sessions belonging + * to this front end. The front end would only send the log message to + * the session if the session's log verbosity level is greater than the + * level of this log message. + * + * @param fe The front end. + * @param level Verbosity level of this message message. + * @param data The message itself. + * @param len Length of this message. + */ + void (*on_write_log)(pj_cli_front_end *fe, int level, const char *data, pj_size_t len); + + /** + * Callback to be called when the application is quitting, to signal the + * front-end to end its main loop or any currently blocking functions, + * if any. + * + * @param fe The front end. + * @param req The session which requested the application quit. + */ + void (*on_quit)(pj_cli_front_end *fe, pj_cli_sess *req); + + /** + * Callback to be called to close and self destroy the front-end. This + * must also close any active sessions created by this front-ends. + * + * @param fe The front end. + */ + void (*on_destroy)(pj_cli_front_end *fe); + +} pj_cli_front_end_op; + +/** + * This structure describes common properties of CLI front-ends. A front- + * end is a mean to interact with end user, for example the CLI application + * may interact with console, telnet, web, or even a GUI. + * + * Each end user's interaction will create an instance of pj_cli_sess. + * + * Application instantiates the front end by calling the appropriate + * function to instantiate them. + */ +struct pj_cli_front_end { + /** + * Linked list members + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_front_end); + + /** + * Front end type. + */ + pj_cli_front_end_type type; + + /** + * The CLI application. + */ + pj_cli_t *cli; + + /** + * Front end operations. + */ + pj_cli_front_end_op *op; +}; + +/** + * Session operations. + */ +typedef struct pj_cli_sess_op { + /** + * Callback to be called to close and self destroy the session. + * + * @param sess The session to destroy. + */ + void (*destroy)(pj_cli_sess *sess); + +} pj_cli_sess_op; + +/** + * This structure describes common properties of a CLI session. A CLI session + * is the interaction of an end user to the CLI application via a specific + * renderer, where the renderer can be console, telnet, web, or a GUI app for + * mobile. A session is created by its renderer, and it's creation procedures + * vary among renderer (for example, a telnet session is created when the + * end user connects to the application, while a console session is always + * instantiated once the program is run). + */ +struct pj_cli_sess { + /** + * Linked list members + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_sess); + + /** + * Pointer to the front-end instance which created this session. + */ + pj_cli_front_end *fe; + + /** + * Session operations. + */ + pj_cli_sess_op *op; + + /** + * Text containing session info, which is filled by the renderer when + * the session is created. + */ + pj_str_t info; + + /** + * Log verbosity of this session. + */ + int log_level; +}; + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_IMP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h new file mode 100755 index 000000000..e68f455e7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_TELNET_H__ +#define __PJLIB_UTIL_CLI_TELNET_H__ + +/** + * @file cli_telnet.h + * @brief Command Line Interface Telnet Front End API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @ingroup PJLIB_UTIL_CLI_IMP + * @{ + * + */ + +/** + * This structure contains the information about the telnet. + * Application will get updated information each time the telnet is started/ + * restarted. + */ +typedef struct pj_cli_telnet_info { + /** + * The telnet's ip address. + */ + pj_str_t ip_address; + + /** + * The telnet's port number. + */ + pj_uint16_t port; + + /** Internal buffer for IP address */ + char buf_[32]; + +} pj_cli_telnet_info; + +/** + * This specifies the callback called when telnet is started + * + * @param status The status of telnet startup process. + * + */ +typedef void (*pj_cli_telnet_on_started)(pj_status_t status); + +/** + * This structure contains various options to instantiate the telnet daemon. + * Application must call pj_cli_telnet_cfg_default() to initialize + * this structure with its default values. + */ +typedef struct pj_cli_telnet_cfg { + /** + * Listening port number. The value may be 0 to let the system choose + * the first available port. + * + * Default value: PJ_CLI_TELNET_PORT + */ + pj_uint16_t port; + + /** + * Ioqueue instance to be used. If this field is NULL, an internal + * ioqueue and worker thread will be created. + */ + pj_ioqueue_t *ioqueue; + + /** + * Default log verbosity level for the session. + * + * Default value: PJ_CLI_TELNET_LOG_LEVEL + */ + int log_level; + + /** + * Specify a password to be asked to the end user to access the + * application. Currently this is not implemented yet. + * + * Default: empty (no password) + */ + pj_str_t passwd; + + /** + * Specify text message to be displayed to newly connected users. + * Currently this is not implemented yet. + * + * Default: empty + */ + pj_str_t welcome_msg; + + /** + * Specify text message as a prompt string to user. + * + * Default: empty + */ + pj_str_t prompt_str; + + /** + * Specify the pj_cli_telnet_on_started callback. + * + * Default: empty + */ + pj_cli_telnet_on_started on_started; + +} pj_cli_telnet_cfg; + +/** + * Initialize pj_cli_telnet_cfg with its default values. + * + * @param param The structure to be initialized. + */ +PJ_DECL(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param); + +/** + * Create, initialize, and start a telnet daemon for the application. + * + * @param cli The CLI application instance. + * @param param Optional parameters for creating the telnet daemon. + * If this value is NULL, default parameters will be used. + * @param p_fe Optional pointer to receive the front-end instance + * of the telnet front-end just created. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli, pj_cli_telnet_cfg *param, pj_cli_front_end **p_fe); + +/** + * Retrieve cli telnet info. + * + * @param fe The front end. + * @param info The telnet runtime information. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_cli_telnet_get_info(pj_cli_front_end *fe, pj_cli_telnet_info *info); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_TELNET_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h new file mode 100755 index 000000000..6852b0895 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CONFIG_H__ +#define __PJLIB_UTIL_CONFIG_H__ + +/** + * @file config.h + * @brief Compile time settings + */ + +/** + * @defgroup PJLIB_UTIL_CONFIG Configuration + * @ingroup PJLIB_UTIL_BASE + * @{ + */ + +/* ************************************************************************** + * DNS CONFIGURATION + */ + +/** + * Maximum number of IP addresses in DNS A response. + */ +#ifndef PJ_DNS_MAX_IP_IN_A_REC +#define PJ_DNS_MAX_IP_IN_A_REC 8 +#endif + +/** + * Maximum server address entries per one SRV record + */ +#ifndef PJ_DNS_SRV_MAX_ADDR +#define PJ_DNS_SRV_MAX_ADDR 8 +#endif + +/** + * This constant specifies the maximum names to keep in the temporary name + * table when performing name compression scheme when duplicating DNS packet + * (the #pj_dns_packet_dup() function). + * + * Generally name compression is desired, since it saves some memory (see + * PJ_DNS_RESOLVER_RES_BUF_SIZE setting). However it comes at the expense of + * a little processing overhead to perform name scanning and also a little + * bit more stack usage (8 bytes per entry on 32bit platform). + * + * Default: 16 + */ +#ifndef PJ_DNS_MAX_NAMES_IN_NAMETABLE +#define PJ_DNS_MAX_NAMES_IN_NAMETABLE 16 +#endif + +/* ************************************************************************** + * RESOLVER CONFIGURATION + */ + +/** + * Maximum numbers of DNS nameservers that can be configured in resolver. + */ +#ifndef PJ_DNS_RESOLVER_MAX_NS +#define PJ_DNS_RESOLVER_MAX_NS 16 +#endif + +/** + * Default retransmission delay, in miliseconds. The combination of + * retransmission delay and count determines the query timeout. + * + * Default: 2000 (2 seconds, according to RFC 1035) + */ +#ifndef PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY +#define PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY 2000 +#endif + +/** + * Maximum number of transmissions before timeout is declared for + * the query. + * + * Default: 5 + */ +#ifndef PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT +#define PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT 5 +#endif + +/** + * Maximum life-time of DNS response in the resolver response cache, + * in seconds. If the value is zero, then DNS response caching will be + * disabled. + * + * Default is 300 seconds (5 minutes). + * + * @see PJ_DNS_RESOLVER_INVALID_TTL + */ +#ifndef PJ_DNS_RESOLVER_MAX_TTL +#define PJ_DNS_RESOLVER_MAX_TTL (5 * 60) +#endif + +/** + * The life-time of invalid DNS response in the resolver response cache. + * An invalid DNS response is a response which RCODE is non-zero and + * response without any answer section. These responses can be put in + * the cache too to minimize message round-trip. + * + * Default: 60 (one minute). + * + * @see PJ_DNS_RESOLVER_MAX_TTL + */ +#ifndef PJ_DNS_RESOLVER_INVALID_TTL +#define PJ_DNS_RESOLVER_INVALID_TTL 60 +#endif + +/** + * The interval on which nameservers which are known to be good to be + * probed again to determine whether they are still good. Note that + * this applies to both active nameserver (the one currently being used) + * and idle nameservers (good nameservers that are not currently selected). + * The probing to query the "goodness" of nameservers involves sending + * the same query to multiple servers, so it's probably not a good idea + * to send this probing too often. + * + * Default: 600 (ten minutes) + * + * @see PJ_DNS_RESOLVER_BAD_NS_TTL + */ +#ifndef PJ_DNS_RESOLVER_GOOD_NS_TTL +#define PJ_DNS_RESOLVER_GOOD_NS_TTL (10 * 60) +#endif + +/** + * The interval on which nameservers which known to be bad to be probed + * again to determine whether it is still bad. + * + * Default: 60 (one minute) + * + * @see PJ_DNS_RESOLVER_GOOD_NS_TTL + */ +#ifndef PJ_DNS_RESOLVER_BAD_NS_TTL +#define PJ_DNS_RESOLVER_BAD_NS_TTL (1 * 60) +#endif + +/** + * Maximum size of UDP packet. RFC 1035 states that maximum size of + * DNS packet carried over UDP is 512 bytes. + * + * Default: 512 byes + */ +#ifndef PJ_DNS_RESOLVER_MAX_UDP_SIZE +#define PJ_DNS_RESOLVER_MAX_UDP_SIZE 512 +#endif + +/** + * Size of memory pool allocated for each individual DNS response cache. + * This value here should be more or less the same as maximum UDP packet + * size (PJ_DNS_RESOLVER_MAX_UDP_SIZE), since the DNS replicator function + * (#pj_dns_packet_dup()) is also capable of performing name compressions. + * + * Default: 512 + */ +#ifndef PJ_DNS_RESOLVER_RES_BUF_SIZE +#define PJ_DNS_RESOLVER_RES_BUF_SIZE 512 +#endif + +/** + * Size of temporary pool buffer for parsing DNS packets in resolver. + * + * default: 4000 + */ +#ifndef PJ_DNS_RESOLVER_TMP_BUF_SIZE +#define PJ_DNS_RESOLVER_TMP_BUF_SIZE 4000 +#endif + +/* ************************************************************************** + * SCANNER CONFIGURATION + */ + +/** + * Macro PJ_SCANNER_USE_BITWISE is defined and non-zero (by default yes) + * will enable the use of bitwise for character input specification (cis). + * This would save several kilobytes of .bss memory in the SIP parser. + */ +#ifndef PJ_SCANNER_USE_BITWISE +#define PJ_SCANNER_USE_BITWISE 1 +#endif + +/* ************************************************************************** + * STUN CLIENT CONFIGURATION + */ + +/** + * Maximum number of attributes in the STUN packet (for the old STUN + * library). + * + * Default: 16 + */ +#ifndef PJSTUN_MAX_ATTR +#define PJSTUN_MAX_ATTR 16 +#endif + +/** + * Maximum number of attributes in the STUN packet (for the new STUN + * library). + * + * Default: 16 + */ +#ifndef PJ_STUN_MAX_ATTR +#define PJ_STUN_MAX_ATTR 16 +#endif + +/* ************************************************************************** + * ENCRYPTION + */ + +/** + * Specifies whether CRC32 algorithm should use the table based lookup table + * for faster calculation, at the expense of about 1KB table size on the + * executable. If zero, the CRC32 will use non-table based which is more than + * an order of magnitude slower. + * + * Default: 1 + */ +#ifndef PJ_CRC32_HAS_TABLES +#define PJ_CRC32_HAS_TABLES 1 +#endif + +/* ************************************************************************** + * HTTP Client configuration + */ +/** + * Timeout value for HTTP request operation. The value is in ms. + * Default: 60000ms + */ +#ifndef PJ_HTTP_DEFAULT_TIMEOUT +#define PJ_HTTP_DEFAULT_TIMEOUT (60000) +#endif + +/* ************************************************************************** + * CLI configuration + */ + +/** + * Initial pool size for CLI. + * Default: 1024 bytes + */ +#ifndef PJ_CLI_POOL_SIZE +#define PJ_CLI_POOL_SIZE 1024 +#endif + +/** + * Pool increment size for CLI. + * Default: 512 bytes + */ +#ifndef PJ_CLI_POOL_INC +#define PJ_CLI_POOL_INC 512 +#endif + +/** + * Maximum length of command buffer. + * Default: 512 + */ +#ifndef PJ_CLI_MAX_CMDBUF +#define PJ_CLI_MAX_CMDBUF 512 +#endif + +/** + * Maximum command arguments. + * Default: 8 + */ +#ifndef PJ_CLI_MAX_ARGS +#define PJ_CLI_MAX_ARGS 8 +#endif + +/** + * Maximum number of hints. + * Default: 32 + */ +#ifndef PJ_CLI_MAX_HINTS +#define PJ_CLI_MAX_HINTS 32 +#endif + +/** + * Maximum short name version (shortcuts) for a command. + * Default: 4 + */ +#ifndef PJ_CLI_MAX_SHORTCUTS +#define PJ_CLI_MAX_SHORTCUTS 4 +#endif + +/** + * Initial pool size for console CLI. + * Default: 256 bytes + */ +#ifndef PJ_CLI_CONSOLE_POOL_SIZE +#define PJ_CLI_CONSOLE_POOL_SIZE 256 +#endif + +/** + * Pool increment size for console CLI. + * Default: 256 bytes + */ +#ifndef PJ_CLI_CONSOLE_POOL_INC +#define PJ_CLI_CONSOLE_POOL_INC 256 +#endif + +/** + * Initial pool size for telnet CLI. + * Default: 1024 bytes + */ +#ifndef PJ_CLI_TELNET_POOL_SIZE +#define PJ_CLI_TELNET_POOL_SIZE 1024 +#endif + +/** + * Pool increment size for telnet CLI. + * Default: 512 bytes + */ +#ifndef PJ_CLI_TELNET_POOL_INC +#define PJ_CLI_TELNET_POOL_INC 512 +#endif + +/** + * Maximum number of argument values of choice type. + * Default: 64 + */ +#ifndef PJ_CLI_MAX_CHOICE_VAL +#define PJ_CLI_MAX_CHOICE_VAL 64 +#endif + +/** + * Maximum number of command history. + * Default: 16 + */ +#ifndef PJ_CLI_MAX_CMD_HISTORY +#define PJ_CLI_MAX_CMD_HISTORY 16 +#endif + +/** + * @} + */ + +#endif /* __PJLIB_UTIL_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h new file mode 100755 index 000000000..669892c32 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CRC32_H__ +#define __PJLIB_UTIL_CRC32_H__ + +/** + * @file crc32.h + * @brief CRC32 implementation + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_CRC32 CRC32 (Cyclic Redundancy Check) + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * This implements CRC32 algorithm. See ITU-T V.42 for the formal + * specification. + */ + +/** CRC32 context. */ +typedef struct pj_crc32_context { + pj_uint32_t crc_state; /**< Current state. */ +} pj_crc32_context; + +/** + * Initialize CRC32 context. + * + * @param ctx CRC32 context. + */ +PJ_DECL(void) pj_crc32_init(pj_crc32_context *ctx); + +/** + * Feed data incrementally to the CRC32 algorithm. + * + * @param ctx CRC32 context. + * @param data Input data. + * @param nbytes Length of the input data. + * + * @return The current CRC32 value. + */ +PJ_DECL(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, const pj_uint8_t *data, pj_size_t nbytes); + +/** + * Finalize CRC32 calculation and retrieve the CRC32 value. + * + * @param ctx CRC32 context. + * + * @return The current CRC value. + */ +PJ_DECL(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx); + +/** + * Perform one-off CRC32 calculation to the specified data. + * + * @param data Input data. + * @param nbytes Length of input data. + * + * @return CRC value of the data. + */ +PJ_DECL(pj_uint32_t) pj_crc32_calc(const pj_uint8_t *data, pj_size_t nbytes); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CRC32_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h new file mode 100755 index 000000000..5dc37f808 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_DNS_H__ +#define __PJLIB_UTIL_DNS_H__ + +/** + * @file dns.h + * @brief Low level DNS message parsing and packetization. + */ +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS DNS and Asynchronous DNS Resolver + * @ingroup PJ_PROTOCOLS + */ + +/** + * @defgroup PJ_DNS_PARSING Low-level DNS Message Parsing and Packetization + * @ingroup PJ_DNS + * @{ + * + * This module provides low-level services to parse and packetize DNS queries + * and responses. The functions support building a DNS query packet and parse + * the data in the DNS response. This implementation conforms to the + * following specifications: + * - RFC 1035: DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION + * - RFC 1886: DNS Extensions to support IP version 6 + * + * To create a DNS query packet, application should call #pj_dns_make_query() + * function, specifying the desired DNS query type, the name to be resolved, + * and the buffer where the DNS packet will be built into. + * + * When incoming DNS query or response packet arrives, application can use + * #pj_dns_parse_packet() to parse the TCP/UDP payload into parsed DNS packet + * structure. + * + * This module does not provide any networking functionalities to send or + * receive DNS packets. This functionality should be provided by higher layer + * modules such as @ref PJ_DNS_RESOLVER. + */ + +enum { + PJ_DNS_CLASS_IN = 1 /**< DNS class IN. */ +}; + +/** + * This enumeration describes standard DNS record types as described by + * RFC 1035, RFC 2782, and others. + */ +typedef enum pj_dns_type { + PJ_DNS_TYPE_A = 1, /**< Host address (A) record. */ + PJ_DNS_TYPE_NS = 2, /**< Authoritative name server (NS) */ + PJ_DNS_TYPE_MD = 3, /**< Mail destination (MD) record. */ + PJ_DNS_TYPE_MF = 4, /**< Mail forwarder (MF) record. */ + PJ_DNS_TYPE_CNAME = 5, /**< Canonical name (CNAME) record. */ + PJ_DNS_TYPE_SOA = 6, /**< Marks start of zone authority. */ + PJ_DNS_TYPE_MB = 7, /**< Mailbox domain name (MB). */ + PJ_DNS_TYPE_MG = 8, /**< Mail group member (MG). */ + PJ_DNS_TYPE_MR = 9, /**< Mail rename domain name. */ + PJ_DNS_TYPE_NULL = 10, /**< NULL RR. */ + PJ_DNS_TYPE_WKS = 11, /**< Well known service description */ + PJ_DNS_TYPE_PTR = 12, /**< Domain name pointer. */ + PJ_DNS_TYPE_HINFO = 13, /**< Host information. */ + PJ_DNS_TYPE_MINFO = 14, /**< Mailbox or mail list information. */ + PJ_DNS_TYPE_MX = 15, /**< Mail exchange record. */ + PJ_DNS_TYPE_TXT = 16, /**< Text string. */ + PJ_DNS_TYPE_RP = 17, /**< Responsible person. */ + PJ_DNS_TYPE_AFSB = 18, /**< AFS cell database. */ + PJ_DNS_TYPE_X25 = 19, /**< X.25 calling address. */ + PJ_DNS_TYPE_ISDN = 20, /**< ISDN calling address. */ + PJ_DNS_TYPE_RT = 21, /**< Router. */ + PJ_DNS_TYPE_NSAP = 22, /**< NSAP address. */ + PJ_DNS_TYPE_NSAP_PTR = 23, /**< NSAP reverse address. */ + PJ_DNS_TYPE_SIG = 24, /**< Signature. */ + PJ_DNS_TYPE_KEY = 25, /**< Key. */ + PJ_DNS_TYPE_PX = 26, /**< X.400 mail mapping. */ + PJ_DNS_TYPE_GPOS = 27, /**< Geographical position (withdrawn) */ + PJ_DNS_TYPE_AAAA = 28, /**< IPv6 address. */ + PJ_DNS_TYPE_LOC = 29, /**< Location. */ + PJ_DNS_TYPE_NXT = 30, /**< Next valid name in the zone. */ + PJ_DNS_TYPE_EID = 31, /**< Endpoint idenfitier. */ + PJ_DNS_TYPE_NIMLOC = 32, /**< Nimrod locator. */ + PJ_DNS_TYPE_SRV = 33, /**< Server selection (SRV) record. */ + PJ_DNS_TYPE_ATMA = 34, /**< DNS ATM address record. */ + PJ_DNS_TYPE_NAPTR = 35, /**< DNS Naming authority pointer record. */ + PJ_DNS_TYPE_KX = 36, /**< DNS key exchange record. */ + PJ_DNS_TYPE_CERT = 37, /**< DNS certificate record. */ + PJ_DNS_TYPE_A6 = 38, /**< DNS IPv6 address (experimental) */ + PJ_DNS_TYPE_DNAME = 39, /**< DNS non-terminal name redirection rec. */ + + PJ_DNS_TYPE_OPT = 41, /**< DNS options - contains EDNS metadata. */ + PJ_DNS_TYPE_APL = 42, /**< DNS Address Prefix List (APL) record. */ + PJ_DNS_TYPE_DS = 43, /**< DNS Delegation Signer (DS) */ + PJ_DNS_TYPE_SSHFP = 44, /**< DNS SSH Key Fingerprint */ + PJ_DNS_TYPE_IPSECKEY = 45, /**< DNS IPSEC Key. */ + PJ_DNS_TYPE_RRSIG = 46, /**< DNS Resource Record signature. */ + PJ_DNS_TYPE_NSEC = 47, /**< DNS Next Secure Name. */ + PJ_DNS_TYPE_DNSKEY = 48 /**< DNSSEC Key. */ +} pj_dns_type; + +/** + * Standard DNS header, according to RFC 1035, which will be present in + * both DNS query and DNS response. + * + * Note that all values seen by application would be in + * host by order. The library would convert them to network + * byte order as necessary. + */ +typedef struct pj_dns_hdr { + pj_uint16_t id; /**< Transaction ID. */ + pj_uint16_t flags; /**< Flags. */ + pj_uint16_t qdcount; /**< Nb. of queries. */ + pj_uint16_t anscount; /**< Nb. of res records */ + pj_uint16_t nscount; /**< Nb. of NS records. */ + pj_uint16_t arcount; /**< Nb. of additional records */ +} pj_dns_hdr; + +/** Create RCODE flag */ +#define PJ_DNS_SET_RCODE(c) ((pj_uint16_t)((c)&0x0F)) + +/** Create RA (Recursion Available) bit */ +#define PJ_DNS_SET_RA(on) ((pj_uint16_t)((on) << 7)) + +/** Create RD (Recursion Desired) bit */ +#define PJ_DNS_SET_RD(on) ((pj_uint16_t)((on) << 8)) + +/** Create TC (Truncated) bit */ +#define PJ_DNS_SET_TC(on) ((pj_uint16_t)((on) << 9)) + +/** Create AA (Authoritative Answer) bit */ +#define PJ_DNS_SET_AA(on) ((pj_uint16_t)((on) << 10)) + +/** Create four bits opcode */ +#define PJ_DNS_SET_OPCODE(o) ((pj_uint16_t)((o) << 11)) + +/** Create query/response bit */ +#define PJ_DNS_SET_QR(on) ((pj_uint16_t)((on) << 15)) + +/** Get RCODE value */ +#define PJ_DNS_GET_RCODE(val) (((val)&PJ_DNS_SET_RCODE(0x0F)) >> 0) + +/** Get RA bit */ +#define PJ_DNS_GET_RA(val) (((val)&PJ_DNS_SET_RA(1)) >> 7) + +/** Get RD bit */ +#define PJ_DNS_GET_RD(val) (((val)&PJ_DNS_SET_RD(1)) >> 8) + +/** Get TC bit */ +#define PJ_DNS_GET_TC(val) (((val)&PJ_DNS_SET_TC(1)) >> 9) + +/** Get AA bit */ +#define PJ_DNS_GET_AA(val) (((val)&PJ_DNS_SET_AA(1)) >> 10) + +/** Get OPCODE value */ +#define PJ_DNS_GET_OPCODE(val) (((val)&PJ_DNS_SET_OPCODE(0x0F)) >> 11) + +/** Get QR bit */ +#define PJ_DNS_GET_QR(val) (((val)&PJ_DNS_SET_QR(1)) >> 15) + +/** + * These constants describe DNS RCODEs. Application can fold these constants + * into PJLIB pj_status_t namespace by calling #PJ_STATUS_FROM_DNS_RCODE() + * macro. + */ +typedef enum pj_dns_rcode { + PJ_DNS_RCODE_FORMERR = 1, /**< Format error. */ + PJ_DNS_RCODE_SERVFAIL = 2, /**< Server failure. */ + PJ_DNS_RCODE_NXDOMAIN = 3, /**< Name Error. */ + PJ_DNS_RCODE_NOTIMPL = 4, /**< Not Implemented. */ + PJ_DNS_RCODE_REFUSED = 5, /**< Refused. */ + PJ_DNS_RCODE_YXDOMAIN = 6, /**< The name exists. */ + PJ_DNS_RCODE_YXRRSET = 7, /**< The RRset (name, type) exists. */ + PJ_DNS_RCODE_NXRRSET = 8, /**< The RRset (name, type) doesn't exist*/ + PJ_DNS_RCODE_NOTAUTH = 9, /**< Not authorized. */ + PJ_DNS_RCODE_NOTZONE = 10 /**< The zone specified is not a zone. */ + +} pj_dns_rcode; + +/** + * This structure describes a DNS query record. + */ +typedef struct pj_dns_parsed_query { + pj_str_t name; /**< The domain in the query. */ + pj_uint16_t type; /**< Type of the query (pj_dns_type) */ + pj_uint16_t dnsclass; /**< Network class (PJ_DNS_CLASS_IN=1) */ +} pj_dns_parsed_query; + +/** + * This structure describes a Resource Record parsed from the DNS packet. + * All integral values are in host byte order. + */ +typedef struct pj_dns_parsed_rr { + pj_str_t name; /**< The domain name which this rec pertains. */ + pj_uint16_t type; /**< RR type code. */ + pj_uint16_t dnsclass; /**< Class of data (PJ_DNS_CLASS_IN=1). */ + pj_uint32_t ttl; /**< Time to live. */ + pj_uint16_t rdlength; /**< Resource data length. */ + void *data; /**< Pointer to the raw resource data, only + when the type is not known. If it is known, + the data will be put in rdata below. */ + + /** For resource types that are recognized/supported by this library, + * the parsed resource data will be placed in this rdata union. + */ + union rdata { + /** SRV Resource Data (PJ_DNS_TYPE_SRV, 33) */ + struct srv { + pj_uint16_t prio; /**< Target priority (lower is higher). */ + pj_uint16_t weight; /**< Weight/proportion */ + pj_uint16_t port; /**< Port number of the service */ + pj_str_t target; /**< Target name. */ + } srv; /**< SRV Resource Data (PJ_DNS_TYPE_SRV, 33) */ + + /** CNAME Resource Data (PJ_DNS_TYPE_CNAME, 5) */ + struct cname { + pj_str_t name; /**< Primary canonical name for an alias. */ + } cname; /**< CNAME Resource Data (PJ_DNS_TYPE_CNAME, 5) */ + + /** NS Resource Data (PJ_DNS_TYPE_NS, 2) */ + struct ns { + pj_str_t name; /**< Primary name server. */ + } ns; /**< NS Resource Data (PJ_DNS_TYPE_NS, 2) */ + + /** PTR Resource Data (PJ_DNS_TYPE_PTR, 12) */ + struct ptr { + pj_str_t name; /**< PTR name. */ + } ptr; /**< PTR Resource Data (PJ_DNS_TYPE_PTR, 12) */ + + /** A Resource Data (PJ_DNS_TYPE_A, 1) */ + struct a { + pj_in_addr ip_addr; /**< IPv4 address in network byte order. */ + } a; /**< A Resource Data (PJ_DNS_TYPE_A, 1) */ + + /** AAAA Resource Data (PJ_DNS_TYPE_AAAA, 28) */ + struct aaaa { + pj_in6_addr ip_addr; /**< IPv6 address in network byte order. */ + } aaaa; /**< AAAA Resource Data (PJ_DNS_TYPE_AAAA, 28) */ + + } rdata; + +} pj_dns_parsed_rr; + +/** + * This structure describes the parsed repersentation of the raw DNS packet. + * Note that all integral values in the parsed packet are represented in + * host byte order. + */ +typedef struct pj_dns_parsed_packet { + pj_dns_hdr hdr; /**< Pointer to DNS hdr, in host byte order */ + pj_dns_parsed_query *q; /**< Array of DNS queries. */ + pj_dns_parsed_rr *ans; /**< Array of DNS RR answer. */ + pj_dns_parsed_rr *ns; /**< Array of NS record in the answer. */ + pj_dns_parsed_rr *arr; /**< Array of additional RR answer. */ +} pj_dns_parsed_packet; + +/** + * Option flags to be specified when calling #pj_dns_packet_dup() function. + * These flags can be combined with bitwise OR operation. + */ +enum pj_dns_dup_options { + PJ_DNS_NO_QD = 1, /**< Do not duplicate the query section. */ + PJ_DNS_NO_ANS = 2, /**< Do not duplicate the answer section. */ + PJ_DNS_NO_NS = 4, /**< Do not duplicate the NS section. */ + PJ_DNS_NO_AR = 8 /**< Do not duplicate the additional rec section */ +}; + +/** + * Create DNS query packet to resolve the specified names. This function + * can be used to build any types of DNS query, such as A record or DNS SRV + * record. + * + * Application specifies the type of record and the name to be queried, + * and the function will build the DNS query packet into the buffer + * specified. Once the packet is successfully built, application can send + * the packet via TCP or UDP connection. + * + * @param packet The buffer to put the DNS query packet. + * @param size On input, it specifies the size of the buffer. + * On output, it will be filled with the actual size of + * the DNS query packet. + * @param id DNS query ID to associate DNS response with the + * query. + * @param qtype DNS type of record to be queried (see #pj_dns_type). + * @param name Name to be queried from the DNS server. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_make_query(void *packet, unsigned *size, pj_uint16_t id, int qtype, const pj_str_t *name); + +/** + * Parse raw DNS packet into parsed DNS packet structure. This function is + * able to parse few DNS resource records such as A record, PTR record, + * CNAME record, NS record, and SRV record. + * + * @param pool Pool to allocate memory for the parsed packet. + * @param packet Pointer to the DNS packet (the TCP/UDP payload of + * the raw packet). + * @param size The size of the DNS packet. + * @param p_res Pointer to store the resulting parsed packet. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_dns_parse_packet(pj_pool_t *pool, const void *packet, unsigned size, pj_dns_parsed_packet **p_res); + +/** + * Duplicate DNS packet. + * + * @param pool The pool to allocate memory for the duplicated packet. + * @param p The DNS packet to be cloned. + * @param options Option flags, from pj_dns_dup_options. + * @param p_dst Pointer to store the cloned DNS packet. + */ +PJ_DECL(void) +pj_dns_packet_dup(pj_pool_t *pool, const pj_dns_parsed_packet *p, unsigned options, pj_dns_parsed_packet **p_dst); + +/** + * Utility function to get the type name string of the specified DNS type. + * + * @param type DNS type (see #pj_dns_type). + * + * @return String name of the type (e.g. "A", "SRV", etc.). + */ +PJ_DECL(const char *) pj_dns_get_type_name(int type); + +/** + * Initialize DNS record as DNS SRV record. + * + * @param rec The DNS resource record to be initialized as DNS + * SRV record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param prio DNS SRV priority. + * @param weight DNS SRV weight. + * @param port Target port. + * @param target Target name. + */ +PJ_DECL(void) +pj_dns_init_srv_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, unsigned prio, + unsigned weight, unsigned port, const pj_str_t *target); + +/** + * Initialize DNS record as DNS CNAME record. + * + * @param rec The DNS resource record to be initialized as DNS + * CNAME record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param name Host name. + */ +PJ_DECL(void) +pj_dns_init_cname_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_str_t *name); + +/** + * Initialize DNS record as DNS A record. + * + * @param rec The DNS resource record to be initialized as DNS + * A record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param ip_addr Host address. + */ +PJ_DECL(void) +pj_dns_init_a_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in_addr *ip_addr); + +/** + * Initialize DNS record as DNS AAAA record. + * + * @param rec The DNS resource record to be initialized as DNS + * AAAA record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param ip_addr Host address. + */ +PJ_DECL(void) +pj_dns_init_aaaa_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in6_addr *ip_addr); + +/** + * Dump DNS packet to standard log. + * + * @param res The DNS packet. + */ +PJ_DECL(void) pj_dns_dump_packet(const pj_dns_parsed_packet *res); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_DNS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h new file mode 100755 index 000000000..aa0e58f6f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_DNS_SERVER_H__ +#define __PJLIB_UTIL_DNS_SERVER_H__ + +/** + * @file dns_server.h + * @brief Simple DNS server + */ +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS_SERVER Simple DNS Server + * @ingroup PJ_DNS + * @{ + * This contains a simple but fully working DNS server implementation, + * mostly for testing purposes. It supports serving various DNS resource + * records such as SRV, CNAME, A, and AAAA. + */ + +/** + * Opaque structure to hold DNS server instance. + */ +typedef struct pj_dns_server pj_dns_server; + +/** + * Create the DNS server instance. The instance will run immediately. + * + * @param pf The pool factory to create memory pools. + * @param ioqueue Ioqueue instance where the server socket will be + * registered to. + * @param af Address family of the server socket (valid values + * are pj_AF_INET() for IPv4 and pj_AF_INET6() for IPv6). + * @param port The UDP port to listen. + * @param flags Flags, currently must be zero. + * @param p_srv Pointer to receive the DNS server instance. + * + * @return PJ_SUCCESS if server has been created successfully, + * otherwise the function will return the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pj_dns_server_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, int af, unsigned port, unsigned flags, + pj_dns_server **p_srv); + +/** + * Destroy DNS server instance. + * + * @param srv The DNS server instance. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_server_destroy(pj_dns_server *srv); + +/** + * Add generic resource record entries to the server. + * + * @param srv The DNS server instance. + * @param count Number of records to be added. + * @param rr Array of records to be added. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_server_add_rec(pj_dns_server *srv, unsigned count, const pj_dns_parsed_rr rr[]); + +/** + * Remove the specified record from the server. + * + * @param srv The DNS server instance. + * @param dns_class The resource's DNS class. Valid value is PJ_DNS_CLASS_IN. + * @param type The resource type. + * @param name The resource name to be removed. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_server_del_rec(pj_dns_server *srv, int dns_class, pj_dns_type type, const pj_str_t *name); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_DNS_SERVER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h new file mode 100755 index 000000000..dd2da37b1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_ERRNO_H__ +#define __PJLIB_UTIL_ERRNO_H__ + +#include + +/** + * @defgroup PJLIB_UTIL_ERROR Error Codes + * @ingroup PJLIB_UTIL_BASE + * @{ + */ + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + * This value is 320000. + */ +#define PJLIB_UTIL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 3) + +/************************************************************ + * STUN ERROR + ***********************************************************/ +/** + * @hideinitializer + * Unable to resolve STUN server + */ +#define PJLIB_UTIL_ESTUNRESOLVE (PJLIB_UTIL_ERRNO_START + 1) /* 320001 */ +/** + * @hideinitializer + * Unknown STUN message type. + */ +#define PJLIB_UTIL_ESTUNINMSGTYPE (PJLIB_UTIL_ERRNO_START + 2) /* 320002 */ +/** + * @hideinitializer + * Invalid STUN message length + */ +#define PJLIB_UTIL_ESTUNINMSGLEN (PJLIB_UTIL_ERRNO_START + 3) /* 320003 */ +/** + * @hideinitializer + * Invalid STUN attribute length + */ +#define PJLIB_UTIL_ESTUNINATTRLEN (PJLIB_UTIL_ERRNO_START + 4) /* 320004 */ +/** + * @hideinitializer + * Invalid STUN attribute type + */ +#define PJLIB_UTIL_ESTUNINATTRTYPE (PJLIB_UTIL_ERRNO_START + 5) /* 320005 */ +/** + * @hideinitializer + * Invalid STUN server/socket index + */ +#define PJLIB_UTIL_ESTUNININDEX (PJLIB_UTIL_ERRNO_START + 6) /* 320006 */ +/** + * @hideinitializer + * No STUN binding response in the message + */ +#define PJLIB_UTIL_ESTUNNOBINDRES (PJLIB_UTIL_ERRNO_START + 7) /* 320007 */ +/** + * @hideinitializer + * Received STUN error attribute + */ +#define PJLIB_UTIL_ESTUNRECVERRATTR (PJLIB_UTIL_ERRNO_START + 8) /* 320008 */ +/** + * @hideinitializer + * No STUN mapped address attribute + */ +#define PJLIB_UTIL_ESTUNNOMAP (PJLIB_UTIL_ERRNO_START + 9) /* 320009 */ +/** + * @hideinitializer + * Received no response from STUN server + */ +#define PJLIB_UTIL_ESTUNNOTRESPOND (PJLIB_UTIL_ERRNO_START + 10) /* 320010 */ +/** + * @hideinitializer + * Symetric NAT detected by STUN + */ +#define PJLIB_UTIL_ESTUNSYMMETRIC (PJLIB_UTIL_ERRNO_START + 11) /* 320011 */ +/** + * @hideinitializer + * Invalid STUN magic value + */ +#define PJLIB_UTIL_ESTUNNOTMAGIC (PJLIB_UTIL_ERRNO_START + 12) /* 320012 */ +/** + * @hideinitializer + * Invalid STUN fingerprint value + */ +#define PJLIB_UTIL_ESTUNFINGERPRINT (PJLIB_UTIL_ERRNO_START + 13) /* 320013 */ + +/************************************************************ + * XML ERROR + ***********************************************************/ +/** + * @hideinitializer + * General invalid XML message. + */ +#define PJLIB_UTIL_EINXML (PJLIB_UTIL_ERRNO_START + 20) /* 320020 */ + +/************************************************************ + * JSON ERROR + ***********************************************************/ +/** + * @hideinitializer + * General invalid JSON message. + */ +#define PJLIB_UTIL_EINJSON (PJLIB_UTIL_ERRNO_START + 30) /* 320030 */ + +/************************************************************ + * DNS ERROR + ***********************************************************/ +/** + * @hideinitializer + * DNS query packet buffer is too small. + * This error occurs when the user supplied buffer for creating DNS + * query (#pj_dns_make_query() function) is too small. + */ +#define PJLIB_UTIL_EDNSQRYTOOSMALL (PJLIB_UTIL_ERRNO_START + 40) /* 320040 */ +/** + * @hideinitializer + * Invalid DNS packet length. + * This error occurs when the received DNS response packet does not + * match all the fields length. + */ +#define PJLIB_UTIL_EDNSINSIZE (PJLIB_UTIL_ERRNO_START + 41) /* 320041 */ +/** + * @hideinitializer + * Invalid DNS class. + * This error occurs when the received DNS response contains network + * class other than IN (Internet). + */ +#define PJLIB_UTIL_EDNSINCLASS (PJLIB_UTIL_ERRNO_START + 42) /* 320042 */ +/** + * @hideinitializer + * Invalid DNS name pointer. + * This error occurs when parsing the compressed names inside DNS + * response packet, when the name pointer points to an invalid address + * or the parsing has triggerred too much recursion. + */ +#define PJLIB_UTIL_EDNSINNAMEPTR (PJLIB_UTIL_ERRNO_START + 43) /* 320043 */ +/** + * @hideinitializer + * Invalid DNS nameserver address. If hostname was specified for nameserver + * address, this error means that the function was unable to resolve + * the nameserver hostname. + */ +#define PJLIB_UTIL_EDNSINNSADDR (PJLIB_UTIL_ERRNO_START + 44) /* 320044 */ +/** + * @hideinitializer + * No nameserver is in DNS resolver. No nameserver is configured in the + * resolver. + */ +#define PJLIB_UTIL_EDNSNONS (PJLIB_UTIL_ERRNO_START + 45) /* 320045 */ +/** + * @hideinitializer + * No working DNS nameserver. All nameservers have been queried, + * but none was able to serve any DNS requests. These "bad" nameservers + * will be re-tested again for "goodness" after some period. + */ +#define PJLIB_UTIL_EDNSNOWORKINGNS (PJLIB_UTIL_ERRNO_START + 46) /* 320046 */ +/** + * @hideinitializer + * No answer record in the DNS response. + */ +#define PJLIB_UTIL_EDNSNOANSWERREC (PJLIB_UTIL_ERRNO_START + 47) /* 320047 */ +/** + * @hideinitializer + * Invalid DNS answer. This error is raised for example when the DNS + * answer does not have a query section, or the type of RR in the answer + * doesn't match the query. + */ +#define PJLIB_UTIL_EDNSINANSWER (PJLIB_UTIL_ERRNO_START + 48) /* 320048 */ + +/* DNS ERRORS MAPPED FROM RCODE: */ + +/** + * Start of error code mapped from DNS RCODE + */ +#define PJLIB_UTIL_DNS_RCODE_START (PJLIB_UTIL_ERRNO_START + 50) /* 320050 */ + +/** + * Map DNS RCODE status into pj_status_t. + */ +#define PJ_STATUS_FROM_DNS_RCODE(rcode) (rcode == 0 ? PJ_SUCCESS : PJLIB_UTIL_DNS_RCODE_START + rcode) +/** + * @hideinitializer + * Format error - The name server was unable to interpret the query. + * This corresponds to DNS RCODE 1. + */ +#define PJLIB_UTIL_EDNS_FORMERR PJ_STATUS_FROM_DNS_RCODE(1) /* 320051 */ +/** + * @hideinitializer + * Server failure - The name server was unable to process this query due to a + * problem with the name server. + * This corresponds to DNS RCODE 2. + */ +#define PJLIB_UTIL_EDNS_SERVFAIL PJ_STATUS_FROM_DNS_RCODE(2) /* 320052 */ +/** + * @hideinitializer + * Name Error - Meaningful only for responses from an authoritative name + * server, this code signifies that the domain name referenced in the query + * does not exist. + * This corresponds to DNS RCODE 3. + */ +#define PJLIB_UTIL_EDNS_NXDOMAIN PJ_STATUS_FROM_DNS_RCODE(3) /* 320053 */ +/** + * @hideinitializer + * Not Implemented - The name server does not support the requested kind of + * query. + * This corresponds to DNS RCODE 4. + */ +#define PJLIB_UTIL_EDNS_NOTIMPL PJ_STATUS_FROM_DNS_RCODE(4) /* 320054 */ +/** + * @hideinitializer + * Refused - The name server refuses to perform the specified operation for + * policy reasons. + * This corresponds to DNS RCODE 5. + */ +#define PJLIB_UTIL_EDNS_REFUSED PJ_STATUS_FROM_DNS_RCODE(5) /* 320055 */ +/** + * @hideinitializer + * The name exists. + * This corresponds to DNS RCODE 6. + */ +#define PJLIB_UTIL_EDNS_YXDOMAIN PJ_STATUS_FROM_DNS_RCODE(6) /* 320056 */ +/** + * @hideinitializer + * The RRset (name, type) exists. + * This corresponds to DNS RCODE 7. + */ +#define PJLIB_UTIL_EDNS_YXRRSET PJ_STATUS_FROM_DNS_RCODE(7) /* 320057 */ +/** + * @hideinitializer + * The RRset (name, type) does not exist. + * This corresponds to DNS RCODE 8. + */ +#define PJLIB_UTIL_EDNS_NXRRSET PJ_STATUS_FROM_DNS_RCODE(8) /* 320058 */ +/** + * @hideinitializer + * The requestor is not authorized to perform this operation. + * This corresponds to DNS RCODE 9. + */ +#define PJLIB_UTIL_EDNS_NOTAUTH PJ_STATUS_FROM_DNS_RCODE(9) /* 320059 */ +/** + * @hideinitializer + * The zone specified is not a zone. + * This corresponds to DNS RCODE 10. + */ +#define PJLIB_UTIL_EDNS_NOTZONE PJ_STATUS_FROM_DNS_RCODE(10) /* 320060 */ + +/************************************************************ + * NEW STUN ERROR + ***********************************************************/ +/* Messaging errors */ +/** + * @hideinitializer + * Too many STUN attributes. + */ +#define PJLIB_UTIL_ESTUNTOOMANYATTR (PJLIB_UTIL_ERRNO_START + 110) /* 320110 */ +/** + * @hideinitializer + * Unknown STUN attribute. This error happens when the decoder encounters + * mandatory attribute type which it doesn't understand. + */ +#define PJLIB_UTIL_ESTUNUNKNOWNATTR (PJLIB_UTIL_ERRNO_START + 111) /* 320111 */ +/** + * @hideinitializer + * Invalid STUN socket address length. + */ +#define PJLIB_UTIL_ESTUNINADDRLEN (PJLIB_UTIL_ERRNO_START + 112) /* 320112 */ +/** + * @hideinitializer + * STUN IPv6 attribute not supported + */ +#define PJLIB_UTIL_ESTUNIPV6NOTSUPP (PJLIB_UTIL_ERRNO_START + 113) /* 320113 */ +/** + * @hideinitializer + * Expecting STUN response message. + */ +#define PJLIB_UTIL_ESTUNNOTRESPONSE (PJLIB_UTIL_ERRNO_START + 114) /* 320114 */ +/** + * @hideinitializer + * STUN transaction ID mismatch. + */ +#define PJLIB_UTIL_ESTUNINVALIDID (PJLIB_UTIL_ERRNO_START + 115) /* 320115 */ +/** + * @hideinitializer + * Unable to find handler for the request. + */ +#define PJLIB_UTIL_ESTUNNOHANDLER (PJLIB_UTIL_ERRNO_START + 116) /* 320116 */ +/** + * @hideinitializer + * Found non-FINGERPRINT attribute after MESSAGE-INTEGRITY. This is not + * valid since MESSAGE-INTEGRITY MUST be the last attribute or the + * attribute right before FINGERPRINT before the message. + */ +#define PJLIB_UTIL_ESTUNMSGINTPOS (PJLIB_UTIL_ERRNO_START + 118) /* 320118 */ +/** + * @hideinitializer + * Found attribute after FINGERPRINT. This is not valid since FINGERPRINT + * MUST be the last attribute in the message. + */ +#define PJLIB_UTIL_ESTUNFINGERPOS (PJLIB_UTIL_ERRNO_START + 119) /* 320119 */ +/** + * @hideinitializer + * Missing STUN USERNAME attribute. + * When credential is included in the STUN message (MESSAGE-INTEGRITY is + * present), the USERNAME attribute must be present in the message. + */ +#define PJLIB_UTIL_ESTUNNOUSERNAME (PJLIB_UTIL_ERRNO_START + 120) /* 320120 */ +/** + * @hideinitializer + * Unknown STUN username/credential. + */ +#define PJLIB_UTIL_ESTUNUSERNAME (PJLIB_UTIL_ERRNO_START + 121) /* 320121 */ +/** + * @hideinitializer + * Missing/invalidSTUN MESSAGE-INTEGRITY attribute. + */ +#define PJLIB_UTIL_ESTUNMSGINT (PJLIB_UTIL_ERRNO_START + 122) /* 320122 */ +/** + * @hideinitializer + * Found duplicate STUN attribute. + */ +#define PJLIB_UTIL_ESTUNDUPATTR (PJLIB_UTIL_ERRNO_START + 123) /* 320123 */ +/** + * @hideinitializer + * Missing STUN REALM attribute. + */ +#define PJLIB_UTIL_ESTUNNOREALM (PJLIB_UTIL_ERRNO_START + 124) /* 320124 */ +/** + * @hideinitializer + * Missing/stale STUN NONCE attribute value. + */ +#define PJLIB_UTIL_ESTUNNONCE (PJLIB_UTIL_ERRNO_START + 125) /* 320125 */ +/** + * @hideinitializer + * STUN transaction terminates with failure. + */ +#define PJLIB_UTIL_ESTUNTSXFAILED (PJLIB_UTIL_ERRNO_START + 126) /* 320126 */ + +//#define PJ_STATUS_FROM_STUN_CODE(code) (PJLIB_UTIL_ERRNO_START+code) + +/************************************************************ + * HTTP Client ERROR + ***********************************************************/ +/** + * @hideinitializer + * Invalid URL format + */ +#define PJLIB_UTIL_EHTTPINURL (PJLIB_UTIL_ERRNO_START + 151) /* 320151 */ +/** + * @hideinitializer + * Invalid port number + */ +#define PJLIB_UTIL_EHTTPINPORT (PJLIB_UTIL_ERRNO_START + 152) /* 320152 */ +/** + * @hideinitializer + * Incomplete headers received + */ +#define PJLIB_UTIL_EHTTPINCHDR (PJLIB_UTIL_ERRNO_START + 153) /* 320153 */ +/** + * @hideinitializer + * Insufficient buffer + */ +#define PJLIB_UTIL_EHTTPINSBUF (PJLIB_UTIL_ERRNO_START + 154) /* 320154 */ +/** + * @hideinitializer + * Connection lost + */ +#define PJLIB_UTIL_EHTTPLOST (PJLIB_UTIL_ERRNO_START + 155) /* 320155 */ + +/************************************************************ + * CLI ERROR + ***********************************************************/ + +/** + * @hideinitializer + * End the current session. This is a special error code returned by + * pj_cli_sess_exec() to indicate that "exit" or equivalent command has been + * called to end the current session. + */ +#define PJ_CLI_EEXIT (PJLIB_UTIL_ERRNO_START + 201) /* 320201 */ +/** + * @hideinitializer + * A required CLI argument is not specified. + */ +#define PJ_CLI_EMISSINGARG (PJLIB_UTIL_ERRNO_START + 202) /* 320202 */ + /** + * @hideinitializer + * Too many CLI arguments. + */ +#define PJ_CLI_ETOOMANYARGS (PJLIB_UTIL_ERRNO_START + 203) /* 320203 */ +/** + * @hideinitializer + * Invalid CLI argument. Typically this is caused by extra characters + * specified in the command line which does not match any arguments. + */ +#define PJ_CLI_EINVARG (PJLIB_UTIL_ERRNO_START + 204) /* 320204 */ +/** + * @hideinitializer + * CLI command with the specified name already exist. + */ +#define PJ_CLI_EBADNAME (PJLIB_UTIL_ERRNO_START + 205) /* 320205 */ +/** + * @hideinitializer + * CLI command with the specified id already exist. + */ +#define PJ_CLI_EBADID (PJLIB_UTIL_ERRNO_START + 206) /* 320206 */ +/** + * @hideinitializer + * Invalid XML format for CLI command specification. + */ +#define PJ_CLI_EBADXML (PJLIB_UTIL_ERRNO_START + 207) /* 320207 */ +/** + * @hideinitializer + * CLI command entered by user match with more than one command/argument + * specification. + */ +#define PJ_CLI_EAMBIGUOUS (PJLIB_UTIL_ERRNO_START + 208) /* 320208 */ +/** + * @hideinitializer + * Telnet connection lost. + */ +#define PJ_CLI_ETELNETLOST (PJLIB_UTIL_ERRNO_START + 211) /* 320211 */ + +/** + * @} + */ + +#endif /* __PJLIB_UTIL_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h new file mode 100755 index 000000000..dbba7b56c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h @@ -0,0 +1,139 @@ +/* Declarations for pj_getopt. + Copyright (C) 1989,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef __PJ_GETOPT_H__ +#define __PJ_GETOPT_H__ 1 + +/** + * @file getopt.h + * @brief Compile time settings + */ + +/** + * @defgroup PJLIB_UTIL_GETOPT Getopt + * @ingroup PJLIB_TEXT + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `pj_getopt' to the caller. + When `pj_getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *pj_optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `pj_getopt'. + + On entry to `pj_getopt', zero means this is the first call; initialize. + + When `pj_getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `pj_optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int pj_optind; + +/* Set to an option character which was unrecognized. */ + +extern int pj_optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to pj_getopt_long or pj_getopt_long_only is a vector + of `struct pj_getopt_option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `pj_optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `pj_getopt' + returns the contents of the `val' field. */ + +struct pj_getopt_option { + const char *name; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct pj_getopt_option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `pj_optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `pj_optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `pj_getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `pj_getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `pj_getopt'. */ + +int pj_getopt(int argc, char *const *argv, const char *shortopts); + +int pj_getopt_long(int argc, char *const *argv, const char *options, const struct pj_getopt_option *longopts, + int *longind); +int pj_getopt_long_only(int argc, char *const *argv, const char *shortopts, const struct pj_getopt_option *longopts, + int *longind); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* pj_getopt.h */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h new file mode 100755 index 000000000..ff348fef4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_HMAC_MD5_H__ +#define __PJLIB_UTIL_HMAC_MD5_H__ + +/** + * @file hmac_md5.h + * @brief HMAC MD5 Message Authentication + */ + +/** + * @defgroup PJLIB_UTIL_ENCRYPTION Encryption Algorithms + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_HMAC_MD5 HMAC MD5 Message Authentication + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * + * This module contains the implementation of HMAC: Keyed-Hashing + * for Message Authentication, as described in RFC 2104 + */ + +/** + * The HMAC-MD5 context used in the incremental HMAC calculation. + */ +typedef struct pj_hmac_md5_context { + pj_md5_context context; /**< MD5 context */ + pj_uint8_t k_opad[64]; /**< opad xor-ed with key */ +} pj_hmac_md5_context; + +/** + * Calculate HMAC MD5 digest for the specified input and key. + * + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + * @param digest Buffer to be filled with HMAC MD5 digest. + */ +PJ_DECL(void) +pj_hmac_md5(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, + pj_uint8_t digest[16]); + +/** + * Initiate HMAC-MD5 context for incremental hashing. + * + * @param hctx HMAC-MD5 context. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + */ +PJ_DECL(void) pj_hmac_md5_init(pj_hmac_md5_context *hctx, const pj_uint8_t *key, unsigned key_len); + +/** + * Append string to the message. + * + * @param hctx HMAC-MD5 context. + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + */ +PJ_DECL(void) pj_hmac_md5_update(pj_hmac_md5_context *hctx, const pj_uint8_t *input, unsigned input_len); + +/** + * Finish the message and return the digest. + * + * @param hctx HMAC-MD5 context. + * @param digest Buffer to be filled with HMAC MD5 digest. + */ +PJ_DECL(void) pj_hmac_md5_final(pj_hmac_md5_context *hctx, pj_uint8_t digest[16]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_HMAC_MD5_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h new file mode 100755 index 000000000..14d47737c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_HMAC_SHA1_H__ +#define __PJLIB_UTIL_HMAC_SHA1_H__ + +/** + * @file hmac_sha1.h + * @brief HMAC SHA1 Message Authentication + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_HMAC_SHA1 HMAC SHA1 Message Authentication + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * + * This module contains the implementation of HMAC: Keyed-Hashing + * for Message Authentication, as described in RFC 2104. + */ + +/** + * The HMAC-SHA1 context used in the incremental HMAC calculation. + */ +typedef struct pj_hmac_sha1_context { + pj_sha1_context context; /**< SHA1 context */ + pj_uint8_t k_opad[64]; /**< opad xor-ed with key */ +} pj_hmac_sha1_context; + +/** + * Calculate HMAC-SHA1 digest for the specified input and key with this + * single function call. + * + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + * @param digest Buffer to be filled with HMAC SHA1 digest. + */ +PJ_DECL(void) +pj_hmac_sha1(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, + pj_uint8_t digest[20]); + +/** + * Initiate HMAC-SHA1 context for incremental hashing. + * + * @param hctx HMAC-SHA1 context. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + */ +PJ_DECL(void) pj_hmac_sha1_init(pj_hmac_sha1_context *hctx, const pj_uint8_t *key, unsigned key_len); + +/** + * Append string to the message. + * + * @param hctx HMAC-SHA1 context. + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + */ +PJ_DECL(void) pj_hmac_sha1_update(pj_hmac_sha1_context *hctx, const pj_uint8_t *input, unsigned input_len); + +/** + * Finish the message and return the digest. + * + * @param hctx HMAC-SHA1 context. + * @param digest Buffer to be filled with HMAC SHA1 digest. + */ +PJ_DECL(void) pj_hmac_sha1_final(pj_hmac_sha1_context *hctx, pj_uint8_t digest[20]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_HMAC_SHA1_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h new file mode 100755 index 000000000..d42f68e65 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_HTTP_CLIENT_H__ +#define __PJLIB_UTIL_HTTP_CLIENT_H__ + +/** + * @file http_client.h + * @brief Simple HTTP Client + */ +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_HTTP_CLIENT Simple HTTP Client + * @ingroup PJ_PROTOCOLS + * @{ + * This contains a simple HTTP client implementation. + * Some known limitations: + * - Does not support chunked Transfer-Encoding. + */ + +/** + * This opaque structure describes the http request. + */ +typedef struct pj_http_req pj_http_req; + +/** + * Defines the maximum number of elements in a pj_http_headers + * structure. + */ +#define PJ_HTTP_HEADER_SIZE 32 + +/** + * HTTP header representation. + */ +typedef struct pj_http_header_elmt { + pj_str_t name; /**< Header name */ + pj_str_t value; /**< Header value */ +} pj_http_header_elmt; + +/** + * This structure describes http request/response headers. + * Application should call #pj_http_headers_add_elmt() to + * add a header field. + */ +typedef struct pj_http_headers { + /**< Number of header fields */ + unsigned count; + + /** Header elements/fields */ + pj_http_header_elmt header[PJ_HTTP_HEADER_SIZE]; +} pj_http_headers; + +/** + * Structure to save HTTP authentication credential. + */ +typedef struct pj_http_auth_cred { + /** + * Specify specific authentication schemes to be responded. Valid values + * are "basic" and "digest". If this field is not set, any authentication + * schemes will be responded. + * + * Default is empty. + */ + pj_str_t scheme; + + /** + * Specify specific authentication realm to be responded. If this field + * is set, only 401/407 response with matching realm will be responded. + * If this field is not set, any realms will be responded. + * + * Default is empty. + */ + pj_str_t realm; + + /** + * Specify authentication username. + * + * Default is empty. + */ + pj_str_t username; + + /** + * The type of password in \a data field. Currently only 0 is + * supported, meaning the \a data contains plain-text password. + * + * Default is 0. + */ + unsigned data_type; + + /** + * Specify authentication password. The encoding of the password depends + * on the value of \a data_type field above. + * + * Default is empty. + */ + pj_str_t data; + +} pj_http_auth_cred; + +/** + * Parameters that can be given during http request creation. Application + * must initialize this structure with #pj_http_req_param_default(). + */ +typedef struct pj_http_req_param { + /** + * The address family of the URL. + * Default is pj_AF_INET(). + */ + int addr_family; + + /** + * The HTTP request method. + * Default is GET. + */ + pj_str_t method; + + /** + * The HTTP protocol version ("1.0" or "1.1"). + * Default is "1.0". + */ + pj_str_t version; + + /** + * HTTP request operation timeout. + * Default is PJ_HTTP_DEFAULT_TIMEOUT. + */ + pj_time_val timeout; + + /** + * User-defined data. + * Default is NULL. + */ + void *user_data; + + /** + * HTTP request headers. + * Default is empty. + */ + pj_http_headers headers; + + /** + * This structure describes the http request body. If application + * specifies the data to send, the data must remain valid until + * the HTTP request is sent. Alternatively, application can choose + * to specify total_size as the total data size to send instead + * while leaving the data NULL (and its size 0). In this case, + * HTTP request will then call on_send_data() callback once it is + * ready to send the request body. This will be useful if + * application does not wish to load the data into the buffer at + * once. + * + * Default is empty. + */ + struct pj_http_reqdata { + void *data; /**< Request body data */ + pj_size_t size; /**< Request body size */ + pj_size_t total_size; /**< If total_size > 0, data */ + /**< will be provided later */ + } reqdata; /**< The request body */ + + /** + * Authentication credential needed to respond to 401/407 response. + */ + pj_http_auth_cred auth_cred; + + /** + * Optional source port range to use when binding the socket. + * This can be used if the source port needs to be within a certain range + * for instance due to strict firewall settings. The port used will be + * randomized within the range. + * + * Note that if authentication is configured, the authentication response + * will be a new transaction + * + * Default is 0 (The OS will select the source port automatically) + */ + pj_uint16_t source_port_range_start; + + /** + * Optional source port range to use when binding. + * The size of the port restriction range + * + * Default is 0 (The OS will select the source port automatically)) + */ + pj_uint16_t source_port_range_size; + + /** + * Max number of retries if binding to a port fails. + * Note that this does not adress the scenario where a request times out + * or errors. This needs to be taken care of by the on_complete callback. + * + * Default is 3 + */ + pj_uint16_t max_retries; + +} pj_http_req_param; + +/** + * HTTP authentication challenge, parsed from WWW-Authenticate header. + */ +typedef struct pj_http_auth_chal { + pj_str_t scheme; /**< Auth scheme. */ + pj_str_t realm; /**< Realm for the challenge. */ + pj_str_t domain; /**< Domain. */ + pj_str_t nonce; /**< Nonce challenge. */ + pj_str_t opaque; /**< Opaque value. */ + int stale; /**< Stale parameter. */ + pj_str_t algorithm; /**< Algorithm parameter. */ + pj_str_t qop; /**< Quality of protection. */ +} pj_http_auth_chal; + +/** + * This structure describes HTTP response. + */ +typedef struct pj_http_resp { + pj_str_t version; /**< HTTP version of the server */ + pj_uint16_t status_code; /**< Status code of the request */ + pj_str_t reason; /**< Reason phrase */ + pj_http_headers headers; /**< Response headers */ + pj_http_auth_chal auth_chal; /**< Parsed WWW-Authenticate header, if + any. */ + pj_int32_t content_length; /**< The value of content-length header + field. -1 if not specified. */ + void *data; /**< Data received */ + pj_size_t size; /**< Data size */ +} pj_http_resp; + +/** + * This structure describes HTTP URL. + */ +typedef struct pj_http_url { + pj_str_t username; /**< Username part */ + pj_str_t passwd; /**< Password part */ + pj_str_t protocol; /**< Protocol used */ + pj_str_t host; /**< Host name */ + pj_uint16_t port; /**< Port number */ + pj_str_t path; /**< Path */ +} pj_http_url; + +/** + * This structure describes the callbacks to be called by the HTTP request. + */ +typedef struct pj_http_req_callback { + /** + * This callback is called when a complete HTTP response header + * is received. + * + * @param http_req The http request. + * @param resp The response of the request. + */ + void (*on_response)(pj_http_req *http_req, const pj_http_resp *resp); + + /** + * This callback is called when the HTTP request is ready to send + * its request body. Application may wish to use this callback if + * it wishes to load the data at a later time or if it does not + * wish to load the whole data into memory. In order for this + * callback to be called, application MUST set http_req_param.total_size + * to a value greater than 0. + * + * @param http_req The http request. + * @param data Pointer to the data that will be sent. Application + * must set the pointer to the current data chunk/segment + * to be sent. Data must remain valid until the next + * on_send_data() callback or for the last segment, + * until it is sent. + * @param size Pointer to the data size that will be sent. + */ + void (*on_send_data)(pj_http_req *http_req, void **data, pj_size_t *size); + + /** + * This callback is called when a segment of response body data + * arrives. If this callback is specified (i.e. not NULL), the + * on_complete() callback will be called with zero-length data + * (within the response parameter), hence the application must + * store and manage its own data buffer, otherwise the + * on_complete() callback will be called with the response + * parameter containing the complete data. + * + * @param http_req The http request. + * @param data The buffer containing the data. + * @param size The length of data in the buffer. + */ + void (*on_data_read)(pj_http_req *http_req, void *data, pj_size_t size); + + /** + * This callback is called when the HTTP request is completed. + * If the callback on_data_read() is specified, the variable + * response->data will be set to NULL, otherwise it will + * contain the complete data. Response data is allocated from + * pj_http_req's internal memory pool so the data remain valid + * as long as pj_http_req is not destroyed and application does + * not start a new request. + * + * If no longer required, application may choose to destroy + * pj_http_req immediately by calling #pj_http_req_destroy() inside + * the callback. + * + * @param http_req The http request. + * @param status The status of the request operation. PJ_SUCCESS + * if the operation completed successfully + * (connection-wise). To check the server's + * status-code response to the HTTP request, + * application should check resp->status_code instead. + * @param resp The response of the corresponding request. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + */ + void (*on_complete)(pj_http_req *http_req, pj_status_t status, const pj_http_resp *resp); + +} pj_http_req_callback; + +/** + * Initialize the http request parameters with the default values. + * + * @param param The parameter to be initialized. + */ +PJ_DECL(void) pj_http_req_param_default(pj_http_req_param *param); + +/** + * Add a header element/field. Application MUST make sure that + * name and val pointer remains valid until the HTTP request is sent. + * + * @param headers The headers. + * @param name The header field name. + * @param val The header field value. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_http_headers_add_elmt(pj_http_headers *headers, pj_str_t *name, pj_str_t *val); + +/** + * The same as #pj_http_headers_add_elmt() with char * as + * its parameters. Application MUST make sure that name and val pointer + * remains valid until the HTTP request is sent. + * + * @param headers The headers. + * @param name The header field name. + * @param val The header field value. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_http_headers_add_elmt2(pj_http_headers *headers, char *name, char *val); + +/** + * Parse a http URL into its components. + * + * @param url The URL to be parsed. + * @param hurl Pointer to receive the parsed result. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_http_req_parse_url(const pj_str_t *url, pj_http_url *hurl); + +/** + * Create the HTTP request. + * + * @param pool Pool to use. HTTP request will use the pool's factory + * to allocate its own memory pool. + * @param url HTTP URL request. + * @param timer The timer to use. + * @param ioqueue The ioqueue to use. + * @param param Optional parameters. When this parameter is not + * specifed (NULL), the default values will be used. + * @param hcb Pointer to structure containing application + * callbacks. + * @param http_req Pointer to receive the http request instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_http_req_create(pj_pool_t *pool, const pj_str_t *url, pj_timer_heap_t *timer, pj_ioqueue_t *ioqueue, + const pj_http_req_param *param, const pj_http_req_callback *hcb, pj_http_req **http_req); + +/** + * Set the timeout of the HTTP request operation. Note that if the + * HTTP request is currently running, the timeout will only affect + * subsequent request operations. + * + * @param http_req The http request. + * @param timeout Timeout value for HTTP request operation. + */ +PJ_DECL(void) pj_http_req_set_timeout(pj_http_req *http_req, const pj_time_val *timeout); + +/** + * Starts an asynchronous HTTP request to the URL specified. + * + * @param http_req The http request. + * + * @return + * - PJ_SUCCESS if success + * - non-zero which indicates the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_http_req_start(pj_http_req *http_req); + +/** + * Cancel the asynchronous HTTP request. + * + * @param http_req The http request. + * @param notify If non-zero, the on_complete() callback will be + * called with status PJ_ECANCELLED to notify that + * the query has been cancelled. + * + * @return + * - PJ_SUCCESS if success + * - non-zero which indicates the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_http_req_cancel(pj_http_req *http_req, pj_bool_t notify); + +/** + * Destroy the http request. + * + * @param http_req The http request to be destroyed. + * + * @return PJ_SUCCESS if success. + */ +PJ_DECL(pj_status_t) pj_http_req_destroy(pj_http_req *http_req); + +/** + * Find out whether the http request is running. + * + * @param http_req The http request. + * + * @return PJ_TRUE if a request is pending, or + * PJ_FALSE if idle + */ +PJ_DECL(pj_bool_t) pj_http_req_is_running(const pj_http_req *http_req); + +/** + * Retrieve the user data previously associated with this http + * request. + * + * @param http_req The http request. + * + * @return The user data. + */ +PJ_DECL(void *) pj_http_req_get_user_data(pj_http_req *http_req); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_HTTP_CLIENT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h new file mode 100755 index 000000000..b55b61e5e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_JSON_H__ +#define __PJLIB_UTIL_JSON_H__ + +/** + * @file json.h + * @brief PJLIB JSON Implementation + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_JSON JSON Writer and Loader + * @ingroup PJ_FILE_FMT + * @{ + * This API implements JSON file format according to RFC 4627. It can be used + * to parse, write, and manipulate JSON documents. + */ + +/** + * Type of JSON value. + */ +typedef enum pj_json_val_type { + PJ_JSON_VAL_NULL, /**< Null value (null) */ + PJ_JSON_VAL_BOOL, /**< Boolean value (true, false) */ + PJ_JSON_VAL_NUMBER, /**< Numeric (float or fixed point) */ + PJ_JSON_VAL_STRING, /**< Literal string value. */ + PJ_JSON_VAL_ARRAY, /**< Array */ + PJ_JSON_VAL_OBJ /**< Object. */ +} pj_json_val_type; + +/* Forward declaration for JSON element */ +typedef struct pj_json_elem pj_json_elem; + +/** + * JSON list to store child elements. + */ +typedef struct pj_json_list { + PJ_DECL_LIST_MEMBER(pj_json_elem); +} pj_json_list; + +/** + * This represents JSON element. A JSON element is basically a name/value + * pair, where the name is a string and the value can be one of null, boolean + * (true and false constants), number, string, array (containing zero or more + * elements), or object. An object can be viewed as C struct, that is a + * compound element containing other elements, each having name/value pair. + */ +struct pj_json_elem { + PJ_DECL_LIST_MEMBER(pj_json_elem); + pj_str_t name; /**< ELement name. */ + pj_json_val_type type; /**< Element type. */ + union { + pj_bool_t is_true; /**< Boolean value. */ + float num; /**< Number value. */ + pj_str_t str; /**< String value. */ + pj_json_list children; /**< Object and array children */ + } value; /**< Element value. */ +}; + +/** + * Structure to be specified to pj_json_parse() to be filled with additional + * info when parsing failed. + */ +typedef struct pj_json_err_info { + unsigned line; /**< Line location of the error */ + unsigned col; /**< Column location of the error */ + int err_char; /**< The offending character. */ +} pj_json_err_info; + +/** + * Type of function callback to write JSON document in pj_json_writef(). + * + * @param s The string to be written to the document. + * @param size The length of the string + * @param user_data User data that was specified to pj_json_writef() + * + * @return If the callback returns non-PJ_SUCCESS, it will + * stop the pj_json_writef() function and this error + * will be returned to caller. + */ +typedef pj_status_t (*pj_json_writer)(const char *s, unsigned size, void *user_data); + +/** + * Initialize null element. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + */ +PJ_DECL(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name); + +/** + * Initialize boolean element with the specified value. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + * @param val The value. + */ +PJ_DECL(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name, pj_bool_t val); + +/** + * Initialize number element with the specified value. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + * @param val The value. + */ +PJ_DECL(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name, float val); + +/** + * Initialize string element with the specified value. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + * @param val The value. + */ +PJ_DECL(void) pj_json_elem_string(pj_json_elem *el, pj_str_t *name, pj_str_t *val); + +/** + * Initialize element as an empty array + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + */ +PJ_DECL(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name); + +/** + * Initialize element as an empty object + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + */ +PJ_DECL(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name); + +/** + * Add an element to an object or array. + * + * @param el The object or array element. + * @param child Element to be added to the object or array. + */ +PJ_DECL(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child); + +/** + * Parse a JSON document in the buffer. The buffer MUST be NULL terminated, + * or if not then it must have enough size to put the NULL character. + * + * @param pool The pool to allocate memory for creating elements. + * @param buffer String buffer containing JSON document. + * @param size Size of the document. + * @param err_info Optional structure to be filled with info when + * parsing failed. + * + * @return The root element from the document. + */ +PJ_DECL(pj_json_elem *) pj_json_parse(pj_pool_t *pool, char *buffer, unsigned *size, pj_json_err_info *err_info); + +/** + * Write the specified element to the string buffer. + * + * @param elem The element to be written. + * @param buffer Output buffer. + * @param size On input, it must be set to the size of the buffer. + * Upon successful return, this will be set to + * the length of the written string. + * + * @return PJ_SUCCESS on success or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_json_write(const pj_json_elem *elem, char *buffer, unsigned *size); + +/** + * Incrementally write the element to arbitrary medium using the specified + * callback to write the document chunks. + * + * @param elem The element to be written. + * @param writer Callback function which will be called to write + * text chunks. + * @param user_data Arbitrary user data which will be given back when + * calling the callback. + * + * @return PJ_SUCCESS on success or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_json_writef(const pj_json_elem *elem, pj_json_writer writer, void *user_data); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_JSON_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h new file mode 100755 index 000000000..9afd97e4a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_MD5_H__ +#define __PJLIB_UTIL_MD5_H__ + +/** + * @file md5.h + * @brief MD5 Functions + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_MD5 MD5 + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + */ + +/** MD5 context. */ +typedef struct pj_md5_context { + pj_uint32_t buf[4]; /**< buf */ + pj_uint32_t bits[2]; /**< bits */ + pj_uint8_t in[64]; /**< in */ +} pj_md5_context; + +/** Initialize the algorithm. + * @param pms MD5 context. + */ +PJ_DECL(void) pj_md5_init(pj_md5_context *pms); + +/** Append a string to the message. + * @param pms MD5 context. + * @param data Data. + * @param nbytes Length of data. + */ +PJ_DECL(void) pj_md5_update(pj_md5_context *pms, const pj_uint8_t *data, unsigned nbytes); + +/** Finish the message and return the digest. + * @param pms MD5 context. + * @param digest 16 byte digest. + */ +PJ_DECL(void) pj_md5_final(pj_md5_context *pms, pj_uint8_t digest[16]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_MD5_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h new file mode 100755 index 000000000..517898a70 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_PCAP_H__ +#define __PJLIB_UTIL_PCAP_H__ + +/** + * @file pcap.h + * @brief Simple PCAP file reader + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_PCAP Simple PCAP file reader + * @ingroup PJ_FILE_FMT + * @{ + * This module describes simple utility to read PCAP file. It is not intended + * to support all PCAP features (that's what libpcap is for!), but it can + * be useful for example to playback or stream PCAP contents. + */ + +/** + * Enumeration to describe supported data link types. + */ +typedef enum pj_pcap_link_type { + /** Ethernet data link */ + PJ_PCAP_LINK_TYPE_ETH = 1 + +} pj_pcap_link_type; + +/** + * Enumeration to describe supported protocol types. + */ +typedef enum pj_pcap_proto_type { + /** UDP protocol */ + PJ_PCAP_PROTO_TYPE_UDP = 17 + +} pj_pcap_proto_type; + +/** + * This describes UDP header, which may optionally be returned in + * #pj_pcap_read_udp() function. All fields are in network byte order. + */ +typedef struct pj_pcap_udp_hdr { + pj_uint16_t src_port; /**< Source port. */ + pj_uint16_t dst_port; /**< Destination port */ + pj_uint16_t len; /**< Length. */ + pj_uint16_t csum; /**< Checksum. */ +} pj_pcap_udp_hdr; + +/** + * This structure describes the filter to be used when reading packets from + * a PCAP file. When a filter is configured, only packets matching all the + * filter specifications will be read from PCAP file. + */ +typedef struct pj_pcap_filter { + /** + * Select data link type, or zero to include any supported data links. + */ + pj_pcap_link_type link; + + /** + * Select protocol, or zero to include all supported protocols. + */ + pj_pcap_proto_type proto; + + /** + * Specify source IP address of the packets, or zero to include packets + * from any IP addresses. Note that IP address here must be in + * network byte order. + */ + pj_uint32_t ip_src; + + /** + * Specify destination IP address of the packets, or zero to include packets + * destined to any IP addresses. Note that IP address here must be in + * network byte order. + */ + pj_uint32_t ip_dst; + + /** + * Specify source port of the packets, or zero to include packets with + * any source port number. Note that the port number must be in network + * byte order. + */ + pj_uint16_t src_port; + + /** + * Specify destination port of the packets, or zero to include packets with + * any destination port number. Note that the port number must be in network + * byte order. + */ + pj_uint16_t dst_port; + +} pj_pcap_filter; + +/** Opaque declaration for PCAP file */ +typedef struct pj_pcap_file pj_pcap_file; + +/** + * Initialize filter with default values. The default value is to allow + * any packets. + * + * @param filter Filter to be initialized. + */ +PJ_DECL(void) pj_pcap_filter_default(pj_pcap_filter *filter); + +/** + * Open PCAP file. + * + * @param pool Pool to allocate memory. + * @param path File/path name. + * @param p_file Pointer to receive PCAP file handle. + * + * @return PJ_SUCCESS if file can be opened successfully. + */ +PJ_DECL(pj_status_t) pj_pcap_open(pj_pool_t *pool, const char *path, pj_pcap_file **p_file); + +/** + * Close PCAP file. + * + * @param file PCAP file handle. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_pcap_close(pj_pcap_file *file); + +/** + * Configure filter for reading the file. When filter is configured, + * only packets matching all the filter settings will be returned. + * + * @param file PCAP file handle. + * @param filter The filter. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_pcap_set_filter(pj_pcap_file *file, const pj_pcap_filter *filter); + +/** + * Read UDP payload from the next packet in the PCAP file. Optionally it + * can return the UDP header, if caller supplies it. + * + * @param file PCAP file handle. + * @param udp_hdr Optional buffer to receive UDP header. + * @param udp_payload Buffer to receive the UDP payload. + * @param udp_payload_size On input, specify the size of the buffer. + * On output, it will be filled with the actual size + * of the payload as read from the packet. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_pcap_read_udp(pj_pcap_file *file, pj_pcap_udp_hdr *udp_hdr, pj_uint8_t *udp_payload, pj_size_t *udp_payload_size); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_PCAP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h new file mode 100755 index 000000000..f2ece28ba --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_RESOLVER_H__ +#define __PJLIB_UTIL_RESOLVER_H__ + +/** + * @file resolver.h + * @brief Asynchronous DNS resolver + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS_RESOLVER DNS Asynchronous/Caching Resolution Engine + * @ingroup PJ_DNS + * @{ + * + * This module manages the host/server resolution by performing asynchronous + * DNS queries and caching the results in the cache. It uses PJLIB-UTIL + * low-level DNS parsing functions (see @ref PJ_DNS) and currently supports + * several types of DNS resource records such as A record (typical query with + * gethostbyname()) and SRV record. + * + * \section PJ_DNS_RESOLVER_FEATURES Features + * + * \subsection PJ_DNS_RESOLVER_FEATURES_ASYNC Asynchronous Query and Query Aggregation + * + * The DNS queries are performed asychronously, with timeout setting + * configured on per resolver instance basis. Application can issue multiple + * asynchronous queries simultaneously. Subsequent queries to the same resource + * (name and DNS resource type) while existing query is still pending will be + * merged into one query, so that only one DNS request packet is issued. + * + * \subsection PJ_DNS_RESOLVER_FEATURES_RETRANSMISSION Query Retransmission + * + * Asynchronous query will be retransmitted if no response is received + * within the preconfigured time. Once maximum retransmission count is + * exceeded and no response is received, the query will time out and the + * callback will be called when error status. + * + * \subsection PJ_DNS_RESOLVER_FEATURES_CACHING Response Caching with TTL + * + * The resolver instance caches the results returned by nameservers, to + * enhance the performance by minimizing the message round-trip to the server. + * The TTL of the cached resposne is calculated from minimum TTL value found + * across all resource record (RR) TTL in the response and further more it can + * be limited to some preconfigured maximum TTL in the resolver. + * + * Response caching can be disabled by setting the maximum TTL value of the + * resolver to zero. + * + * \subsection PJ_DNS_RESOLVER_FEATURES_PARALLEL Parallel and Backup Name Servers + * + * When the resolver is configured with multiple nameservers, initially the + * queries will be issued to multiple name servers simultaneously to probe + * which servers are not active. Once the probing stage is done, subsequent + * queries will be directed to only one ACTIVE server which provides the best + * response time. + * + * Name servers are probed periodically to see which nameservers are active + * and which are down. This probing is done when a query is sent, thus no + * timer is needed to maintain this. Also probing will be done in parallel + * so that there would be no additional delay for the query. + * + * + * \subsection PJ_DNS_RESOLVER_FEATURES_REC Supported Resource Records + * + * The low-level DNS parsing utility (see @ref PJ_DNS) supports parsing of + * the following DNS resource records (RR): + * - DNS A record + * - DNS SRV record + * - DNS PTR record + * - DNS NS record + * - DNS CNAME record + * + * For other types of record, application can parse the raw resource + * record data (rdata) from the parsed DNS packet (#pj_dns_parsed_packet). + * + * + * \section PJ_DNS_RESOLVER_USING Using the Resolver + * + * To use the resolver, application first creates the resolver instance by + * calling #pj_dns_resolver_create(). If application already has its own + * timer and ioqueue instances, it can instruct the resolver to use these + * instances so that application does not need to poll the resolver + * periodically to process events. If application does not specify the + * timer and ioqueue instance for the resolver, an internal timer and + * ioqueue will be created by the resolver. And since the resolver does not + * create it's own thread, application MUST poll the resolver periodically + * by calling #pj_dns_resolver_handle_events() to allow events (network and + * timer) to be processed. + * + * Next, application MUST configure the nameservers to be used by the + * resolver, by calling #pj_dns_resolver_set_ns(). + * + * Application performs asynchronous query by submitting the query with + * #pj_dns_resolver_start_query(). Once the query completes (either + * successfully or times out), the callback will be called. + * + * Application can cancel a pending query by calling #pj_dns_resolver_cancel_query(). + * + * Resolver must be destroyed by calling #pj_dns_resolver_destroy() to + * release all resources back to the system. + * + * + * \section PJ_DNS_RESOLVER_LIMITATIONS Resolver Limitations + * + * Current implementation mainly suffers from a growing memory problem, + * which mainly is caused by the response caching. Although there is only + * one cache entry per {query, name} combination, these cache entry will + * never get deleted since there is no timer is created to invalidate these + * entries. So the more unique names being queried by application, there more + * enties will be created in the response cache. + * + * Note that a single response entry will occupy about 600-700 bytes of + * pool memory (the PJ_DNS_RESOLVER_RES_BUF_SIZE value plus internal + * structure). + * + * Application can work around this problem by doing one of these: + * - disable caching by setting PJ_DNS_RESOLVER_MAX_TTL and + * PJ_DNS_RESOLVER_INVALID_TTL to zero. + * - periodically query #pj_dns_resolver_get_cached_count() and destroy- + * recreate the resolver to recycle the memory used by the resolver. + * + * Note that future improvement may solve this problem by introducing + * expiration timer to the cached entries. + * + * + * \section PJ_DNS_RESOLVER_REFERENCE Reference + * + * The PJLIB-UTIL resolver was built from the information in the following + * standards: + * - + * RFC 1035: "Domain names - implementation and specification" + * - + * RFC 2782: "A DNS RR for specifying the location of services (DNS SRV)" + * + */ + +/** + * Opaque data type for DNS resolver object. + */ +typedef struct pj_dns_resolver pj_dns_resolver; + +/** + * Opaque data type for asynchronous DNS query object. + */ +typedef struct pj_dns_async_query pj_dns_async_query; + +/** + * Type of asynchronous callback which will be called when the asynchronous + * query completes. + * + * @param user_data The user data set by application when creating the + * asynchronous query. + * @param status Status of the DNS resolution. + * @param response The response packet received from the server. This + * argument may be NULL when status is not PJ_SUCCESS. + */ +typedef void pj_dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *response); + +/** + * This structure describes resolver settings. + */ +typedef struct pj_dns_settings { + unsigned options; /**< Options flags. */ + unsigned qretr_delay; /**< Query retransmit delay in msec. */ + unsigned qretr_count; /**< Query maximum retransmission count. */ + unsigned cache_max_ttl; /**< Maximum TTL for cached responses. If the + value is zero, caching is disabled. */ + unsigned good_ns_ttl; /**< See #PJ_DNS_RESOLVER_GOOD_NS_TTL */ + unsigned bad_ns_ttl; /**< See #PJ_DNS_RESOLVER_BAD_NS_TTL */ +} pj_dns_settings; + +/** + * This structure represents DNS A record, as the result of parsing + * DNS response packet using #pj_dns_parse_a_response(). + */ +typedef struct pj_dns_a_record { + /** The target name being queried. */ + pj_str_t name; + + /** If target name corresponds to a CNAME entry, the alias contains + * the value of the CNAME entry, otherwise it will be empty. + */ + pj_str_t alias; + + /** Number of IP addresses. */ + unsigned addr_count; + + /** IP addresses of the host found in the response */ + pj_in_addr addr[PJ_DNS_MAX_IP_IN_A_REC]; + + /** Internal buffer for hostname and alias. */ + char buf_[128]; + +} pj_dns_a_record; + +/** + * This structure represents DNS address record, i.e: DNS A and DNS AAAA + * records, as the result of parsing DNS response packet using + * #pj_dns_parse_addr_response(). + */ +typedef struct pj_dns_addr_record { + /** The target name being queried. */ + pj_str_t name; + + /** If target name corresponds to a CNAME entry, the alias contains + * the value of the CNAME entry, otherwise it will be empty. + */ + pj_str_t alias; + + /** Number of IP addresses. */ + unsigned addr_count; + + /** IP addresses of the host found in the response */ + struct { + + /** IP address family */ + int af; + + /** IP address */ + union { + /** IPv4 address */ + pj_in_addr v4; + + /** IPv6 address */ + pj_in6_addr v6; + } ip; + + } addr[PJ_DNS_MAX_IP_IN_A_REC]; + + /** Internal buffer for hostname and alias. */ + char buf_[128]; + +} pj_dns_addr_record; + +/** + * Set default values to the DNS settings. + * + * @param s The DNS settings to be initialized. + */ +PJ_DECL(void) pj_dns_settings_default(pj_dns_settings *s); + +/** + * Create DNS resolver instance. After the resolver is created, application + * MUST configure the nameservers with #pj_dns_resolver_set_ns(). + * + * When creating the resolver, application may specify both timer heap + * and ioqueue instance, so that it doesn't need to poll the resolver + * periodically. + * + * @param pf Pool factory where the memory pool will be created from. + * @param name Optional resolver name to identify the instance in + * the log. + * @param options Optional options, must be zero for now. + * @param timer Optional timer heap instance to be used by the resolver. + * If timer heap is not specified, an internal timer will be + * created, and application would need to poll the resolver + * periodically. + * @param ioqueue Optional I/O Queue instance to be used by the resolver. + * If ioqueue is not specified, an internal one will be + * created, and application would need to poll the resolver + * periodically. + * @param p_resolver Pointer to receive the resolver instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_create(pj_pool_factory *pf, const char *name, unsigned options, pj_timer_heap_t *timer, + pj_ioqueue_t *ioqueue, pj_dns_resolver **p_resolver); + +/** + * Update the name servers for the DNS resolver. The name servers MUST be + * configured before any resolution can be done. The order of nameservers + * specifies their priority; the first name server will be tried first + * before the next in the list. + * + * @param resolver The resolver instance. + * @param count Number of name servers in the array. + * @param servers Array of name server IP addresses or hostnames. If + * hostname is specified, the hostname must be resolvable + * with pj_gethostbyname(). + * @param ports Optional array of ports. If this argument is NULL, + * the nameserver will use default port. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, const pj_str_t servers[], const pj_uint16_t ports[]); + +/** + * Get the resolver current settings. + * + * @param resolver The resolver instance. + * @param st Buffer to be filled up with resolver settings. + * + * @return The query timeout setting, in seconds. + */ +PJ_DECL(pj_status_t) pj_dns_resolver_get_settings(pj_dns_resolver *resolver, pj_dns_settings *st); + +/** + * Modify the resolver settings. Application should initialize the settings + * by retrieving current settings first before applying new settings, to + * ensure that all fields are initialized properly. + * + * @param resolver The resolver instance. + * @param st The resolver settings. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver, const pj_dns_settings *st); + +/** + * Poll for events from the resolver. This function MUST be called + * periodically when the resolver is using it's own timer or ioqueue + * (in other words, when NULL is specified as either \a timer or + * \a ioqueue argument in #pj_dns_resolver_create()). + * + * @param resolver The resolver instance. + * @param timeout Maximum time to wait for event occurence. If this + * argument is NULL, this function will wait forever + * until events occur. + */ +PJ_DECL(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver, const pj_time_val *timeout); + +/** + * Destroy DNS resolver instance. + * + * @param resolver The resolver object to be destryed + * @param notify If non-zero, all pending asynchronous queries will be + * cancelled and its callback will be called. If FALSE, + * then no callback will be called. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_resolver_destroy(pj_dns_resolver *resolver, pj_bool_t notify); + +/** + * Create and start asynchronous DNS query for a single resource. Depending + * on whether response cache is available, this function will either start + * an asynchronous DNS query or call the callback immediately. + * + * If response is not available in the cache, an asynchronous query will be + * started, and callback will be called at some time later when the query + * completes. If \a p_query argument is not NULL, it will be filled with + * the asynchronous query object. + * + * If response is available in the cache, the callback will be called + * immediately before this function returns. In this case, if \a p_query + * argument is not NULL, the value will be set to NULL since no new query + * is started. + * + * @param resolver The resolver object. + * @param name The name to be resolved. + * @param type The type of resource (see #pj_dns_type constants). + * @param options Optional options, must be zero for now. + * @param cb Callback to be called when the query completes, + * either successfully or with failure. + * @param user_data Arbitrary user data to be associated with the query, + * and which will be given back in the callback. + * @param p_query Optional pointer to receive the query object, if one + * was started. If this pointer is specified, a NULL may + * be returned if response cache is available immediately. + * + * @return PJ_SUCCESS if either an asynchronous query has been + * started successfully or response cache is available and + * the user callback has been called. + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_start_query(pj_dns_resolver *resolver, const pj_str_t *name, int type, unsigned options, + pj_dns_callback *cb, void *user_data, pj_dns_async_query **p_query); + +/** + * Cancel a pending query. + * + * @param query The pending asynchronous query to be cancelled. + * @param notify If non-zero, the callback will be called with failure + * status to notify that the query has been cancelled. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query, pj_bool_t notify); + +/** + * A utility function to parse a DNS response containing A records into + * DNS A record. + * + * @param pkt The DNS response packet. + * @param rec The structure to be initialized with the parsed + * DNS A record from the packet. + * + * @return PJ_SUCCESS if response can be parsed successfully. + */ +PJ_DECL(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, pj_dns_a_record *rec); + +/** + * A utility function to parse a DNS response containing AAAA records into + * DNS AAAA record. + * + * @param pkt The DNS response packet. + * @param rec The structure to be initialized with the parsed + * DNS AAAA record from the packet. + * + * @return PJ_SUCCESS if response can be parsed successfully. + */ +PJ_DECL(pj_status_t) pj_dns_parse_addr_response(const pj_dns_parsed_packet *pkt, pj_dns_addr_record *rec); + +/** + * Put the specified DNS packet into DNS cache. This function is mainly used + * for testing the resolver, however it can also be used to inject entries + * into the resolver. + * + * The packet MUST contain either answer section or query section so that + * it can be indexed. + * + * @param resolver The resolver instance. + * @param pkt DNS packet to be added to the DNS cache. If the packet + * matches existing entry, it will update the entry. + * @param set_ttl If the value is PJ_FALSE, the entry will not expire + * (so use with care). Otherwise cache expiration will be + * calculated based on the TTL of the answeres. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_add_entry(pj_dns_resolver *resolver, const pj_dns_parsed_packet *pkt, pj_bool_t set_ttl); + +/** + * Get the total number of response in the response cache. + * + * @param resolver The resolver instance. + * + * @return Current number of entries being stored in the response + * cache. + */ +PJ_DECL(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver); + +/** + * Dump resolver state to the log. + * + * @param resolver The resolver instance. + * @param detail Will print detailed entries. + */ +PJ_DECL(void) pj_dns_resolver_dump(pj_dns_resolver *resolver, pj_bool_t detail); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_RESOLVER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h new file mode 100755 index 000000000..f44862cd1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SCANNER_H__ +#define __PJ_SCANNER_H__ + +/** + * @file scanner.h + * @brief Text Scanning. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SCAN Fast Text Scanning + * @ingroup PJLIB_TEXT + * @brief Text scanning utility. + * + * This module describes a fast text scanning functions. + * + * @{ + */ +#if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0 +#include +#else +#include +#endif + +/** + * Initialize scanner input specification buffer. + * + * @param cs_buf The scanner character specification. + */ +PJ_DECL(void) pj_cis_buf_init(pj_cis_buf_t *cs_buf); + +/** + * Create a new input specification. + * + * @param cs_buf Specification buffer. + * @param cis Character input specification to be initialized. + * + * @return PJ_SUCCESS if new specification has been successfully + * created, or PJ_ETOOMANY if there are already too many + * specifications in the buffer. + */ +PJ_DECL(pj_status_t) pj_cis_init(pj_cis_buf_t *cs_buf, pj_cis_t *cis); + +/** + * Create a new input specification based on an existing specification. + * + * @param new_cis The new specification to be initialized. + * @param existing The existing specification, from which the input + * bitmask will be copied to the new specification. + * + * @return PJ_SUCCESS if new specification has been successfully + * created, or PJ_ETOOMANY if there are already too many + * specifications in the buffer. + */ +PJ_DECL(pj_status_t) pj_cis_dup(pj_cis_t *new_cis, pj_cis_t *existing); + +/** + * Add the characters in the specified range '[cstart, cend)' to the + * specification (the last character itself ('cend') is not added). + * + * @param cis The scanner character specification. + * @param cstart The first character in the range. + * @param cend The next character after the last character in the range. + */ +PJ_DECL(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend); + +/** + * Add alphabetic characters to the specification. + * + * @param cis The scanner character specification. + */ +PJ_DECL(void) pj_cis_add_alpha(pj_cis_t *cis); + +/** + * Add numeric characters to the specification. + * + * @param cis The scanner character specification. + */ +PJ_DECL(void) pj_cis_add_num(pj_cis_t *cis); + +/** + * Add the characters in the string to the specification. + * + * @param cis The scanner character specification. + * @param str The string. + */ +PJ_DECL(void) pj_cis_add_str(pj_cis_t *cis, const char *str); + +/** + * Add specification from another specification. + * + * @param cis The specification is to be set. + * @param rhs The specification to be copied. + */ +PJ_DECL(void) pj_cis_add_cis(pj_cis_t *cis, const pj_cis_t *rhs); + +/** + * Delete characters in the specified range from the specification. + * + * @param cis The scanner character specification. + * @param cstart The first character in the range. + * @param cend The next character after the last character in the range. + */ +PJ_DECL(void) pj_cis_del_range(pj_cis_t *cis, int cstart, int cend); + +/** + * Delete characters in the specified string from the specification. + * + * @param cis The scanner character specification. + * @param str The string. + */ +PJ_DECL(void) pj_cis_del_str(pj_cis_t *cis, const char *str); + +/** + * Invert specification. + * + * @param cis The scanner character specification. + */ +PJ_DECL(void) pj_cis_invert(pj_cis_t *cis); + +/** + * Check whether the specified character belongs to the specification. + * + * @param cis The scanner character specification. + * @param c The character to check for matching. + * + * @return Non-zero if match (not necessarily one). + */ +PJ_INLINE(int) pj_cis_match(const pj_cis_t *cis, pj_uint8_t c) +{ + return PJ_CIS_ISSET(cis, c); +} + +/** + * Flags for scanner. + */ +enum { + /** This flags specifies that the scanner should automatically skip + whitespaces + */ + PJ_SCAN_AUTOSKIP_WS = 1, + + /** This flags specifies that the scanner should automatically skip + SIP header continuation. This flag implies PJ_SCAN_AUTOSKIP_WS. + */ + PJ_SCAN_AUTOSKIP_WS_HEADER = 3, + + /** Auto-skip new lines. + */ + PJ_SCAN_AUTOSKIP_NEWLINE = 4 +}; + +/* Forward decl. */ +struct pj_scanner; + +/** + * The callback function type to be called by the scanner when it encounters + * syntax error. + * + * @param scanner The scanner instance that calls the callback . + */ +typedef void (*pj_syn_err_func_ptr)(struct pj_scanner *scanner); + +/** + * The text scanner structure. + */ +typedef struct pj_scanner { + char *begin; /**< Start of input buffer. */ + char *end; /**< End of input buffer. */ + char *curptr; /**< Current pointer. */ + int line; /**< Current line. */ + char *start_line; /**< Where current line starts. */ + int skip_ws; /**< Skip whitespace flag. */ + pj_syn_err_func_ptr callback; /**< Syntax error callback. */ +} pj_scanner; + +/** + * This structure can be used by application to store the state of the parser, + * so that the scanner state can be rollback to this state when necessary. + */ +typedef struct pj_scan_state { + char *curptr; /**< Current scanner's pointer. */ + int line; /**< Current line. */ + char *start_line; /**< Start of current line. */ +} pj_scan_state; + +/** + * Initialize the scanner. + * Note that the input string buffer MUST be NULL terminated and have + * length at least buflen+1 (buflen MUST NOT include the NULL terminator). + * + * @param scanner The scanner to be initialized. + * @param bufstart The input buffer to scan, which must be NULL terminated. + * @param buflen The length of the input buffer, which normally is + * strlen(bufstart), hence not counting the NULL terminator. + * @param options Zero, or combination of PJ_SCAN_AUTOSKIP_WS or + * PJ_SCAN_AUTOSKIP_WS_HEADER + * @param callback Callback to be called when the scanner encounters syntax + * error condition. + */ +PJ_DECL(void) +pj_scan_init(pj_scanner *scanner, char *bufstart, pj_size_t buflen, unsigned options, pj_syn_err_func_ptr callback); + +/** + * Call this function when application has finished using the scanner. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_fini(pj_scanner *scanner); + +/** + * Determine whether the EOF condition for the scanner has been met. + * + * @param scanner The scanner. + * + * @return Non-zero if scanner is EOF. + */ +PJ_INLINE(int) pj_scan_is_eof(const pj_scanner *scanner) +{ + return scanner->curptr >= scanner->end; +} + +/** + * Peek strings in current position according to parameter spec, and return + * the strings in parameter out. The current scanner position will not be + * moved. If the scanner is already in EOF state, syntax error callback will + * be called thrown. + * + * @param scanner The scanner. + * @param spec The spec to match input string. + * @param out String to store the result. + * + * @return the character right after the peek-ed position or zero if there's + * no more characters. + */ +PJ_DECL(int) pj_scan_peek(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Peek len characters in current position, and return them in out parameter. + * Note that whitespaces or newlines will be returned as it is, regardless + * of PJ_SCAN_AUTOSKIP_WS settings. If the character left is less than len, + * syntax error callback will be called. + * + * @param scanner The scanner. + * @param len Length to peek. + * @param out String to store the result. + * + * @return the character right after the peek-ed position or zero if there's + * no more characters. + */ +PJ_DECL(int) pj_scan_peek_n(pj_scanner *scanner, pj_size_t len, pj_str_t *out); + +/** + * Peek strings in current position until spec is matched, and return + * the strings in parameter out. The current scanner position will not be + * moved. If the scanner is already in EOF state, syntax error callback will + * be called. + * + * @param scanner The scanner. + * @param spec The peeking will stop when the input match this spec. + * @param out String to store the result. + * + * @return the character right after the peek-ed position. + */ +PJ_DECL(int) pj_scan_peek_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Get characters from the buffer according to the spec, and return them + * in out parameter. The scanner will attempt to get as many characters as + * possible as long as the spec matches. If the first character doesn't + * match the spec, or scanner is already in EOF when this function is called, + * an exception will be thrown. + * + * @param scanner The scanner. + * @param spec The spec to match input string. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Just like #pj_scan_get(), but additionally performs unescaping when + * escaped ('%') character is found. The input spec MUST NOT contain the + * specification for '%' characted. + * + * @param scanner The scanner. + * @param spec The spec to match input string. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_unescape(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Get characters between quotes. If current input doesn't match begin_quote, + * syntax error will be thrown. Note that the resulting string will contain + * the enclosing quote. + * + * @param scanner The scanner. + * @param begin_quote The character to begin the quote. + * @param end_quote The character to end the quote. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_quote(pj_scanner *scanner, int begin_quote, int end_quote, pj_str_t *out); + +/** + * Get characters between quotes. If current input doesn't match begin_quote, + * syntax error will be thrown. Note that the resulting string will contain + * the enclosing quote. + * + * @param scanner The scanner. + * @param begin_quotes The character array to begin the quotes. For example, + * the two characters " and '. + * @param end_quotes The character array to end the quotes. The position + * found in the begin_quotes array will be used to match + * the end quotes. So if the begin_quotes was the array + * of "'< the end_quotes should be "'>. If begin_array + * matched the ' then the end_quotes will look for ' to + * match at the end. + * @param qsize The size of the begin_quotes and end_quotes arrays. + * @param out String to store the result. + */ +PJ_DECL(void) +pj_scan_get_quotes(pj_scanner *scanner, const char *begin_quotes, const char *end_quotes, int qsize, pj_str_t *out); + +/** + * Get N characters from the scanner. + * + * @param scanner The scanner. + * @param N Number of characters to get. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_n(pj_scanner *scanner, unsigned N, pj_str_t *out); + +/** + * Get one character from the scanner. + * + * @param scanner The scanner. + * + * @return The character. + */ +PJ_DECL(int) pj_scan_get_char(pj_scanner *scanner); + +/** + * Get characters from the scanner and move the scanner position until the + * current character matches the spec. + * + * @param scanner The scanner. + * @param spec Get until the input match this spec. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Get characters from the scanner and move the scanner position until the + * current character matches until_char. + * + * @param scanner The scanner. + * @param until_char Get until the input match this character. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_until_ch(pj_scanner *scanner, int until_char, pj_str_t *out); + +/** + * Get characters from the scanner and move the scanner position until the + * current character matches until_char. + * + * @param scanner The scanner. + * @param until_spec Get until the input match any of these characters. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_until_chr(pj_scanner *scanner, const char *until_spec, pj_str_t *out); + +/** + * Advance the scanner N characters, and skip whitespace + * if necessary. + * + * @param scanner The scanner. + * @param N Number of characters to skip. + * @param skip Flag to specify whether whitespace should be skipped + * after skipping the characters. + */ +PJ_DECL(void) pj_scan_advance_n(pj_scanner *scanner, unsigned N, pj_bool_t skip); + +/** + * Compare string in current position with the specified string. + * + * @param scanner The scanner. + * @param s The string to compare with. + * @param len Length of the string to compare. + * + * @return zero, <0, or >0 (just like strcmp()). + */ +PJ_DECL(int) pj_scan_strcmp(pj_scanner *scanner, const char *s, int len); + +/** + * Case-less string comparison of current position with the specified + * string. + * + * @param scanner The scanner. + * @param s The string to compare with. + * @param len Length of the string to compare with. + * + * @return zero, <0, or >0 (just like strcmp()). + */ +PJ_DECL(int) pj_scan_stricmp(pj_scanner *scanner, const char *s, int len); + +/** + * Perform case insensitive string comparison of string in current position, + * knowing that the string to compare only consists of alphanumeric + * characters. + * + * Note that unlike #pj_scan_stricmp, this function can only return zero or + * -1. + * + * @param scanner The scanner. + * @param s The string to compare with. + * @param len Length of the string to compare with. + * + * @return zero if equal or -1. + * + * @see strnicmp_alnum, pj_stricmp_alnum + */ +PJ_DECL(int) pj_scan_stricmp_alnum(pj_scanner *scanner, const char *s, int len); + +/** + * Get a newline from the scanner. A newline is defined as '\\n', or '\\r', or + * "\\r\\n". If current input is not newline, syntax error will be thrown. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_get_newline(pj_scanner *scanner); + +/** + * Manually skip whitespaces according to flag that was specified when + * the scanner was initialized. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_skip_whitespace(pj_scanner *scanner); + +/** + * Skip current line. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_skip_line(pj_scanner *scanner); + +/** + * Save the full scanner state. + * + * @param scanner The scanner. + * @param state Variable to store scanner's state. + */ +PJ_DECL(void) pj_scan_save_state(const pj_scanner *scanner, pj_scan_state *state); + +/** + * Restore the full scanner state. + * Note that this would not restore the string if application has modified + * it. This will only restore the scanner scanning position. + * + * @param scanner The scanner. + * @param state State of the scanner. + */ +PJ_DECL(void) pj_scan_restore_state(pj_scanner *scanner, pj_scan_state *state); + +/** + * Get current column position. + * + * @param scanner The scanner. + * + * @return The column position. + */ +PJ_INLINE(int) pj_scan_get_col(const pj_scanner *scanner) +{ + return (int)(scanner->curptr - scanner->start_line); +} + +/** + * @} + */ + +PJ_END_DECL + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h new file mode 100755 index 000000000..ffa2ea84d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SCANNER_CIS_BIT_H__ +#define __PJLIB_UTIL_SCANNER_CIS_BIT_H__ + +#include + +PJ_BEGIN_DECL + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. Basicly the number of bits here + */ +#ifndef PJ_CIS_ELEM_TYPE +#define PJ_CIS_ELEM_TYPE pj_uint32_t +#endif + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. + */ +typedef PJ_CIS_ELEM_TYPE pj_cis_elem_t; + +/** + * Maximum number of input specification in a buffer. + * Effectively this means the number of bits in pj_cis_elem_t. + */ +#define PJ_CIS_MAX_INDEX (sizeof(pj_cis_elem_t) << 3) + +/** + * The scanner input specification buffer. + */ +typedef struct pj_cis_buf_t { + pj_cis_elem_t cis_buf[256]; /**< Must be 256 (not 128)! */ + pj_cis_elem_t use_mask; /**< To keep used indexes. */ +} pj_cis_buf_t; + +/** + * Character input specification. + */ +typedef struct pj_cis_t { + pj_cis_elem_t *cis_buf; /**< Pointer to buffer. */ + int cis_id; /**< Id. */ +} pj_cis_t; + +/** + * Set the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_SET(cis, c) ((cis)->cis_buf[(int)(c)] |= (1 << (cis)->cis_id)) + +/** + * Remove the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character to be removed from the membership. + */ +#define PJ_CIS_CLR(cis, c) ((cis)->cis_buf[(int)c] &= ~(1 << (cis)->cis_id)) + +/** + * Check the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_ISSET(cis, c) ((cis)->cis_buf[(int)c] & (1 << (cis)->cis_id)) + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SCANNER_CIS_BIT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h new file mode 100755 index 000000000..d129f2c66 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SCANNER_CIS_BIT_H__ +#define __PJLIB_UTIL_SCANNER_CIS_BIT_H__ + +#include + +PJ_BEGIN_DECL + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. Basicly the number of bits here + */ +#ifndef PJ_CIS_ELEM_TYPE +#define PJ_CIS_ELEM_TYPE int +#endif + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. + */ +typedef PJ_CIS_ELEM_TYPE pj_cis_elem_t; + +/** pj_cis_buf_t is not used when uint back-end is used. */ +typedef int pj_cis_buf_t; + +/** + * Character input specification. + */ +typedef struct pj_cis_t { + PJ_CIS_ELEM_TYPE cis_buf[256]; /**< Internal buffer. */ +} pj_cis_t; + +/** + * Set the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_SET(cis, c) ((cis)->cis_buf[(int)(c)] = 1) + +/** + * Remove the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character to be removed from the membership. + */ +#define PJ_CIS_CLR(cis, c) ((cis)->cis_buf[(int)c] = 0) + +/** + * Check the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_ISSET(cis, c) ((cis)->cis_buf[(int)c]) + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SCANNER_CIS_BIT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h new file mode 100755 index 000000000..7cb861a89 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SHA1_H__ +#define __PJLIB_UTIL_SHA1_H__ + +/** + * @file sha1.h + * @brief SHA1 encryption implementation + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_SHA1 SHA1 + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + */ + +/** SHA1 context */ +typedef struct pj_sha1_context { + pj_uint32_t state[5]; /**< State */ + pj_uint32_t count[2]; /**< Count */ + pj_uint8_t buffer[64]; /**< Buffer */ +} pj_sha1_context; + +/** SHA1 digest size is 20 bytes */ +#define PJ_SHA1_DIGEST_SIZE 20 + +/** Initialize the algorithm. + * @param ctx SHA1 context. + */ +PJ_DECL(void) pj_sha1_init(pj_sha1_context *ctx); + +/** Append a stream to the message. + * @param ctx SHA1 context. + * @param data Data. + * @param nbytes Length of data. + */ +PJ_DECL(void) pj_sha1_update(pj_sha1_context *ctx, const pj_uint8_t *data, const pj_size_t nbytes); + +/** Finish the message and return the digest. + * @param ctx SHA1 context. + * @param digest 16 byte digest. + */ +PJ_DECL(void) pj_sha1_final(pj_sha1_context *ctx, pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SHA1_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h new file mode 100755 index 000000000..ebdeb6a8d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SRV_RESOLVER_H__ +#define __PJLIB_UTIL_SRV_RESOLVER_H__ + +/** + * @file srv_resolver.h + * @brief DNS SRV resolver + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS_SRV_RESOLVER DNS SRV Resolution Helper + * @ingroup PJ_DNS + * @{ + * + * \section PJ_DNS_SRV_RESOLVER_INTRO DNS SRV Resolution Helper + * + * This module provides an even higher layer of abstraction for the DNS + * resolution framework, to resolve DNS SRV names. + * + * The #pj_dns_srv_resolve() function will asynchronously resolve the server + * name into IP address(es) with a single function call. If the SRV name + * contains multiple names, then each will be resolved with individual + * DNS A resolution to get the IP addresses. Upon successful completion, + * application callback will be called with each IP address of the + * target selected based on the load-balancing and fail-over criteria + * below. + * + * When the resolver fails to resolve the name using DNS SRV resolution + * (for example when the DNS SRV record is not present in the DNS server), + * the resolver will fallback to using DNS A record resolution to resolve + * the name. + * + * \subsection PJ_DNS_SRV_RESOLVER_FAILOVER_LOADBALANCE Load-Balancing and Fail-Over + * + * When multiple targets are returned in the DNS SRV response, server entries + * are selected based on the following rule (which is described in RFC 2782): + * - targets will be sorted based on the priority first. + * - for targets with the same priority, #pj_dns_srv_resolve() will select + * only one target according to its weight. To select this one target, + * the function associates running-sum for all targets, and generates + * a random number between zero and the total running-sum (inclusive). + * The target selected is the first target with running-sum greater than + * or equal to this random number. + * + * The above procedure will select one target for each priority, allowing + * application to fail-over to the next target when the previous target fails. + * These targets are returned in the #pj_dns_srv_record structure + * argument of the callback. + * + * \section PJ_DNS_SRV_RESOLVER_REFERENCE Reference + * + * Reference: + * - RFC 2782: + * A DNS RR for specifying the location of services (DNS SRV) + */ + +/** + * Flags to be specified when starting the DNS SRV query. + */ +typedef enum pj_dns_srv_option { + /** + * Specify if the resolver should fallback with DNS A + * resolution when the SRV resolution fails. This option may + * be specified together with PJ_DNS_SRV_FALLBACK_AAAA to + * make the resolver fallback to both DNS A and DNS AAAA + * resolutions if SRV resolution fails. + */ + PJ_DNS_SRV_FALLBACK_A = 1, + + /** + * Specify if the resolver should fallback with DNS AAAA + * resolution when the SRV resolution fails. This option may + * be specified together with PJ_DNS_SRV_FALLBACK_AAAA to + * make the resolver fallback to both DNS A and DNS AAAA + * resolutions if SRV resolution fails. + */ + PJ_DNS_SRV_FALLBACK_AAAA = 2, + + /** + * Specify if the resolver should try to resolve with DNS AAAA + * resolution of each targets in the DNS SRV record. If this + * option is not specified, the SRV resolver will query the + * DNS A record for the target instead. + */ + PJ_DNS_SRV_RESOLVE_AAAA = 4, + + /** + * Specify if the resolver should try to resolve with DNS AAAA + * resolution only (i.e: without DNS A resolution) for each targets + * in the DNS SRV record. + */ + PJ_DNS_SRV_RESOLVE_AAAA_ONLY = 8 + +} pj_dns_srv_option; + +/** + * This structure represents DNS SRV records as the result of DNS SRV + * resolution using #pj_dns_srv_resolve(). + */ +typedef struct pj_dns_srv_record { + /** Number of address records. */ + unsigned count; + + /** Address records. */ + struct { + /** Server priority (the lower the higher the priority). */ + unsigned priority; + + /** Server weight (the higher the more load it can handle). */ + unsigned weight; + + /** Port number. */ + pj_uint16_t port; + + /** The host address. */ + pj_dns_addr_record server; + + } entry[PJ_DNS_SRV_MAX_ADDR]; + +} pj_dns_srv_record; + +/** Opaque declaration for DNS SRV query */ +typedef struct pj_dns_srv_async_query pj_dns_srv_async_query; + +/** + * Type of callback function to receive notification from the resolver + * when the resolution process completes. + */ +typedef void pj_dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec); + +/** + * Start DNS SRV resolution for the specified name. The full name of the + * entry will be concatenated from \a res_name and \a domain_name fragments. + * + * @param domain_name The domain name part of the name. + * @param res_name The full service name, including the transport name + * and with all the leading underscore characters and + * ending dot (e.g. "_sip._udp.", "_stun._udp."). + * @param def_port The port number to be assigned to the resolved address + * when the DNS SRV resolution fails and the name is + * resolved with DNS A resolution. + * @param pool Memory pool used to allocate memory for the query. + * @param resolver The resolver instance. + * @param option Option flags, which can be constructed from + * #pj_dns_srv_option bitmask. Note that this argument + * was called "fallback_a" in pjsip version 0.8.0 and + * older, but the new option should be backward + * compatible with existing applications. If application + * specifies PJ_TRUE as "fallback_a" value, it will + * correspond to PJ_DNS_SRV_FALLBACK_A option. + * @param token Arbitrary data to be associated with this query when + * the calback is called. + * @param cb Pointer to callback function to receive the + * notification when the resolution process completes. + * @param p_query Optional pointer to receive the query object, if one + * was started. If this pointer is specified, a NULL may + * be returned if response cache is available immediately. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_dns_srv_resolve(const pj_str_t *domain_name, const pj_str_t *res_name, unsigned def_port, pj_pool_t *pool, + pj_dns_resolver *resolver, unsigned option, void *token, pj_dns_srv_resolver_cb *cb, + pj_dns_srv_async_query **p_query); + +/** + * Cancel an outstanding DNS SRV query. + * + * @param query The pending asynchronous query to be cancelled. + * @param notify If non-zero, the callback will be called with failure + * status to notify that the query has been cancelled. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query, pj_bool_t notify); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SRV_RESOLVER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h new file mode 100755 index 000000000..764f2f454 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_STRING_H__ +#define __PJLIB_UTIL_STRING_H__ + +/** + * @file string.h + * @brief More string functions. + */ + +#include +#include + +/** + * @defgroup PJLIB_UTIL_STRING String Escaping Utilities + * @ingroup PJLIB_TEXT + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Unescape string. If source string does not contain any escaped + * characters, the function would simply return the original string. + * Otherwise a new string will be allocated. + * + * @param pool Pool to allocate the string. + * @param src Source string to unescape. + * + * @return String with no escaped characters. + */ +PJ_DECL(pj_str_t) pj_str_unescape(pj_pool_t *pool, const pj_str_t *src); + +/** + * Unescape string to destination. + * + * @param dst Target string. + * @param src Source string. + * + * @return Target string. + */ +PJ_DECL(pj_str_t *) pj_strcpy_unescape(pj_str_t *dst, const pj_str_t *src); + +/** + * Copy string to destination while escaping reserved characters, up to + * the specified maximum length. + * + * @param dst Target string. + * @param src Source string. + * @param max Maximum length to copy to target string. + * @param unres Unreserved characters, which are allowed to appear + * unescaped. + * + * @return The target string if all characters have been copied + * successfully, or NULL if there's not enough buffer to + * escape the strings. + */ +PJ_DECL(pj_str_t *) pj_strncpy_escape(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max, const pj_cis_t *unres); + +/** + * Copy string to destination while escaping reserved characters, up to + * the specified maximum length. + * + * @param dst Target string. + * @param src Source string. + * @param max Maximum length to copy to target string. + * @param unres Unreserved characters, which are allowed to appear + * unescaped. + * + * @return The length of the destination, or -1 if there's not + * enough buffer. + */ +PJ_DECL(pj_ssize_t) pj_strncpy2_escape(char *dst, const pj_str_t *src, pj_ssize_t max, const pj_cis_t *unres); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJLIB_UTIL_STRING_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h new file mode 100755 index 000000000..7514453c5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSTUN_H__ +#define __PJSTUN_H__ + +/** + * @file stun_simple.h + * @brief STUN client. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * This enumeration describes STUN message types. + */ +typedef enum pjstun_msg_type { + PJSTUN_BINDING_REQUEST = 0x0001, /**< Binding request */ + PJSTUN_BINDING_RESPONSE = 0x0101, /**< Binding response */ + PJSTUN_BINDING_ERROR_RESPONSE = 0x0111, /**< Binding error */ + PJSTUN_SHARED_SECRET_REQUEST = 0x0002, /**< Secret request */ + PJSTUN_SHARED_SECRET_RESPONSE = 0x0102, /**< Secret response */ + PJSTUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112 /**< Secret error */ +} pjstun_msg_type; + +/** + * This enumeration describes STUN attribute types. + */ +typedef enum pjstun_attr_type { + PJSTUN_ATTR_MAPPED_ADDR = 1, + PJSTUN_ATTR_RESPONSE_ADDR, + PJSTUN_ATTR_CHANGE_REQUEST, + PJSTUN_ATTR_SOURCE_ADDR, + PJSTUN_ATTR_CHANGED_ADDR, + PJSTUN_ATTR_USERNAME, + PJSTUN_ATTR_PASSWORD, + PJSTUN_ATTR_MESSAGE_INTEGRITY, + PJSTUN_ATTR_ERROR_CODE, + PJSTUN_ATTR_UNKNOWN_ATTRIBUTES, + PJSTUN_ATTR_REFLECTED_FROM, + PJSTUN_ATTR_XOR_MAPPED_ADDR = 0x0020 +} pjstun_attr_type; + +/* + * This structre describes STUN message header. + */ +typedef struct pjstun_msg_hdr { + pj_uint16_t type; + pj_uint16_t length; + pj_uint32_t tsx[4]; +} pjstun_msg_hdr; + +/* + * This structre describes STUN attribute header. + */ +typedef struct pjstun_attr_hdr { + pj_uint16_t type; + pj_uint16_t length; +} pjstun_attr_hdr; + +/* + * This structre describes STUN MAPPED-ADDR attribute. + */ +typedef struct pjstun_mapped_addr_attr { + pjstun_attr_hdr hdr; + pj_uint8_t ignored; + pj_uint8_t family; + pj_uint16_t port; + pj_uint32_t addr; +} pjstun_mapped_addr_attr; + +typedef pjstun_mapped_addr_attr pjstun_response_addr_attr; +typedef pjstun_mapped_addr_attr pjstun_changed_addr_attr; +typedef pjstun_mapped_addr_attr pjstun_src_addr_attr; +typedef pjstun_mapped_addr_attr pjstun_reflected_form_attr; + +typedef struct pjstun_change_request_attr { + pjstun_attr_hdr hdr; + pj_uint32_t value; +} pjstun_change_request_attr; + +typedef struct pjstun_username_attr { + pjstun_attr_hdr hdr; + pj_uint32_t value[1]; +} pjstun_username_attr; + +typedef pjstun_username_attr pjstun_password_attr; + +typedef struct pjstun_error_code_attr { + pjstun_attr_hdr hdr; + pj_uint16_t ignored; + pj_uint8_t err_class; + pj_uint8_t number; + char reason[4]; +} pjstun_error_code_attr; + +typedef struct pjstun_msg { + pjstun_msg_hdr *hdr; + int attr_count; + pjstun_attr_hdr *attr[PJSTUN_MAX_ATTR]; +} pjstun_msg; + +/* STUN message API (stun.c). */ + +PJ_DECL(pj_status_t) +pjstun_create_bind_req(pj_pool_t *pool, void **msg, pj_size_t *len, pj_uint32_t id_hi, pj_uint32_t id_lo); +PJ_DECL(pj_status_t) pjstun_parse_msg(void *buf, pj_size_t len, pjstun_msg *msg); +PJ_DECL(void *) pjstun_msg_find_attr(pjstun_msg *msg, pjstun_attr_type t); + +/** + * @defgroup PJLIB_UTIL_STUN_CLIENT Simple STUN Helper + * @ingroup PJ_PROTOCOLS + * @brief A simple and small footprint STUN resolution helper + * @{ + * + * This is the older implementation of STUN client, with only one function + * provided (pjstun_get_mapped_addr()) to retrieve the public IP address + * of multiple sockets. + */ + +/** + * This is the main function to request the mapped address of local sockets + * to multiple STUN servers. This function is able to find the mapped + * addresses of multiple sockets simultaneously, and for each socket, two + * requests will be sent to two different STUN servers to see if both servers + * get the same public address for the same socket. (Note that application can + * specify the same address for the two servers, but still two requests will + * be sent for each server). + * + * This function will perform necessary retransmissions of the requests if + * response is not received within a predetermined period. When all responses + * have been received, the function will compare the mapped addresses returned + * by the servers, and when both are equal, the address will be returned in + * \a mapped_addr argument. + * + * @param pf The pool factory where memory will be allocated from. + * @param sock_cnt Number of sockets in the socket array. + * @param sock Array of local UDP sockets which public addresses are + * to be queried from the STUN servers. + * @param srv1 Host name or IP address string of the first STUN + * server. + * @param port1 The port number of the first STUN server. + * @param srv2 Host name or IP address string of the second STUN + * server. + * @param port2 The port number of the second STUN server. + * @param mapped_addr Array to receive the mapped public address of the local + * UDP sockets, when the function returns PJ_SUCCESS. + * + * @return This functions returns PJ_SUCCESS if responses are + * received from all servers AND all servers returned the + * same mapped public address. Otherwise this function may + * return one of the following error codes: + * - PJLIB_UTIL_ESTUNNOTRESPOND: no respons from servers. + * - PJLIB_UTIL_ESTUNSYMMETRIC: different mapped addresses + * are returned by servers. + * - etc. + * + */ +PJ_DECL(pj_status_t) +pjstun_get_mapped_addr(pj_pool_factory *pf, int sock_cnt, pj_sock_t sock[], const pj_str_t *srv1, int port1, + const pj_str_t *srv2, int port2, pj_sockaddr_in mapped_addr[]); + +/* + * This structre describes configurable setting for requesting mapped address. + */ +typedef struct pjstun_setting { + /** + * Specifies whether STUN request generated by old STUN library should + * insert magic cookie (specified in RFC 5389) in the transaction ID. + */ + pj_bool_t use_stun2; + + /** + * Address family of the STUN servers. + */ + int af; + + /** + * Host name or IP address string of the first STUN server. + */ + pj_str_t srv1; + + /** + * The port number of the first STUN server. + */ + int port1; + + /** + * Host name or IP address string of the second STUN server. + */ + pj_str_t srv2; + + /** + * The port number of the second STUN server. + */ + int port2; + +} pjstun_setting; + +/** + * Another version of mapped address resolution of local sockets to multiple + * STUN servers configured in #pjstun_setting. This function is able to find + * the mapped addresses of multiple sockets simultaneously, and for each + * socket, two requests will be sent to two different STUN servers to see if + * both servers get the same public address for the same socket. (Note that + * application can specify the same address for the two servers, but still + * two requests will be sent for each server). + * + * This function will perform necessary retransmissions of the requests if + * response is not received within a predetermined period. When all responses + * have been received, the function will compare the mapped addresses returned + * by the servers, and when both are equal, the address will be returned in + * \a mapped_addr argument. + * + * @param pf The pool factory where memory will be allocated from. + * @param opt The STUN settings. + * @param sock_cnt Number of sockets in the socket array. + * @param sock Array of local UDP sockets which public addresses are + * to be queried from the STUN servers. + * @param mapped_addr Array to receive the mapped public address of the local + * UDP sockets, when the function returns PJ_SUCCESS. + * + * @return This functions returns PJ_SUCCESS if responses are + * received from all servers AND all servers returned the + * same mapped public address. Otherwise this function may + * return one of the following error codes: + * - PJLIB_UTIL_ESTUNNOTRESPOND: no respons from servers. + * - PJLIB_UTIL_ESTUNSYMMETRIC: different mapped addresses + * are returned by servers. + * - etc. + * + */ +PJ_DECL(pj_status_t) +pjstun_get_mapped_addr2(pj_pool_factory *pf, const pjstun_setting *opt, int sock_cnt, pj_sock_t sock[], + pj_sockaddr_in mapped_addr[]); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJSTUN_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h new file mode 100755 index 000000000..e406d88cf --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_TYPES_H__ +#define __PJLIB_UTIL_TYPES_H__ + +/** + * @file types.h + * @brief PJLIB-UTIL types. + */ + +#include +#include + +/** + * @defgroup PJLIB_UTIL_BASE Base + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Initialize PJLIB UTIL (defined in errno.c) + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjlib_util_init(void); + +PJ_END_DECL + +/** + * @} + */ + +/** + * @defgroup PJLIB_TEXT Text and String Manipulation + */ + +/** + * @defgroup PJ_PROTOCOLS Protocols + */ + +/** + * @defgroup PJ_FILE_FMT File Formats + */ + +/** + * @mainpage PJLIB-UTIL + * + * \n + * \n + * \n + * This is the documentation of PJLIB-UTIL, an auxiliary library providing + * adjunct functions to PJLIB. + * + * Please go to the Table of Contents page + * for list of modules. + * + * + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + */ + +#endif /* __PJLIB_UTIL_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h new file mode 100755 index 000000000..72b41e843 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_XML_H__ +#define __PJ_XML_H__ + +/** + * @file xml.h + * @brief PJLIB XML Parser/Helper. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_TINY_XML Mini/Tiny XML Parser/Helper + * @ingroup PJ_FILE_FMT + * @{ + */ + +/** Typedef for XML attribute. */ +typedef struct pj_xml_attr pj_xml_attr; + +/** Typedef for XML nodes. */ +typedef struct pj_xml_node pj_xml_node; + +/** This structure declares XML attribute. */ +struct pj_xml_attr { + PJ_DECL_LIST_MEMBER(pj_xml_attr); /**< Standard list elements. */ + pj_str_t name; /**< Attribute name. */ + pj_str_t value; /**< Attribute value. */ +}; + +/** This structure describes XML node head inside XML node structure. + */ +typedef struct pj_xml_node_head { + PJ_DECL_LIST_MEMBER(pj_xml_node); /**< Standard list elements. */ +} pj_xml_node_head; + +/** This structure describes XML node. */ +struct pj_xml_node { + PJ_DECL_LIST_MEMBER(pj_xml_node); /**< List @a prev and @a next member */ + pj_str_t name; /**< Node name. */ + pj_xml_attr attr_head; /**< Attribute list. */ + pj_xml_node_head node_head; /**< Node list. */ + pj_str_t content; /**< Node content. */ +}; + +/** + * Parse XML message into XML document with a single root node. The parser + * is capable of parsing XML processing instruction construct ("next is the starting point. + * @param name Node name to find. + * + * @return XML node found or NULL. + */ +PJ_DECL(pj_xml_node *) pj_xml_find_next_node(const pj_xml_node *parent, const pj_xml_node *node, const pj_str_t *name); + +/** + * Recursively find the first node with the specified name in the child nodes + * and their children. + * + * @param parent Parent node. + * @param name Node name to find. + * + * @return XML node found or NULL. + */ +PJ_DECL(pj_xml_node *) pj_xml_find_node_rec(const pj_xml_node *parent, const pj_str_t *name); + +/** + * Find first attribute within a node with the specified name and optional + * value. + * + * @param node XML Node. + * @param name Attribute name to find. + * @param value Optional value to match. + * + * @return XML attribute found, or NULL. + */ +PJ_DECL(pj_xml_attr *) pj_xml_find_attr(const pj_xml_node *node, const pj_str_t *name, const pj_str_t *value); + +/** + * Find a direct child node with the specified name and match the function. + * + * @param parent Parent node. + * @param name Optional name. If this is NULL, the name will not be + * matched. + * @param data Data to be passed to matching function. + * @param match Optional matching function. + * + * @return The first matched node, or NULL. + */ +PJ_DECL(pj_xml_node *) +pj_xml_find(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)); + +/** + * Recursively find a child node with the specified name and match the + * function. + * + * @param parent Parent node. + * @param name Optional name. If this is NULL, the name will not be + * matched. + * @param data Data to be passed to matching function. + * @param match Optional matching function. + * + * @return The first matched node, or NULL. + */ +PJ_DECL(pj_xml_node *) +pj_xml_find_rec(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_XML_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c new file mode 100755 index 000000000..10e417ee5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#define INV -1 +#define PADDING '=' + +static const char base64_char[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + +static int base256_char(char c) +{ + if (c >= 'A' && c <= 'Z') + return (c - 'A'); + else if (c >= 'a' && c <= 'z') + return (c - 'a' + 26); + else if (c >= '0' && c <= '9') + return (c - '0' + 52); + else if (c == '+') + return (62); + else if (c == '/') + return (63); + else { + /* It *may* happen on bad input, so this is not a good idea. + * pj_assert(!"Should not happen as '=' should have been filtered"); + */ + return INV; + } +} + +static void base256to64(pj_uint8_t c1, pj_uint8_t c2, pj_uint8_t c3, int padding, char *output) +{ + *output++ = base64_char[c1 >> 2]; + *output++ = base64_char[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)]; + switch (padding) { + case 0: + *output++ = base64_char[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)]; + *output = base64_char[c3 & 0x3F]; + break; + case 1: + *output++ = base64_char[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)]; + *output = PADDING; + break; + case 2: + default: + *output++ = PADDING; + *output = PADDING; + break; + } +} + +PJ_DEF(pj_status_t) pj_base64_encode(const pj_uint8_t *input, int in_len, char *output, int *out_len) +{ + const pj_uint8_t *pi = input; + pj_uint8_t c1, c2, c3; + int i = 0; + char *po = output; + + PJ_ASSERT_RETURN(input && output && out_len, PJ_EINVAL); + PJ_ASSERT_RETURN(*out_len >= PJ_BASE256_TO_BASE64_LEN(in_len), PJ_ETOOSMALL); + + while (i < in_len) { + c1 = *pi++; + ++i; + + if (i == in_len) { + base256to64(c1, 0, 0, 2, po); + po += 4; + break; + } else { + c2 = *pi++; + ++i; + + if (i == in_len) { + base256to64(c1, c2, 0, 1, po); + po += 4; + break; + } else { + c3 = *pi++; + ++i; + base256to64(c1, c2, c3, 0, po); + } + } + + po += 4; + } + + *out_len = (int)(po - output); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_base64_decode(const pj_str_t *input, pj_uint8_t *out, int *out_len) +{ + const char *buf; + int len; + int i, j, k; + int c[4]; + + PJ_ASSERT_RETURN(input && out && out_len, PJ_EINVAL); + + buf = input->ptr; + len = (int)input->slen; + while (len && buf[len - 1] == '=') + --len; + + PJ_ASSERT_RETURN(*out_len >= PJ_BASE64_TO_BASE256_LEN(len), PJ_ETOOSMALL); + + for (i = 0, j = 0; i < len;) { + /* Fill up c, silently ignoring invalid characters */ + for (k = 0; k < 4 && i < len; ++k) { + do { + c[k] = base256_char(buf[i++]); + } while (c[k] == INV && i < len); + } + + if (k < 4) { + if (k > 1) { + out[j++] = (pj_uint8_t)((c[0] << 2) | ((c[1] & 0x30) >> 4)); + if (k > 2) { + out[j++] = (pj_uint8_t)(((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2)); + } + } + break; + } + + out[j++] = (pj_uint8_t)((c[0] << 2) | ((c[1] & 0x30) >> 4)); + out[j++] = (pj_uint8_t)(((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2)); + out[j++] = (pj_uint8_t)(((c[2] & 0x03) << 6) | (c[3] & 0x3F)); + } + + pj_assert(j <= *out_len); + *out_len = j; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c new file mode 100755 index 000000000..e441f38e0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c @@ -0,0 +1,1225 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMD_HASH_TABLE_SIZE 63 /* Hash table size */ + +#define CLI_CMD_CHANGE_LOG 30000 +#define CLI_CMD_EXIT 30001 + +#define MAX_CMD_HASH_NAME_LENGTH PJ_CLI_MAX_CMDBUF +#define MAX_CMD_ID_LENGTH 16 + +#if 1 +/* Enable some tracing */ +#define THIS_FILE "cli.c" +#define TRACE_(arg) PJ_LOG(3, arg) +#else +#define TRACE_(arg) +#endif + +/** + * This structure describes the full specification of a CLI command. A CLI + * command mainly consists of the name of the command, zero or more arguments, + * and a callback function to be called to execute the command. + * + * Application can create this specification by forming an XML document and + * calling pj_cli_add_cmd_from_xml() to instantiate the spec. A sample XML + * document containing a command spec is as follows: + * + \verbatim + + + + + + \endverbatim + */ +struct pj_cli_cmd_spec { + /** + * To make list of child cmds. + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); + + /** + * Command ID assigned to this command by the application during command + * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is + * a command group and it can't be executed. + */ + pj_cli_cmd_id id; + + /** + * The command name. + */ + pj_str_t name; + + /** + * The full description of the command. + */ + pj_str_t desc; + + /** + * Number of optional shortcuts + */ + unsigned sc_cnt; + + /** + * Optional array of shortcuts, if any. Shortcut is a short name version + * of the command. If the command doesn't have any shortcuts, this + * will be initialized to NULL. + */ + pj_str_t *sc; + + /** + * The command handler, to be executed when a command matching this command + * specification is invoked by the end user. The value may be NULL if this + * is a command group. + */ + pj_cli_cmd_handler handler; + + /** + * Number of arguments. + */ + unsigned arg_cnt; + + /** + * Array of arguments. + */ + pj_cli_arg_spec *arg; + + /** + * Child commands, if any. A command will only have subcommands if it is + * a group. If the command doesn't have subcommands, this field will be + * initialized with NULL. + */ + pj_cli_cmd_spec *sub_cmd; +}; + +struct pj_cli_t { + pj_pool_t *pool; /* Pool to allocate memory from */ + pj_cli_cfg cfg; /* CLI configuration */ + pj_cli_cmd_spec root; /* Root of command tree structure */ + pj_cli_front_end fe_head; /* List of front-ends */ + pj_hash_table_t *cmd_name_hash; /* Command name hash table, this will + include the command name and shortcut + as hash key */ + pj_hash_table_t *cmd_id_hash; /* Command id hash table */ + + pj_bool_t is_quitting; + pj_bool_t is_restarting; +}; + +/** + * Reserved command id constants. + */ +typedef enum pj_cli_std_cmd_id { + /** + * Constant to indicate an invalid command id. + */ + PJ_CLI_INVALID_CMD_ID = -1, + + /** + * A special command id to indicate that a command id denotes + * a command group. + */ + PJ_CLI_CMD_ID_GROUP = -2 + +} pj_cli_std_cmd_id; + +/** + * This describes the type of an argument (pj_cli_arg_spec). + */ +typedef enum pj_cli_arg_type { + /** + * Unformatted string. + */ + PJ_CLI_ARG_TEXT, + + /** + * An integral number. + */ + PJ_CLI_ARG_INT, + + /** + * Choice type + */ + PJ_CLI_ARG_CHOICE + +} pj_cli_arg_type; + +struct arg_type { + const pj_str_t msg; +} arg_type[3] = {{{"Text", 4}}, {{"Int", 3}}, {{"Choice", 6}}}; + +/** + * This structure describe the specification of a command argument. + */ +struct pj_cli_arg_spec { + /** + * Argument id + */ + pj_cli_arg_id id; + + /** + * Argument name. + */ + pj_str_t name; + + /** + * Helpful description of the argument. This text will be used when + * displaying help texts for the command/argument. + */ + pj_str_t desc; + + /** + * Argument type, which will be used for rendering the argument and + * to perform basic validation against an input value. + */ + pj_cli_arg_type type; + + /** + * Argument status + */ + pj_bool_t optional; + + /** + * Validate choice values + */ + pj_bool_t validate; + + /** + * Static Choice Values count + */ + unsigned stat_choice_cnt; + + /** + * Static Choice Values + */ + pj_cli_arg_choice_val *stat_choice_val; + + /** + * Argument callback to get the valid values + */ + pj_cli_get_dyn_choice get_dyn_choice; +}; + +/** + * This describe the parse mode of the command line + */ +typedef enum pj_cli_parse_mode { + PARSE_NONE, + PARSE_COMPLETION, /* Complete the command line */ + PARSE_NEXT_AVAIL, /* Find the next available command line */ + PARSE_EXEC /* Exec the command line */ +} pj_cli_parse_mode; + +/** + * This is used to get the matched command/argument from the + * command/argument structure. + * + * @param sess The session on which the command is execute on. + * @param cmd The active command. + * @param cmd_val The command value to match. + * @param argc The number of argument that the + * current active command have. + * @param pool The memory pool to allocate memory. + * @param get_cmd Set true to search matching command from sub command. + * @param parse_mode The parse mode. + * @param p_cmd The command that mathes the command value. + * @param info The output information containing any hints for + * matching command/arg. + * @return This function return the status of the + * matching process.Please see the return value + * of pj_cli_sess_parse() for possible return values. + */ +static pj_status_t get_available_cmds(pj_cli_sess *sess, pj_cli_cmd_spec *cmd, pj_str_t *cmd_val, unsigned argc, + pj_pool_t *pool, pj_bool_t get_cmd, pj_cli_parse_mode parse_mode, + pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info); + +PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) +{ + return cmd->id; +} + +PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + pj_strset2(¶m->name, ""); +} + +PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + param->err_pos = -1; + param->cmd_id = PJ_CLI_INVALID_CMD_ID; + param->cmd_ret = PJ_SUCCESS; +} + +PJ_DEF(void) pj_cli_write_log(pj_cli_t *cli, int level, const char *buffer, int len) +{ + struct pj_cli_front_end *fe; + + pj_assert(cli); + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + if (fe->op && fe->op->on_write_log) + (*fe->op->on_write_log)(fe, level, buffer, len); + fe = fe->next; + } +} + +PJ_DEF(void) pj_cli_sess_write_msg(pj_cli_sess *sess, const char *buffer, pj_size_t len) +{ + struct pj_cli_front_end *fe; + + pj_assert(sess); + + fe = sess->fe; + if (fe->op && fe->op->on_write_log) + (*fe->op->on_write_log)(fe, 0, buffer, len); +} + +/* Command handler */ +static pj_status_t cmd_handler(pj_cli_cmd_val *cval) +{ + unsigned level; + + switch (cval->cmd->id) { + case CLI_CMD_CHANGE_LOG: + level = pj_strtoul(&cval->argv[1]); + if (!level && cval->argv[1].slen > 0 && (cval->argv[1].ptr[0] < '0' || cval->argv[1].ptr[0] > '9')) { + return PJ_CLI_EINVARG; + } + cval->sess->log_level = level; + return PJ_SUCCESS; + case CLI_CMD_EXIT: + pj_cli_sess_end_session(cval->sess); + return PJ_CLI_EEXIT; + default: + return PJ_SUCCESS; + } +} + +PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, pj_cli_t **p_cli) +{ + pj_pool_t *pool; + pj_cli_t *cli; + unsigned i; + + /* This is an example of the command structure */ + char *cmd_xmls[] = { + "" + " " + "", + "" + "", + }; + + PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL); + + pool = pj_pool_create(cfg->pf, "cli", PJ_CLI_POOL_SIZE, PJ_CLI_POOL_INC, NULL); + if (!pool) + return PJ_ENOMEM; + cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t); + + pj_memcpy(&cli->cfg, cfg, sizeof(*cfg)); + cli->pool = pool; + pj_list_init(&cli->fe_head); + + cli->cmd_name_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); + cli->cmd_id_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); + + cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec); + pj_list_init(cli->root.sub_cmd); + + /* Register some standard commands. */ + for (i = 0; i < sizeof(cmd_xmls) / sizeof(cmd_xmls[0]); i++) { + pj_str_t xml = pj_str(cmd_xmls[i]); + + if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, &cmd_handler, NULL, NULL) != PJ_SUCCESS) { + TRACE_((THIS_FILE, "Failed to add command #%d", i)); + } + } + + *p_cli = cli; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_cli_cfg *) pj_cli_get_param(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, NULL); + + return &cli->cfg; +} + +PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli, pj_cli_front_end *fe) +{ + pj_list_push_back(&cli->fe_head, fe); +} + +PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, pj_bool_t restart) +{ + pj_cli_front_end *fe; + + pj_assert(cli); + if (cli->is_quitting) + return; + + cli->is_quitting = PJ_TRUE; + cli->is_restarting = restart; + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + if (fe->op && fe->op->on_quit) + (*fe->op->on_quit)(fe, req); + fe = fe->next; + } +} + +PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, PJ_FALSE); + + return cli->is_quitting; +} + +PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, PJ_FALSE); + + return cli->is_restarting; +} + +PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli) +{ + pj_cli_front_end *fe; + + if (!cli) + return; + + if (!pj_cli_is_quitting(cli)) + pj_cli_quit(cli, NULL, PJ_FALSE); + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + pj_list_erase(fe); + if (fe->op && fe->op->on_destroy) + (*fe->op->on_destroy)(fe); + + fe = cli->fe_head.next; + } + cli->is_quitting = PJ_FALSE; + pj_pool_release(cli->pool); +} + +PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) +{ + pj_assert(sess); + + if (sess->op && sess->op->destroy) + (*sess->op->destroy)(sess); +} + +/* Syntax error handler for parser. */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); +} + +/* Get the command from the command hash */ +static pj_cli_cmd_spec *get_cmd_name(const pj_cli_t *cli, const pj_cli_cmd_spec *group, const pj_str_t *cmd) +{ + pj_str_t cmd_val; + char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH]; + + cmd_val.ptr = cmd_ptr; + cmd_val.slen = 0; + + if (group) { + char cmd_str[MAX_CMD_ID_LENGTH]; + pj_ansi_sprintf(cmd_str, "%d", group->id); + pj_strcat2(&cmd_val, cmd_str); + } + pj_strcat(&cmd_val, cmd); + return (pj_cli_cmd_spec *)pj_hash_get(cli->cmd_name_hash, cmd_val.ptr, (unsigned)cmd_val.slen, NULL); +} + +/* Add command to the command hash */ +static void add_cmd_name(pj_cli_t *cli, pj_cli_cmd_spec *group, pj_cli_cmd_spec *cmd, pj_str_t *cmd_name) +{ + pj_str_t cmd_val; + pj_str_t add_cmd; + char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH]; + + cmd_val.ptr = cmd_ptr; + cmd_val.slen = 0; + + if (group) { + char cmd_str[MAX_CMD_ID_LENGTH]; + pj_ansi_sprintf(cmd_str, "%d", group->id); + pj_strcat2(&cmd_val, cmd_str); + } + pj_strcat(&cmd_val, cmd_name); + pj_strdup(cli->pool, &add_cmd, &cmd_val); + + pj_hash_set(cli->pool, cli->cmd_name_hash, cmd_val.ptr, (unsigned)cmd_val.slen, 0, cmd); +} + +/** + * This method is to parse and add the choice type + * argument values to command structure. + **/ +static pj_status_t add_choice_node(pj_cli_t *cli, pj_xml_node *xml_node, pj_cli_arg_spec *arg, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_node *choice_node; + pj_xml_node *sub_node; + pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL]; + pj_status_t status = PJ_SUCCESS; + + sub_node = xml_node; + arg->type = PJ_CLI_ARG_CHOICE; + arg->get_dyn_choice = get_choice; + + choice_node = sub_node->node_head.next; + while (choice_node != (pj_xml_node *)&sub_node->node_head) { + pj_xml_attr *choice_attr; + unsigned *stat_cnt = &arg->stat_choice_cnt; + pj_cli_arg_choice_val *choice_val = &choice_values[*stat_cnt]; + pj_bzero(choice_val, sizeof(*choice_val)); + + choice_attr = choice_node->attr_head.next; + while (choice_attr != &choice_node->attr_head) { + if (!pj_stricmp2(&choice_attr->name, "value")) { + pj_strassign(&choice_val->value, &choice_attr->value); + } else if (!pj_stricmp2(&choice_attr->name, "desc")) { + pj_strassign(&choice_val->desc, &choice_attr->value); + } + choice_attr = choice_attr->next; + } + if (++(*stat_cnt) >= PJ_CLI_MAX_CHOICE_VAL) + break; + choice_node = choice_node->next; + } + if (arg->stat_choice_cnt > 0) { + unsigned i; + + arg->stat_choice_val = + (pj_cli_arg_choice_val *)pj_pool_zalloc(cli->pool, arg->stat_choice_cnt * sizeof(pj_cli_arg_choice_val)); + + for (i = 0; i < arg->stat_choice_cnt; i++) { + pj_strdup(cli->pool, &arg->stat_choice_val[i].value, &choice_values[i].value); + pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, &choice_values[i].desc); + } + } + return status; +} + +/** + * This method is to parse and add the argument attribute to command structure. + **/ +static pj_status_t add_arg_node(pj_cli_t *cli, pj_xml_node *xml_node, pj_cli_cmd_spec *cmd, pj_cli_arg_spec *arg, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_attr *attr; + pj_status_t status = PJ_SUCCESS; + pj_xml_node *sub_node = xml_node; + + if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) + return PJ_CLI_ETOOMANYARGS; + + pj_bzero(arg, sizeof(*arg)); + attr = sub_node->attr_head.next; + arg->optional = PJ_FALSE; + arg->validate = PJ_TRUE; + while (attr != &sub_node->attr_head) { + if (!pj_stricmp2(&attr->name, "name")) { + pj_strassign(&arg->name, &attr->value); + } else if (!pj_stricmp2(&attr->name, "id")) { + arg->id = pj_strtol(&attr->value); + } else if (!pj_stricmp2(&attr->name, "type")) { + if (!pj_stricmp2(&attr->value, "text")) { + arg->type = PJ_CLI_ARG_TEXT; + } else if (!pj_stricmp2(&attr->value, "int")) { + arg->type = PJ_CLI_ARG_INT; + } else if (!pj_stricmp2(&attr->value, "choice")) { + /* Get choice value */ + add_choice_node(cli, xml_node, arg, get_choice); + } + } else if (!pj_stricmp2(&attr->name, "desc")) { + pj_strassign(&arg->desc, &attr->value); + } else if (!pj_stricmp2(&attr->name, "optional")) { + if (!pj_strcmp2(&attr->value, "1")) { + arg->optional = PJ_TRUE; + } + } else if (!pj_stricmp2(&attr->name, "validate")) { + if (!pj_strcmp2(&attr->value, "1")) { + arg->validate = PJ_TRUE; + } else { + arg->validate = PJ_FALSE; + } + } + attr = attr->next; + } + cmd->arg_cnt++; + return status; +} + +/** + * This method is to parse and add the command attribute to command structure. + **/ +static pj_status_t add_cmd_node(pj_cli_t *cli, pj_cli_cmd_spec *group, pj_xml_node *xml_node, + pj_cli_cmd_handler handler, pj_cli_cmd_spec **p_cmd, pj_cli_get_dyn_choice get_choice) +{ + pj_xml_node *root = xml_node; + pj_xml_attr *attr; + pj_xml_node *sub_node; + pj_cli_cmd_spec *cmd; + pj_cli_arg_spec args[PJ_CLI_MAX_ARGS]; + pj_str_t sc[PJ_CLI_MAX_SHORTCUTS]; + pj_status_t status = PJ_SUCCESS; + + if (pj_stricmp2(&root->name, "CMD")) + return PJ_EINVAL; + + /* Initialize the command spec */ + cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec); + + /* Get the command attributes */ + attr = root->attr_head.next; + while (attr != &root->attr_head) { + if (!pj_stricmp2(&attr->name, "name")) { + pj_strltrim(&attr->value); + if (!attr->value.slen || (get_cmd_name(cli, group, &attr->value))) { + return PJ_CLI_EBADNAME; + } + pj_strdup(cli->pool, &cmd->name, &attr->value); + } else if (!pj_stricmp2(&attr->name, "id")) { + pj_bool_t is_valid = PJ_FALSE; + if (attr->value.slen) { + pj_cli_cmd_id cmd_id = pj_strtol(&attr->value); + if (!pj_hash_get(cli->cmd_id_hash, &cmd_id, sizeof(pj_cli_cmd_id), NULL)) + is_valid = PJ_TRUE; + } + if (!is_valid) + return PJ_CLI_EBADID; + cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value); + } else if (!pj_stricmp2(&attr->name, "sc")) { + pj_scanner scanner; + pj_str_t str; + + PJ_USE_EXCEPTION; + + /* The buffer passed to the scanner is not NULL terminated, + * but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_ch(&scanner, ',', &str); + pj_strrtrim(&str); + if (!pj_scan_is_eof(&scanner)) + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + if (!str.slen) + continue; + + if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) { + PJ_THROW(PJ_CLI_ETOOMANYARGS); + } + /* Check whether the shortcuts are already used */ + if (get_cmd_name(cli, &cli->root, &str)) { + PJ_THROW(PJ_CLI_EBADNAME); + } + + pj_strassign(&sc[cmd->sc_cnt++], &str); + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return (PJ_GET_EXCEPTION()); + } + PJ_END; + + pj_scan_fini(&scanner); + + } else if (!pj_stricmp2(&attr->name, "desc")) { + pj_strdup(cli->pool, &cmd->desc, &attr->value); + } + attr = attr->next; + } + + /* Get the command childs/arguments */ + sub_node = root->node_head.next; + while (sub_node != (pj_xml_node *)&root->node_head) { + if (!pj_stricmp2(&sub_node->name, "CMD")) { + status = add_cmd_node(cli, cmd, sub_node, handler, NULL, get_choice); + if (status != PJ_SUCCESS) + return status; + } else if (!pj_stricmp2(&sub_node->name, "ARG")) { + /* Get argument attribute */ + status = add_arg_node(cli, sub_node, cmd, &args[cmd->arg_cnt], get_choice); + + if (status != PJ_SUCCESS) + return status; + } + sub_node = sub_node->next; + } + + if (!cmd->name.slen) + return PJ_CLI_EBADNAME; + + if (!cmd->id) + return PJ_CLI_EBADID; + + if (cmd->arg_cnt) { + unsigned i; + + cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt * sizeof(pj_cli_arg_spec)); + + for (i = 0; i < cmd->arg_cnt; i++) { + pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name); + pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); + cmd->arg[i].id = args[i].id; + cmd->arg[i].type = args[i].type; + cmd->arg[i].optional = args[i].optional; + cmd->arg[i].validate = args[i].validate; + cmd->arg[i].get_dyn_choice = args[i].get_dyn_choice; + cmd->arg[i].stat_choice_cnt = args[i].stat_choice_cnt; + cmd->arg[i].stat_choice_val = args[i].stat_choice_val; + } + } + + if (cmd->sc_cnt) { + unsigned i; + + cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt * sizeof(pj_str_t)); + for (i = 0; i < cmd->sc_cnt; i++) { + pj_strdup(cli->pool, &cmd->sc[i], &sc[i]); + /** Add shortcut to root command **/ + add_cmd_name(cli, &cli->root, cmd, &sc[i]); + } + } + + add_cmd_name(cli, group, cmd, &cmd->name); + pj_hash_set(cli->pool, cli->cmd_id_hash, &cmd->id, sizeof(pj_cli_cmd_id), 0, cmd); + + cmd->handler = handler; + + if (group) { + if (!group->sub_cmd) { + group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); + pj_list_init(group->sub_cmd); + } + pj_list_push_back(group->sub_cmd, cmd); + } else { + pj_list_push_back(cli->root.sub_cmd, cmd); + } + + if (p_cmd) + *p_cmd = cmd; + + return status; +} + +PJ_DEF(pj_status_t) +pj_cli_add_cmd_from_xml(pj_cli_t *cli, pj_cli_cmd_spec *group, const pj_str_t *xml, pj_cli_cmd_handler handler, + pj_cli_cmd_spec **p_cmd, pj_cli_get_dyn_choice get_choice) +{ + pj_pool_t *pool; + pj_xml_node *root; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); + + /* Parse the xml */ + pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + root = pj_xml_parse(pool, xml->ptr, xml->slen); + if (!root) { + TRACE_((THIS_FILE, "Error: unable to parse XML")); + pj_pool_release(pool); + return PJ_CLI_EBADXML; + } + status = add_cmd_node(cli, group, root, handler, p_cmd, get_choice); + pj_pool_release(pool); + return status; +} + +PJ_DEF(pj_status_t) +pj_cli_sess_parse(pj_cli_sess *sess, char *cmdline, pj_cli_cmd_val *val, pj_pool_t *pool, pj_cli_exec_info *info) +{ + pj_scanner scanner; + pj_str_t str; + pj_size_t len; + pj_cli_cmd_spec *cmd; + pj_cli_cmd_spec *next_cmd; + pj_status_t status = PJ_SUCCESS; + pj_cli_parse_mode parse_mode = PARSE_NONE; + + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); + + PJ_UNUSED_ARG(pool); + + str.slen = 0; + pj_cli_exec_info_default(info); + + /* Set the parse mode based on the latest char. + * And NULL terminate the buffer for the scanner. + */ + len = pj_ansi_strlen(cmdline); + if (len > 0 && ((cmdline[len - 1] == '\r') || (cmdline[len - 1] == '\n'))) { + cmdline[--len] = 0; + parse_mode = PARSE_EXEC; + } else if (len > 0 && (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) { + cmdline[--len] = 0; + if (len == 0) { + parse_mode = PARSE_NEXT_AVAIL; + } else { + if (cmdline[len - 1] == ' ') + parse_mode = PARSE_NEXT_AVAIL; + else + parse_mode = PARSE_COMPLETION; + } + } + val->argc = 0; + info->err_pos = 0; + cmd = &sess->fe->cli->root; + if (len > 0) { + pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + PJ_TRY + { + val->argc = 0; + while (!pj_scan_is_eof(&scanner)) { + info->err_pos = (int)(scanner.curptr - scanner.begin); + if (*scanner.curptr == '\'' || *scanner.curptr == '"' || *scanner.curptr == '{') { + pj_scan_get_quotes(&scanner, "'\"{", "'\"}", 3, &str); + /* Remove the quotes */ + str.ptr++; + str.slen -= 2; + } else { + pj_scan_get_until_chr(&scanner, " \t\r\n", &str); + } + ++val->argc; + if (val->argc == PJ_CLI_MAX_ARGS) + PJ_THROW(PJ_CLI_ETOOMANYARGS); + + status = get_available_cmds(sess, cmd, &str, val->argc - 1, pool, PJ_TRUE, parse_mode, &next_cmd, info); + + if (status != PJ_SUCCESS) + PJ_THROW(status); + + if (cmd != next_cmd) { + /* Found new command, set it as the active command */ + cmd = next_cmd; + val->argc = 1; + val->cmd = cmd; + } + if (parse_mode == PARSE_EXEC) + pj_strassign(&val->argv[val->argc - 1], &info->hint->name); + else + pj_strassign(&val->argv[val->argc - 1], &str); + } + if (!pj_scan_is_eof(&scanner)) + PJ_THROW(PJ_CLI_EINVARG); + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + } + + if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) { + /* If exec mode, just get the matching argument */ + status = get_available_cmds(sess, cmd, NULL, val->argc, pool, (parse_mode == PARSE_NEXT_AVAIL), parse_mode, + NULL, info); + if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) { + pj_str_t data = pj_str(cmdline); + pj_strrtrim(&data); + data.ptr[data.slen] = ' '; + data.ptr[data.slen + 1] = 0; + + info->err_pos = (int)pj_ansi_strlen(cmdline); + } + } + + val->sess = sess; + return status; +} + +PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, char *cmdline, pj_pool_t *pool, pj_cli_exec_info *info) +{ + pj_cli_cmd_val val; + pj_status_t status; + pj_cli_exec_info einfo; + pj_str_t cmd; + + PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL); + + PJ_UNUSED_ARG(pool); + + cmd.ptr = cmdline; + cmd.slen = pj_ansi_strlen(cmdline); + + if (pj_strtrim(&cmd)->slen == 0) + return PJ_SUCCESS; + + if (!info) + info = &einfo; + + status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); + if (status != PJ_SUCCESS) + return status; + + if ((val.argc > 0) && (val.cmd->handler)) { + info->cmd_ret = (*val.cmd->handler)(&val); + if (info->cmd_ret == PJ_CLI_EINVARG || info->cmd_ret == PJ_CLI_EEXIT) { + return info->cmd_ret; + } + } + + return PJ_SUCCESS; +} + +static pj_bool_t hint_inserted(const pj_str_t *name, const pj_str_t *desc, const pj_str_t *type, pj_cli_exec_info *info) +{ + unsigned i; + for (i = 0; i < info->hint_cnt; ++i) { + pj_cli_hint_info *hint = &info->hint[i]; + if ((!pj_strncmp(&hint->name, name, hint->name.slen)) && (!pj_strncmp(&hint->desc, desc, hint->desc.slen)) && + (!pj_strncmp(&hint->type, type, hint->type.slen))) { + return PJ_TRUE; + } + } + return PJ_FALSE; +} + +/** This will insert new hint with the option to check for the same + previous entry **/ +static pj_status_t insert_new_hint2(pj_pool_t *pool, pj_bool_t unique_insert, const pj_str_t *name, + const pj_str_t *desc, const pj_str_t *type, pj_cli_exec_info *info) +{ + pj_cli_hint_info *hint; + PJ_ASSERT_RETURN(pool && info, PJ_EINVAL); + PJ_ASSERT_RETURN((info->hint_cnt < PJ_CLI_MAX_HINTS), PJ_EINVAL); + + if ((unique_insert) && (hint_inserted(name, desc, type, info))) + return PJ_SUCCESS; + + hint = &info->hint[info->hint_cnt]; + + pj_strdup(pool, &hint->name, name); + + if (desc && (desc->slen > 0)) { + pj_strdup(pool, &hint->desc, desc); + } else { + hint->desc.slen = 0; + } + + if (type && (type->slen > 0)) { + pj_strdup(pool, &hint->type, type); + } else { + hint->type.slen = 0; + } + + ++info->hint_cnt; + return PJ_SUCCESS; +} + +/** This will insert new hint without checking for the same previous entry **/ +static pj_status_t insert_new_hint(pj_pool_t *pool, const pj_str_t *name, const pj_str_t *desc, const pj_str_t *type, + pj_cli_exec_info *info) +{ + return insert_new_hint2(pool, PJ_FALSE, name, desc, type, info); +} + +/** This will get a complete/exact match of a command from the cmd hash **/ +static pj_status_t get_comp_match_cmds(const pj_cli_t *cli, const pj_cli_cmd_spec *group, const pj_str_t *cmd_val, + pj_pool_t *pool, pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info) +{ + pj_cli_cmd_spec *cmd; + PJ_ASSERT_RETURN(cli && group && cmd_val && pool && info, PJ_EINVAL); + + cmd = get_cmd_name(cli, group, cmd_val); + + if (cmd) { + pj_status_t status; + status = insert_new_hint(pool, cmd_val, &cmd->desc, NULL, info); + + if (status != PJ_SUCCESS) + return status; + + *p_cmd = cmd; + } + + return PJ_SUCCESS; +} + +/** This method will search for any shortcut with pattern match to the input + command. This method should be called from root command, as shortcut could + only be executed from root **/ +static pj_status_t get_pattern_match_shortcut(const pj_cli_t *cli, const pj_str_t *cmd_val, pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info) +{ + pj_hash_iterator_t it_buf, *it; + pj_status_t status; + PJ_ASSERT_RETURN(cli && pool && cmd_val && info, PJ_EINVAL); + + it = pj_hash_first(cli->cmd_name_hash, &it_buf); + while (it) { + unsigned i; + pj_cli_cmd_spec *cmd = (pj_cli_cmd_spec *)pj_hash_this(cli->cmd_name_hash, it); + + PJ_ASSERT_RETURN(cmd, PJ_EINVAL); + + for (i = 0; i < cmd->sc_cnt; ++i) { + static const pj_str_t SHORTCUT = {"SC", 2}; + pj_str_t *sc = &cmd->sc[i]; + PJ_ASSERT_RETURN(sc, PJ_EINVAL); + + if (!pj_strncmp(sc, cmd_val, cmd_val->slen)) { + /** Unique hints needed because cmd hash contain command name + and shortcut referencing to the same command **/ + status = insert_new_hint2(pool, PJ_TRUE, sc, &cmd->desc, &SHORTCUT, info); + if (status != PJ_SUCCESS) + return status; + + if (p_cmd) + *p_cmd = cmd; + } + } + + it = pj_hash_next(cli->cmd_name_hash, it); + } + + return PJ_SUCCESS; +} + +/** This method will search a pattern match to the input command from the child + command list of the current/active command. **/ +static pj_status_t get_pattern_match_cmds(pj_cli_cmd_spec *cmd, const pj_str_t *cmd_val, pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, pj_cli_parse_mode parse_mode, pj_cli_exec_info *info) +{ + pj_status_t status; + PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL); + + /* Get matching command */ + if (cmd->sub_cmd) { + pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next; + while (child_cmd != cmd->sub_cmd) { + pj_bool_t found = PJ_FALSE; + if (!pj_strncmp(&child_cmd->name, cmd_val, cmd_val->slen)) { + status = insert_new_hint(pool, &child_cmd->name, &child_cmd->desc, NULL, info); + if (status != PJ_SUCCESS) + return status; + + found = PJ_TRUE; + } + if (found) { + if (parse_mode == PARSE_NEXT_AVAIL) { + /** Only insert shortcut on next available commands mode **/ + unsigned i; + for (i = 0; i < child_cmd->sc_cnt; ++i) { + static const pj_str_t SHORTCUT = {"SC", 2}; + pj_str_t *sc = &child_cmd->sc[i]; + PJ_ASSERT_RETURN(sc, PJ_EINVAL); + + status = insert_new_hint(pool, sc, &child_cmd->desc, &SHORTCUT, info); + if (status != PJ_SUCCESS) + return status; + } + } + + if (p_cmd) + *p_cmd = child_cmd; + } + child_cmd = child_cmd->next; + } + } + return PJ_SUCCESS; +} + +/** This will match the arguments passed to the command with the argument list + of the specified command list. **/ +static pj_status_t get_match_args(pj_cli_sess *sess, pj_cli_cmd_spec *cmd, const pj_str_t *cmd_val, unsigned argc, + pj_pool_t *pool, pj_cli_parse_mode parse_mode, pj_cli_exec_info *info) +{ + pj_cli_arg_spec *arg; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL); + + if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) { + if (cmd_val->slen > 0) + return PJ_CLI_ETOOMANYARGS; + else + return PJ_SUCCESS; + } + + if (cmd->arg_cnt > 0) { + arg = &cmd->arg[argc - 1]; + PJ_ASSERT_RETURN(arg, PJ_EINVAL); + if (arg->type == PJ_CLI_ARG_CHOICE) { + unsigned j; + + if ((parse_mode == PARSE_EXEC) && (!arg->validate)) { + /* If no validation needed, then insert the values */ + status = insert_new_hint(pool, cmd_val, NULL, NULL, info); + return status; + } + + for (j = 0; j < arg->stat_choice_cnt; ++j) { + pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j]; + + PJ_ASSERT_RETURN(choice_val, PJ_EINVAL); + + if (!pj_strncmp(&choice_val->value, cmd_val, cmd_val->slen)) { + status = insert_new_hint(pool, &choice_val->value, &choice_val->desc, + &arg_type[PJ_CLI_ARG_CHOICE].msg, info); + if (status != PJ_SUCCESS) + return status; + } + } + if (arg->get_dyn_choice) { + pj_cli_dyn_choice_param dyn_choice_param; + static pj_str_t choice_str = {"choice", 6}; + + /* Get the dynamic choice values */ + dyn_choice_param.sess = sess; + dyn_choice_param.cmd = cmd; + dyn_choice_param.arg_id = arg->id; + dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL; + dyn_choice_param.pool = pool; + dyn_choice_param.cnt = 0; + + (*arg->get_dyn_choice)(&dyn_choice_param); + for (j = 0; j < dyn_choice_param.cnt; ++j) { + pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j]; + if (!pj_strncmp(&choice->value, cmd_val, cmd_val->slen)) { + pj_strassign(&info->hint[info->hint_cnt].name, &choice->value); + pj_strassign(&info->hint[info->hint_cnt].type, &choice_str); + pj_strassign(&info->hint[info->hint_cnt].desc, &choice->desc); + if ((++info->hint_cnt) >= PJ_CLI_MAX_HINTS) + break; + } + } + if ((info->hint_cnt == 0) && (!arg->optional)) + return PJ_CLI_EMISSINGARG; + } + } else { + if (cmd_val->slen == 0) { + if (info->hint_cnt == 0) { + if (!((parse_mode == PARSE_EXEC) && (arg->optional))) { + /* For exec mode,no need to insert hint if optional */ + status = insert_new_hint(pool, &arg->name, &arg->desc, &arg_type[arg->type].msg, info); + if (status != PJ_SUCCESS) + return status; + } + if (!arg->optional) + return PJ_CLI_EMISSINGARG; + } + } else { + return insert_new_hint(pool, cmd_val, NULL, NULL, info); + } + } + } + return status; +} + +/** This will check for a match of the commands/arguments input. Any match + will be inserted to the hint data. **/ +static pj_status_t get_available_cmds(pj_cli_sess *sess, pj_cli_cmd_spec *cmd, pj_str_t *cmd_val, unsigned argc, + pj_pool_t *pool, pj_bool_t get_cmd, pj_cli_parse_mode parse_mode, + pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info) +{ + pj_status_t status = PJ_SUCCESS; + pj_str_t *prefix; + pj_str_t EMPTY_STR = {NULL, 0}; + + prefix = cmd_val ? (pj_strtrim(cmd_val)) : (&EMPTY_STR); + + info->hint_cnt = 0; + + if (get_cmd) { + status = get_comp_match_cmds(sess->fe->cli, cmd, prefix, pool, p_cmd, info); + if (status != PJ_SUCCESS) + return status; + + /** If exact match found, then no need to search for pattern match **/ + if (info->hint_cnt == 0) { + if ((parse_mode != PARSE_NEXT_AVAIL) && (cmd == &sess->fe->cli->root)) { + /** Pattern match for shortcut needed on root command only **/ + status = get_pattern_match_shortcut(sess->fe->cli, prefix, pool, p_cmd, info); + + if (status != PJ_SUCCESS) + return status; + } + + status = get_pattern_match_cmds(cmd, prefix, pool, p_cmd, parse_mode, info); + } + + if (status != PJ_SUCCESS) + return status; + } + + if (argc > 0) + status = get_match_args(sess, cmd, prefix, argc, pool, parse_mode, info); + + if (status == PJ_SUCCESS) { + if (prefix->slen > 0) { + /** If a command entered is not a an empty command, and have a + single match in the command list then it is a valid command **/ + if (info->hint_cnt == 0) { + status = PJ_CLI_EINVARG; + } else if (info->hint_cnt > 1) { + status = PJ_CLI_EAMBIGUOUS; + } + } else { + if (info->hint_cnt > 0) + status = PJ_CLI_EAMBIGUOUS; + } + } + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c new file mode 100755 index 000000000..bb2bb1eb1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * This specify the state of output character parsing. + */ +typedef enum out_parse_state { OP_NORMAL, OP_TYPE, OP_SHORTCUT, OP_CHOICE } out_parse_state; + +struct cli_console_fe { + pj_cli_front_end base; + pj_pool_t *pool; + pj_cli_sess *sess; + pj_thread_t *input_thread; + pj_bool_t thread_quit; + pj_sem_t *thread_sem; + pj_cli_console_cfg cfg; + + struct async_input_t { + char *buf; + unsigned maxlen; + pj_sem_t *sem; + } input; +}; + +static void console_write_log(pj_cli_front_end *fe, int level, const char *data, pj_size_t len) +{ + struct cli_console_fe *cfe = (struct cli_console_fe *)fe; + + if (cfe->sess->log_level > level) + printf("%.*s", (int)len, data); +} + +static void console_quit(pj_cli_front_end *fe, pj_cli_sess *req) +{ + struct cli_console_fe *cfe = (struct cli_console_fe *)fe; + + PJ_UNUSED_ARG(req); + + pj_assert(cfe); + if (cfe->input_thread) { + cfe->thread_quit = PJ_TRUE; + pj_sem_post(cfe->input.sem); + pj_sem_post(cfe->thread_sem); + } +} + +static void console_destroy(pj_cli_front_end *fe) +{ + struct cli_console_fe *cfe = (struct cli_console_fe *)fe; + + pj_assert(cfe); + console_quit(fe, NULL); + + if (cfe->input_thread) + pj_thread_join(cfe->input_thread); + + if (cfe->input_thread) { + pj_thread_destroy(cfe->input_thread); + cfe->input_thread = NULL; + } + + pj_sem_destroy(cfe->thread_sem); + pj_sem_destroy(cfe->input.sem); + pj_pool_release(cfe->pool); +} + +PJ_DEF(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param) +{ + pj_assert(param); + + param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL; + param->prompt_str.slen = 0; + param->quit_command.slen = 0; +} + +PJ_DEF(pj_status_t) +pj_cli_console_create(pj_cli_t *cli, const pj_cli_console_cfg *param, pj_cli_sess **p_sess, pj_cli_front_end **p_fe) +{ + pj_cli_sess *sess; + struct cli_console_fe *fe; + pj_cli_console_cfg cfg; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(cli && p_sess, PJ_EINVAL); + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "console_fe", PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess); + fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe); + + if (!param) { + pj_cli_console_cfg_default(&cfg); + param = &cfg; + } + sess->fe = &fe->base; + sess->log_level = param->log_level; + sess->op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op); + fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op); + fe->base.cli = cli; + fe->base.type = PJ_CLI_CONSOLE_FRONT_END; + fe->base.op->on_write_log = &console_write_log; + fe->base.op->on_quit = &console_quit; + fe->base.op->on_destroy = &console_destroy; + fe->pool = pool; + fe->sess = sess; + status = pj_sem_create(pool, "console_fe", 0, 1, &fe->thread_sem); + if (status != PJ_SUCCESS) + return status; + + status = pj_sem_create(pool, "console_fe", 0, 1, &fe->input.sem); + if (status != PJ_SUCCESS) + return status; + + pj_cli_register_front_end(cli, &fe->base); + if (param->prompt_str.slen == 0) { + pj_str_t prompt_sign = pj_str(">>> "); + fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, prompt_sign.slen + 1); + pj_strcpy(&fe->cfg.prompt_str, &prompt_sign); + } else { + fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, param->prompt_str.slen + 1); + pj_strcpy(&fe->cfg.prompt_str, ¶m->prompt_str); + } + fe->cfg.prompt_str.ptr[fe->cfg.prompt_str.slen] = 0; + + if (param->quit_command.slen) + pj_strdup(fe->pool, &fe->cfg.quit_command, ¶m->quit_command); + + *p_sess = sess; + if (p_fe) + *p_fe = &fe->base; + + return PJ_SUCCESS; +} + +static void send_prompt_str(pj_cli_sess *sess) +{ + pj_str_t send_data; + char data_str[128]; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + pj_strcat(&send_data, &fe->cfg.prompt_str); + send_data.ptr[send_data.slen] = 0; + + printf("%s", send_data.ptr); +} + +static void send_err_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, const pj_str_t *msg, pj_bool_t with_return) +{ + pj_str_t send_data; + char data_str[256]; + pj_size_t len; + unsigned i; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, msg); + pj_strcat(&send_data, &fe->cfg.prompt_str); + + send_data.ptr[send_data.slen] = 0; + printf("%s", send_data.ptr); +} + +static void send_inv_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return) +{ + static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; + send_err_arg(sess, info, &ERR_MSG, with_return); +} + +static void send_too_many_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return) +{ + static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; + send_err_arg(sess, info, &ERR_MSG, with_return); +} + +static void send_hint_arg(pj_str_t *send_data, const pj_str_t *desc, pj_ssize_t cmd_len, pj_ssize_t max_len) +{ + if ((desc) && (desc->slen > 0)) { + int j; + + for (j = 0; j < (max_len - cmd_len); ++j) { + pj_strcat2(send_data, " "); + } + pj_strcat2(send_data, " "); + pj_strcat(send_data, desc); + send_data->ptr[send_data->slen] = 0; + printf("%s", send_data->ptr); + send_data->slen = 0; + } +} + +static void send_ambi_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return) +{ + unsigned i; + pj_size_t len; + pj_str_t send_data; + char data[1028]; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + const pj_cli_hint_info *hint = info->hint; + out_parse_state parse_state = OP_NORMAL; + pj_ssize_t max_length = 0; + pj_ssize_t cmd_length = 0; + static const pj_str_t sc_type = {"sc", 2}; + static const pj_str_t choice_type = {"choice", 6}; + send_data.ptr = data; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + /* Get the max length of the command name */ + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + cmd_length += (hint[i].name.slen + 3); + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + + if (cmd_length > max_length) { + max_length = cmd_length; + } + } + + cmd_length = 0; + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + parse_state = OP_SHORTCUT; + } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) { + parse_state = OP_CHOICE; + } else { + parse_state = OP_TYPE; + } + } else { + parse_state = OP_NORMAL; + } + + if (parse_state != OP_SHORTCUT) { + pj_strcat2(&send_data, "\r\n "); + cmd_length = hint[i].name.slen; + } + + switch (parse_state) { + case OP_CHOICE: + pj_strcat2(&send_data, "["); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, "]"); + break; + case OP_TYPE: + pj_strcat2(&send_data, "<"); + pj_strcat(&send_data, &hint[i].type); + pj_strcat2(&send_data, ">"); + break; + case OP_SHORTCUT: + /* Format : "Command | sc | description" */ + { + cmd_length += hint[i].name.slen; + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + pj_strcat2(&send_data, " | "); + cmd_length += 3; + } else { + pj_strcat2(&send_data, "\r\n "); + } + pj_strcat(&send_data, &hint[i].name); + } + break; + default: + pj_strcat(&send_data, &hint[i].name); + break; + } + + if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || ((i + 1) >= info->hint_cnt) || + (pj_strncmp(&hint[i].desc, &hint[i + 1].desc, hint[i].desc.slen))) { + /* Add description info */ + send_hint_arg(&send_data, &hint[i].desc, cmd_length, max_length); + + cmd_length = 0; + } + } + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, &fe->cfg.prompt_str); + send_data.ptr[send_data.slen] = 0; + printf("%s", send_data.ptr); +} + +static pj_bool_t handle_hint(pj_cli_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_cmd_val *cmd_val; + pj_cli_exec_info info; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + char *recv_buf = fe->input.buf; + pj_cli_t *cli = sess->fe->cli; + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_hint", PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + + cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); + + status = pj_cli_sess_parse(sess, recv_buf, cmd_val, pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_TRUE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_TRUE); + break; + case PJ_CLI_EMISSINGARG: + case PJ_CLI_EAMBIGUOUS: + send_ambi_arg(sess, &info, PJ_TRUE); + break; + case PJ_SUCCESS: + if (info.hint_cnt > 0) { + /* Compelete command */ + send_ambi_arg(sess, &info, PJ_TRUE); + } else { + retval = PJ_FALSE; + } + break; + } + + pj_pool_release(pool); + return retval; +} + +static pj_bool_t handle_exec(pj_cli_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_exec_info info; + pj_cli_t *cli = sess->fe->cli; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + char *recv_buf = fe->input.buf; + + printf("\r\n"); + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_exec", PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + + status = pj_cli_sess_exec(sess, recv_buf, pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_EAMBIGUOUS: + case PJ_CLI_EMISSINGARG: + send_ambi_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_EEXIT: + retval = PJ_FALSE; + break; + case PJ_SUCCESS: + send_prompt_str(sess); + break; + } + + pj_pool_release(pool); + return retval; +} + +static int readline_thread(void *p) +{ + struct cli_console_fe *fe = (struct cli_console_fe *)p; + + printf("%s", fe->cfg.prompt_str.ptr); + + while (!fe->thread_quit) { + pj_size_t input_len = 0; + pj_str_t input_str; + char *recv_buf = fe->input.buf; + pj_bool_t is_valid = PJ_TRUE; + + if (fgets(recv_buf, fe->input.maxlen, stdin) == NULL) { + /* + * Be friendly to users who redirect commands into + * program, when file ends, resume with kbd. + * If exit is desired end script with q for quit + */ + /* Reopen stdin/stdout/stderr to /dev/console */ +#if ((defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0)) && \ + (!defined(PJ_WIN32_WINCE) || PJ_WIN32_WINCE == 0) + if (freopen("CONIN$", "r", stdin) == NULL) { +#else + if (1) { +#endif + puts("Cannot switch back to console from file redirection"); + if (fe->cfg.quit_command.slen) { + pj_memcpy(recv_buf, fe->cfg.quit_command.ptr, fe->input.maxlen); + } + recv_buf[fe->cfg.quit_command.slen] = '\0'; + } else { + puts("Switched back to console from file redirection"); + continue; + } + } + + input_str.ptr = recv_buf; + input_str.slen = pj_ansi_strlen(recv_buf); + pj_strrtrim(&input_str); + recv_buf[input_str.slen] = '\n'; + recv_buf[input_str.slen + 1] = 0; + if (fe->thread_quit) { + break; + } + input_len = pj_ansi_strlen(fe->input.buf); + if ((input_len > 1) && (fe->input.buf[input_len - 2] == '?')) { + fe->input.buf[input_len - 1] = 0; + is_valid = handle_hint(fe->sess); + if (!is_valid) + printf("%s", fe->cfg.prompt_str.ptr); + } else { + is_valid = handle_exec(fe->sess); + } + + pj_sem_post(fe->input.sem); + pj_sem_wait(fe->thread_sem); + } + + return 0; +} + +PJ_DEF(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, char *buf, unsigned maxlen) +{ + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + fe->input.buf = buf; + fe->input.maxlen = maxlen; + + if (!fe->input_thread) { + pj_status_t status; + + status = pj_thread_create(fe->pool, NULL, &readline_thread, fe, 0, 0, &fe->input_thread); + if (status != PJ_SUCCESS) + return status; + } else { + /* Wake up readline thread */ + pj_sem_post(fe->thread_sem); + } + + pj_sem_wait(fe->input.sem); + + return (pj_cli_is_quitting(fe->base.cli) ? PJ_CLI_EEXIT : PJ_SUCCESS); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c new file mode 100755 index 000000000..3877c1200 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c @@ -0,0 +1,1859 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) || \ + (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0) + +/* Undefine EADDRINUSE first, we want it equal to WSAEADDRINUSE, + * while WinSDK 10 defines it to another value. + */ +#undef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE + +#endif + +#define CLI_TELNET_BUF_SIZE 256 + +#define CUT_MSG "<..data truncated..>\r\n" +#define MAX_CUT_MSG_LEN 25 + +#if 1 +/* Enable some tracing */ +#define THIS_FILE "cli_telnet.c" +#define TRACE_(arg) PJ_LOG(3, arg) +#else +#define TRACE_(arg) +#endif + +#define MAX_CLI_TELNET_OPTIONS 256 +/** Maximum retry on Telnet Restart **/ +#define MAX_RETRY_ON_TELNET_RESTART 100 +/** Minimum number of millisecond to wait before retrying to re-bind on + * telnet restart **/ +#define MIN_WAIT_ON_TELNET_RESTART 20 +/** Maximum number of millisecod to wait before retrying to re-bind on + * telnet restart **/ +#define MAX_WAIT_ON_TELNET_RESTART 1000 + +/** + * This specify the state for the telnet option negotiation. + */ +enum cli_telnet_option_states { + OPT_DISABLE, /* Option disable */ + OPT_ENABLE, /* Option enable */ + OPT_EXPECT_DISABLE, /* Already send disable req, expecting resp */ + OPT_EXPECT_ENABLE, /* Already send enable req, expecting resp */ + OPT_EXPECT_DISABLE_REV, /* Already send disable req, expecting resp, + * need to send enable req */ + OPT_EXPECT_ENABLE_REV /* Already send enable req, expecting resp, + * need to send disable req */ +}; + +/** + * This structure contains information for telnet session option negotiation. + * It contains the local/peer option config and the option negotiation state. + */ +typedef struct cli_telnet_sess_option { + /** + * Local setting for the option. + * Default: FALSE; + */ + pj_bool_t local_is_enable; + + /** + * Remote setting for the option. + * Default: FALSE; + */ + pj_bool_t peer_is_enable; + + /** + * Local state of the option negotiation. + */ + enum cli_telnet_option_states local_state; + + /** + * Remote state of the option negotiation. + */ + enum cli_telnet_option_states peer_state; +} cli_telnet_sess_option; + +/** + * This specify the state of input character parsing. + */ +typedef enum cmd_parse_state { + ST_NORMAL, + ST_CR, + ST_ESC, + ST_VT100, + ST_IAC, + ST_DO, + ST_DONT, + ST_WILL, + ST_WONT +} cmd_parse_state; + +typedef enum cli_telnet_command { + SUBNEGO_END = 240, /* End of subnegotiation parameters. */ + NOP = 241, /* No operation. */ + DATA_MARK = 242, /* Marker for NVT cleaning. */ + BREAK = 243, /* Indicates that the "break" key was hit. */ + INT_PROCESS = 244, /* Suspend, interrupt or abort the process. */ + ABORT_OUTPUT = 245, /* Abort output, abort output stream. */ + ARE_YOU_THERE = 246, /* Are you there. */ + ERASE_CHAR = 247, /* Erase character, erase the current char. */ + ERASE_LINE = 248, /* Erase line, erase the current line. */ + GO_AHEAD = 249, /* Go ahead, other end can transmit. */ + SUBNEGO_BEGIN = 250, /* Subnegotiation begin. */ + WILL = 251, /* Accept the use of option. */ + WONT = 252, /* Refuse the use of option. */ + DO = 253, /* Request to use option. */ + DONT = 254, /* Request to not use option. */ + IAC = 255 /* Interpret as command */ +} cli_telnet_command; + +enum cli_telnet_options { + TRANSMIT_BINARY = 0, /* Transmit Binary. */ + TERM_ECHO = 1, /* Echo. */ + RECONNECT = 2, /* Reconnection. */ + SUPPRESS_GA = 3, /* Suppress Go Aheah. */ + MESSAGE_SIZE_NEGO = 4, /* Approx Message Size Negotiation. */ + STATUS = 5, /* Status. */ + TIMING_MARK = 6, /* Timing Mark. */ + RTCE_OPTION = 7, /* Remote Controlled Trans and Echo. */ + OUTPUT_LINE_WIDTH = 8, /* Output Line Width. */ + OUTPUT_PAGE_SIZE = 9, /* Output Page Size. */ + CR_DISPOSITION = 10, /* Carriage-Return Disposition. */ + HORI_TABSTOPS = 11, /* Horizontal Tabstops. */ + HORI_TAB_DISPO = 12, /* Horizontal Tab Disposition. */ + FF_DISP0 = 13, /* Formfeed Disposition. */ + VERT_TABSTOPS = 14, /* Vertical Tabstops. */ + VERT_TAB_DISPO = 15, /* Vertical Tab Disposition. */ + LF_DISP0 = 16, /* Linefeed Disposition. */ + EXT_ASCII = 17, /* Extended ASCII. */ + LOGOUT = 18, /* Logout. */ + BYTE_MACRO = 19, /* Byte Macro. */ + DE_TERMINAL = 20, /* Data Entry Terminal. */ + SUPDUP_PROTO = 21, /* SUPDUP Protocol. */ + SUPDUP_OUTPUT = 22, /* SUPDUP Output. */ + SEND_LOC = 23, /* Send Location. */ + TERM_TYPE = 24, /* Terminal Type. */ + EOR = 25, /* End of Record. */ + TACACS_UID = 26, /* TACACS User Identification. */ + OUTPUT_MARKING = 27, /* Output Marking. */ + TTYLOC = 28, /* Terminal Location Number. */ + USE_3270_REGIME = 29, /* Telnet 3270 Regime. */ + USE_X3_PAD = 30, /* X.3 PAD. */ + WINDOW_SIZE = 31, /* Window Size. */ + TERM_SPEED = 32, /* Terminal Speed. */ + REM_FLOW_CONTROL = 33, /* Remote Flow Control. */ + LINE_MODE = 34, /* Linemode. */ + X_DISP_LOC = 35, /* X Display Location. */ + ENVIRONMENT = 36, /* Environment. */ + AUTH = 37, /* Authentication. */ + ENCRYPTION = 38, /* Encryption Option. */ + NEW_ENVIRONMENT = 39, /* New Environment. */ + TN_3270E = 40, /* TN3270E. */ + XAUTH = 41, /* XAUTH. */ + CHARSET = 42, /* CHARSET. */ + REM_SERIAL_PORT = 43, /* Telnet Remote Serial Port. */ + COM_PORT_CONTROL = 44, /* Com Port Control. */ + SUPP_LOCAL_ECHO = 45, /* Telnet Suppress Local Echo. */ + START_TLS = 46, /* Telnet Start TLS. */ + KERMIT = 47, /* KERMIT. */ + SEND_URL = 48, /* SEND-URL. */ + FWD_X = 49, /* FORWARD_X. */ + EXT_OPTIONS = 255 /* Extended-Options-List */ +}; + +enum terminal_cmd { + TC_ESC = 27, + TC_UP = 65, + TC_DOWN = 66, + TC_RIGHT = 67, + TC_LEFT = 68, + TC_END = 70, + TC_HOME = 72, + TC_CTRL_C = 3, + TC_CR = 13, + TC_BS = 8, + TC_TAB = 9, + TC_QM = 63, + TC_BELL = 7, + TC_DEL = 127 +}; + +/** + * This specify the state of output character parsing. + */ +typedef enum out_parse_state { OP_NORMAL, OP_TYPE, OP_SHORTCUT, OP_CHOICE } out_parse_state; + +/** + * This structure contains the command line shown to the user. + * The telnet also needs to maintain and manage command cursor position. + * Due to that reason, the insert/delete character process from buffer will + * consider its current cursor position. + */ +typedef struct telnet_recv_buf { + /** + * Buffer containing the characters, NULL terminated. + */ + unsigned char rbuf[PJ_CLI_MAX_CMDBUF]; + + /** + * Current length of the command line. + */ + unsigned len; + + /** + * Current cursor position. + */ + unsigned cur_pos; +} telnet_recv_buf; + +/** + * This structure contains the command history executed by user. + * Besides storing the command history, it is necessary to be able + * to browse it. + */ +typedef struct cmd_history { + PJ_DECL_LIST_MEMBER(struct cmd_history); + pj_str_t command; +} cmd_history; + +typedef struct cli_telnet_sess { + pj_cli_sess base; + pj_pool_t *pool; + pj_activesock_t *asock; + pj_bool_t authorized; + pj_ioqueue_op_key_t op_key; + pj_mutex_t *smutex; + cmd_parse_state parse_state; + cli_telnet_sess_option telnet_option[MAX_CLI_TELNET_OPTIONS]; + cmd_history *history; + cmd_history *active_history; + + telnet_recv_buf *rcmd; + unsigned char buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN]; + unsigned buf_len; +} cli_telnet_sess; + +typedef struct cli_telnet_fe { + pj_cli_front_end base; + pj_pool_t *pool; + pj_cli_telnet_cfg cfg; + pj_bool_t own_ioqueue; + pj_cli_sess sess_head; + + pj_activesock_t *asock; + pj_thread_t *worker_thread; + pj_bool_t is_quitting; + pj_mutex_t *mutex; +} cli_telnet_fe; + +/* Forward Declaration */ +static pj_status_t telnet_sess_send2(cli_telnet_sess *sess, const unsigned char *str, int len); + +static pj_status_t telnet_sess_send(cli_telnet_sess *sess, const pj_str_t *str); + +static pj_status_t telnet_start(cli_telnet_fe *fe); +static pj_status_t telnet_restart(cli_telnet_fe *tfe); + +/** + * Return the number of characters between the current cursor position + * to the end of line. + */ +static unsigned recv_buf_right_len(telnet_recv_buf *recv_buf) +{ + return (recv_buf->len - recv_buf->cur_pos); +} + +/** + * Insert character to the receive buffer. + */ +static pj_bool_t recv_buf_insert(telnet_recv_buf *recv_buf, unsigned char *data) +{ + if (recv_buf->len + 1 >= PJ_CLI_MAX_CMDBUF) { + return PJ_FALSE; + } else { + if (*data == '\t' || *data == '?' || *data == '\r') { + /* Always insert to the end of line */ + recv_buf->rbuf[recv_buf->len] = *data; + } else { + /* Insert based on the current cursor pos */ + unsigned cur_pos = recv_buf->cur_pos; + unsigned rlen = recv_buf_right_len(recv_buf); + if (rlen > 0) { + /* Shift right characters */ + pj_memmove(&recv_buf->rbuf[cur_pos + 1], &recv_buf->rbuf[cur_pos], rlen + 1); + } + recv_buf->rbuf[cur_pos] = *data; + } + ++recv_buf->cur_pos; + ++recv_buf->len; + recv_buf->rbuf[recv_buf->len] = 0; + } + return PJ_TRUE; +} + +/** + * Delete character on the previous cursor position of the receive buffer. + */ +static pj_bool_t recv_buf_backspace(telnet_recv_buf *recv_buf) +{ + if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) { + return PJ_FALSE; + } else { + unsigned rlen = recv_buf_right_len(recv_buf); + if (rlen) { + unsigned cur_pos = recv_buf->cur_pos; + /* Shift left characters */ + pj_memmove(&recv_buf->rbuf[cur_pos - 1], &recv_buf->rbuf[cur_pos], rlen); + } + --recv_buf->cur_pos; + --recv_buf->len; + recv_buf->rbuf[recv_buf->len] = 0; + } + return PJ_TRUE; +} + +static int compare_str(void *value, const pj_list_type *nd) +{ + cmd_history *node = (cmd_history *)nd; + return (pj_strcmp((pj_str_t *)value, &node->command)); +} + +/** + * Insert the command to history. If the entered command is not on the list, + * a new entry will be created. All entered command will be moved to + * the first entry of the history. + */ +static pj_status_t insert_history(cli_telnet_sess *sess, char *cmd_val) +{ + cmd_history *in_history; + pj_str_t cmd; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + cmd = pj_str(cmd_val); + pj_strtrim(&cmd); + if (cmd.slen == 0) + return PJ_SUCCESS; + + /* Find matching history */ + in_history = pj_list_search(sess->history, (void *)&cmd, compare_str); + if (!in_history) { + if (pj_list_size(sess->history) < PJ_CLI_MAX_CMD_HISTORY) { + char *data_history; + in_history = PJ_POOL_ZALLOC_T(sess->pool, cmd_history); + pj_list_init(in_history); + data_history = (char *)pj_pool_calloc(sess->pool, sizeof(char), PJ_CLI_MAX_CMDBUF); + in_history->command.ptr = data_history; + in_history->command.slen = 0; + } else { + /* Get the oldest history */ + in_history = sess->history->prev; + pj_list_erase(in_history); + } + pj_strncpy(&in_history->command, &cmd, PJ_CLI_MAX_CMDBUF); + } else { + pj_list_erase(in_history); + } + pj_list_push_front(sess->history, in_history); + sess->active_history = sess->history; + + return PJ_SUCCESS; +} + +/** + * Get the next or previous history of the shown/active history. + */ +static pj_str_t *get_prev_history(cli_telnet_sess *sess, pj_bool_t is_forward) +{ + pj_str_t *retval; + pj_size_t history_size; + cmd_history *node; + cmd_history *root; + + PJ_ASSERT_RETURN(sess, NULL); + + node = sess->active_history; + root = sess->history; + history_size = pj_list_size(sess->history); + + if (history_size == 0) { + return NULL; + } else { + if (is_forward) { + node = (node->next == root) ? node->next->next : node->next; + } else { + node = (node->prev == root) ? node->prev->prev : node->prev; + } + retval = &node->command; + sess->active_history = node; + } + return retval; +} + +/* + * This method is used to send option negotiation command. + * The commands dealing with option negotiation are + * three byte sequences, the third byte being the code for the option + * referenced - (RFC-854). + */ +static pj_bool_t send_telnet_cmd(cli_telnet_sess *sess, cli_telnet_command cmd, unsigned char option) +{ + unsigned char buf[3]; + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + buf[0] = IAC; + buf[1] = cmd; + buf[2] = option; + telnet_sess_send2(sess, buf, 3); + + return PJ_TRUE; +} + +/** + * This method will handle sending telnet's ENABLE option negotiation. + * For local option: send WILL. + * For remote option: send DO. + * This method also handle the state transition of the ENABLE + * negotiation process. + */ +static pj_bool_t send_enable_option(cli_telnet_sess *sess, pj_bool_t is_local, unsigned char option) +{ + cli_telnet_sess_option *sess_option; + enum cli_telnet_option_states *state; + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + sess_option = &sess->telnet_option[option]; + state = is_local ? (&sess_option->local_state) : (&sess_option->peer_state); + switch (*state) { + case OPT_ENABLE: + /* Ignore if already enabled */ + break; + case OPT_DISABLE: + *state = OPT_EXPECT_ENABLE; + send_telnet_cmd(sess, (is_local ? WILL : DO), option); + break; + case OPT_EXPECT_ENABLE: + *state = OPT_DISABLE; + break; + case OPT_EXPECT_DISABLE: + *state = OPT_EXPECT_DISABLE_REV; + break; + case OPT_EXPECT_ENABLE_REV: + *state = OPT_EXPECT_ENABLE; + break; + case OPT_EXPECT_DISABLE_REV: + *state = OPT_DISABLE; + break; + default: + return PJ_FALSE; + } + return PJ_TRUE; +} + +static pj_bool_t send_cmd_do(cli_telnet_sess *sess, unsigned char option) +{ + return send_enable_option(sess, PJ_FALSE, option); +} + +static pj_bool_t send_cmd_will(cli_telnet_sess *sess, unsigned char option) +{ + return send_enable_option(sess, PJ_TRUE, option); +} + +/** + * This method will handle receiving telnet's ENABLE option negotiation. + * This method also handle the state transition of the ENABLE + * negotiation process. + */ +static pj_bool_t receive_enable_option(cli_telnet_sess *sess, pj_bool_t is_local, unsigned char option) +{ + cli_telnet_sess_option *sess_opt; + enum cli_telnet_option_states *state; + pj_bool_t opt_ena; + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + sess_opt = &sess->telnet_option[option]; + state = is_local ? (&sess_opt->local_state) : (&sess_opt->peer_state); + opt_ena = is_local ? sess_opt->local_is_enable : sess_opt->peer_is_enable; + switch (*state) { + case OPT_ENABLE: + /* Ignore if already enabled */ + break; + case OPT_DISABLE: + if (opt_ena) { + *state = OPT_ENABLE; + send_telnet_cmd(sess, is_local ? WILL : DO, option); + } else { + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + } + break; + case OPT_EXPECT_ENABLE: + *state = OPT_ENABLE; + break; + case OPT_EXPECT_DISABLE: + *state = OPT_DISABLE; + break; + case OPT_EXPECT_ENABLE_REV: + *state = OPT_EXPECT_DISABLE; + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + break; + case OPT_EXPECT_DISABLE_REV: + *state = OPT_EXPECT_DISABLE; + break; + default: + return PJ_FALSE; + } + return PJ_TRUE; +} + +/** + * This method will handle receiving telnet's DISABLE option negotiation. + * This method also handle the state transition of the DISABLE + * negotiation process. + */ +static pj_bool_t receive_disable_option(cli_telnet_sess *sess, pj_bool_t is_local, unsigned char option) +{ + cli_telnet_sess_option *sess_opt; + enum cli_telnet_option_states *state; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + sess_opt = &sess->telnet_option[option]; + state = is_local ? (&sess_opt->local_state) : (&sess_opt->peer_state); + + switch (*state) { + case OPT_ENABLE: + /* Disabling option always need to be accepted */ + *state = OPT_DISABLE; + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + break; + case OPT_DISABLE: + /* Ignore if already enabled */ + break; + case OPT_EXPECT_ENABLE: + case OPT_EXPECT_DISABLE: + *state = OPT_DISABLE; + break; + case OPT_EXPECT_ENABLE_REV: + *state = OPT_DISABLE; + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + break; + case OPT_EXPECT_DISABLE_REV: + *state = OPT_EXPECT_ENABLE; + send_telnet_cmd(sess, is_local ? WILL : DO, option); + break; + default: + return PJ_FALSE; + } + return PJ_TRUE; +} + +static pj_bool_t receive_do(cli_telnet_sess *sess, unsigned char option) +{ + return receive_enable_option(sess, PJ_TRUE, option); +} + +static pj_bool_t receive_dont(cli_telnet_sess *sess, unsigned char option) +{ + return receive_disable_option(sess, PJ_TRUE, option); +} + +static pj_bool_t receive_will(cli_telnet_sess *sess, unsigned char option) +{ + return receive_enable_option(sess, PJ_FALSE, option); +} + +static pj_bool_t receive_wont(cli_telnet_sess *sess, unsigned char option) +{ + return receive_disable_option(sess, PJ_FALSE, option); +} + +static void set_local_option(cli_telnet_sess *sess, unsigned char option, pj_bool_t enable) +{ + sess->telnet_option[option].local_is_enable = enable; +} + +static void set_peer_option(cli_telnet_sess *sess, unsigned char option, pj_bool_t enable) +{ + sess->telnet_option[option].peer_is_enable = enable; +} + +static pj_bool_t is_local_option_state_ena(cli_telnet_sess *sess, unsigned char option) +{ + return (sess->telnet_option[option].local_state == OPT_ENABLE); +} + +static void send_return_key(cli_telnet_sess *sess) +{ + telnet_sess_send2(sess, (unsigned char *)"\r\n", 2); +} + +static void send_bell(cli_telnet_sess *sess) +{ + static const unsigned char bell = 0x07; + telnet_sess_send2(sess, &bell, 1); +} + +static void send_prompt_str(cli_telnet_sess *sess) +{ + pj_str_t send_data; + char data_str[128]; + cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + pj_strcat(&send_data, &fe->cfg.prompt_str); + + telnet_sess_send(sess, &send_data); +} + +/* + * This method is used to send error message to client, including + * the error position of the source command. + */ +static void send_err_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, const pj_str_t *msg, + pj_bool_t with_return, pj_bool_t with_last_cmd) +{ + pj_str_t send_data; + char data_str[256]; + pj_size_t len; + unsigned i; + cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + /* Set the error pointer mark */ + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, msg); + pj_strcat(&send_data, &fe->cfg.prompt_str); + if (with_last_cmd) + pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); + + telnet_sess_send(sess, &send_data); +} + +static void send_inv_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return, + pj_bool_t with_last_cmd) +{ + static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; + send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); +} + +static void send_too_many_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return, + pj_bool_t with_last_cmd) +{ + static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; + send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); +} + +static void send_hint_arg(cli_telnet_sess *sess, pj_str_t *send_data, const pj_str_t *desc, pj_ssize_t cmd_len, + pj_ssize_t max_len) +{ + if ((desc) && (desc->slen > 0)) { + int j; + + for (j = 0; j < (max_len - cmd_len); ++j) { + pj_strcat2(send_data, " "); + } + pj_strcat2(send_data, " "); + pj_strcat(send_data, desc); + telnet_sess_send(sess, send_data); + send_data->slen = 0; + } +} + +/* + * This method is used to notify to the client that the entered command + * is ambiguous. It will show the matching command as the hint information. + */ +static void send_ambi_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return, + pj_bool_t with_last_cmd) +{ + unsigned i; + pj_size_t len; + pj_str_t send_data; + char data[1028]; + cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; + const pj_cli_hint_info *hint = info->hint; + out_parse_state parse_state = OP_NORMAL; + pj_ssize_t max_length = 0; + pj_ssize_t cmd_length = 0; + static const pj_str_t sc_type = {"sc", 2}; + static const pj_str_t choice_type = {"choice", 6}; + send_data.ptr = data; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + /* Get the max length of the command name */ + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + cmd_length += (hint[i].name.slen + 3); + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + + if (cmd_length > max_length) { + max_length = cmd_length; + } + } + + cmd_length = 0; + /* Build hint information */ + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + parse_state = OP_SHORTCUT; + } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) { + parse_state = OP_CHOICE; + } else { + parse_state = OP_TYPE; + } + } else { + parse_state = OP_NORMAL; + } + + if (parse_state != OP_SHORTCUT) { + pj_strcat2(&send_data, "\r\n "); + cmd_length = hint[i].name.slen; + } + + switch (parse_state) { + case OP_CHOICE: + /* Format : "[Choice Value] description" */ + pj_strcat2(&send_data, "["); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, "]"); + break; + case OP_TYPE: + /* Format : " description" */ + pj_strcat2(&send_data, "<"); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, ">"); + break; + case OP_SHORTCUT: + /* Format : "Command | sc | description" */ + { + cmd_length += hint[i].name.slen; + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + pj_strcat2(&send_data, " | "); + cmd_length += 3; + } else { + pj_strcat2(&send_data, "\r\n "); + } + pj_strcat(&send_data, &hint[i].name); + } + break; + default: + /* Command */ + pj_strcat(&send_data, &hint[i].name); + break; + } + + if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || ((i + 1) >= info->hint_cnt) || + (pj_strncmp(&hint[i].desc, &hint[i + 1].desc, hint[i].desc.slen))) { + /* Add description info */ + send_hint_arg(sess, &send_data, &hint[i].desc, cmd_length, max_length); + + cmd_length = 0; + } + } + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, &fe->cfg.prompt_str); + if (with_last_cmd) + pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); + + telnet_sess_send(sess, &send_data); +} + +/* + * This method is to send command completion of the entered command. + */ +static void send_comp_arg(cli_telnet_sess *sess, pj_cli_exec_info *info) +{ + pj_str_t send_data; + char data[128]; + + pj_strcat2(&info->hint[0].name, " "); + + send_data.ptr = data; + send_data.slen = 0; + + pj_strcat(&send_data, &info->hint[0].name); + + telnet_sess_send(sess, &send_data); +} + +/* + * This method is to process the alfa numeric character sent by client. + */ +static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data) +{ + if (is_local_option_state_ena(sess, TERM_ECHO)) { + if (recv_buf_right_len(sess->rcmd) > 0) { + /* Cursor is not at EOL, insert character */ + unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00}; + echo[4] = *data; + telnet_sess_send2(sess, echo, 5); + } else { + /* Append character */ + telnet_sess_send2(sess, data, 1); + } + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * This method is to process the backspace character sent by client. + */ +static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data) +{ + unsigned rlen = recv_buf_right_len(sess->rcmd); + if (recv_buf_backspace(sess->rcmd)) { + if (rlen) { + /* + * Cursor is not at the end of line, move the characters + * after the cursor to left + */ + unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50}; + echo[0] = *data; + telnet_sess_send2(sess, echo, 5); + } else { + const static unsigned char echo[3] = {0x08, 0x20, 0x08}; + telnet_sess_send2(sess, echo, 3); + } + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * Syntax error handler for parser. + */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); +} + +/* + * This method is to process the backspace character sent by client. + */ +static pj_status_t get_last_token(pj_str_t *cmd, pj_str_t *str) +{ + pj_scanner scanner; + PJ_USE_EXCEPTION; + pj_scan_init(&scanner, cmd->ptr, cmd->slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_chr(&scanner, " \t\r\n", str); + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + +/* + * This method is to process the tab character sent by client. + */ +static pj_bool_t handle_tab(cli_telnet_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + unsigned len; + + pj_pool_t *pool; + pj_cli_cmd_val *cmd_val; + pj_cli_exec_info info; + pool = pj_pool_create(sess->pool->factory, "handle_tab", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + + cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); + + status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val, pool, &info); + + len = (unsigned)pj_ansi_strlen((char *)sess->rcmd->rbuf); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE); + break; + case PJ_CLI_EMISSINGARG: + case PJ_CLI_EAMBIGUOUS: + send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE); + break; + case PJ_SUCCESS: + if (len > sess->rcmd->cur_pos) { + /* Send the cursor to EOL */ + unsigned rlen = len - sess->rcmd->cur_pos + 1; + unsigned char *data_sent = &sess->rcmd->rbuf[sess->rcmd->cur_pos - 1]; + telnet_sess_send2(sess, data_sent, rlen); + } + if (info.hint_cnt > 0) { + /* Complete command */ + pj_str_t cmd = pj_str((char *)sess->rcmd->rbuf); + pj_str_t last_token; + + if (get_last_token(&cmd, &last_token) == PJ_SUCCESS) { + /* Hint contains the match to the last command entered */ + pj_str_t *hint_info = &info.hint[0].name; + pj_strtrim(&last_token); + if (hint_info->slen >= last_token.slen) { + hint_info->slen -= last_token.slen; + pj_memmove(hint_info->ptr, &hint_info->ptr[last_token.slen], hint_info->slen); + } + send_comp_arg(sess, &info); + + pj_memcpy(&sess->rcmd->rbuf[len], info.hint[0].name.ptr, info.hint[0].name.slen); + + len += (unsigned)info.hint[0].name.slen; + sess->rcmd->rbuf[len] = 0; + } + } else { + retval = PJ_FALSE; + } + break; + } + sess->rcmd->len = len; + sess->rcmd->cur_pos = sess->rcmd->len; + + pj_pool_release(pool); + return retval; +} + +/* + * This method is to process the return character sent by client. + */ +static pj_bool_t handle_return(cli_telnet_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_exec_info info; + + send_return_key(sess); + insert_history(sess, (char *)&sess->rcmd->rbuf); + + pool = pj_pool_create(sess->pool->factory, "handle_return", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + + status = pj_cli_sess_exec(&sess->base, (char *)&sess->rcmd->rbuf, pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE); + break; + case PJ_CLI_EAMBIGUOUS: + case PJ_CLI_EMISSINGARG: + send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE); + break; + case PJ_CLI_EEXIT: + retval = PJ_FALSE; + break; + case PJ_SUCCESS: + send_prompt_str(sess); + break; + } + if (retval) { + sess->rcmd->rbuf[0] = 0; + sess->rcmd->len = 0; + sess->rcmd->cur_pos = sess->rcmd->len; + } + + pj_pool_release(pool); + return retval; +} + +/* + * This method is to process the right key character sent by client. + */ +static pj_bool_t handle_right_key(cli_telnet_sess *sess) +{ + if (recv_buf_right_len(sess->rcmd)) { + unsigned char *data = &sess->rcmd->rbuf[sess->rcmd->cur_pos++]; + telnet_sess_send2(sess, data, 1); + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * This method is to process the left key character sent by client. + */ +static pj_bool_t handle_left_key(cli_telnet_sess *sess) +{ + static const unsigned char move_cursor_left = 0x08; + if (sess->rcmd->cur_pos) { + telnet_sess_send2(sess, &move_cursor_left, 1); + --sess->rcmd->cur_pos; + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * This method is to process the up/down key character sent by client. + */ +static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up) +{ + pj_str_t *history; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + history = get_prev_history(sess, is_up); + if (history) { + pj_str_t send_data; + char str[PJ_CLI_MAX_CMDBUF]; + enum { MOVE_CURSOR_LEFT = 0x08, CLEAR_CHAR = 0x20 }; + send_data.ptr = str; + send_data.slen = 0; + + /* Move cursor position to the beginning of line */ + if (sess->rcmd->cur_pos > 0) { + pj_memset(send_data.ptr, MOVE_CURSOR_LEFT, sess->rcmd->cur_pos); + send_data.slen = sess->rcmd->cur_pos; + } + + if (sess->rcmd->len > (unsigned)history->slen) { + /* Clear the command currently shown*/ + unsigned buf_len = sess->rcmd->len; + pj_memset(&send_data.ptr[send_data.slen], CLEAR_CHAR, buf_len); + send_data.slen += buf_len; + + /* Move cursor position to the beginning of line */ + pj_memset(&send_data.ptr[send_data.slen], MOVE_CURSOR_LEFT, buf_len); + send_data.slen += buf_len; + } + /* Send data */ + pj_strcat(&send_data, history); + telnet_sess_send(sess, &send_data); + pj_ansi_strncpy((char *)&sess->rcmd->rbuf, history->ptr, history->slen); + sess->rcmd->rbuf[history->slen] = 0; + sess->rcmd->len = (unsigned)history->slen; + sess->rcmd->cur_pos = sess->rcmd->len; + return PJ_TRUE; + } + return PJ_FALSE; +} + +static pj_status_t process_vt100_cmd(cli_telnet_sess *sess, unsigned char *cmd) +{ + pj_status_t status = PJ_TRUE; + switch (*cmd) { + case TC_ESC: + break; + case TC_UP: + status = handle_up_down(sess, PJ_TRUE); + break; + case TC_DOWN: + status = handle_up_down(sess, PJ_FALSE); + break; + case TC_RIGHT: + status = handle_right_key(sess); + break; + case TC_LEFT: + status = handle_left_key(sess); + break; + case TC_END: + break; + case TC_HOME: + break; + case TC_CTRL_C: + break; + case TC_CR: + break; + case TC_BS: + break; + case TC_TAB: + break; + case TC_QM: + break; + case TC_BELL: + break; + case TC_DEL: + break; + }; + return status; +} + +PJ_DEF(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param) +{ + pj_assert(param); + + pj_bzero(param, sizeof(*param)); + param->port = PJ_CLI_TELNET_PORT; + param->log_level = PJ_CLI_TELNET_LOG_LEVEL; +} + +/* + * Send a message to a telnet session + */ +static pj_status_t telnet_sess_send(cli_telnet_sess *sess, const pj_str_t *str) +{ + pj_ssize_t sz; + pj_status_t status = PJ_SUCCESS; + + sz = str->slen; + if (!sz) + return PJ_SUCCESS; + + pj_mutex_lock(sess->smutex); + + if (sess->buf_len == 0) + status = pj_activesock_send(sess->asock, &sess->op_key, str->ptr, &sz, 0); + /* If we cannot send now, append it at the end of the buffer + * to be sent later. + */ + if (sess->buf_len > 0 || (status != PJ_SUCCESS && status != PJ_EPENDING)) { + int clen = (int)sz; + + if (sess->buf_len + clen > CLI_TELNET_BUF_SIZE) + clen = CLI_TELNET_BUF_SIZE - sess->buf_len; + if (clen > 0) + pj_memmove(sess->buf + sess->buf_len, str->ptr, clen); + if (clen < sz) { + pj_ansi_snprintf((char *)sess->buf + CLI_TELNET_BUF_SIZE, MAX_CUT_MSG_LEN, CUT_MSG); + sess->buf_len = (unsigned)(CLI_TELNET_BUF_SIZE + pj_ansi_strlen((char *)sess->buf + CLI_TELNET_BUF_SIZE)); + } else + sess->buf_len += clen; + } else if (status == PJ_SUCCESS && sz < str->slen) { + pj_mutex_unlock(sess->smutex); + return PJ_CLI_ETELNETLOST; + } + + pj_mutex_unlock(sess->smutex); + + return PJ_SUCCESS; +} + +/* + * Send a message to a telnet session with formatted text + * (add single linefeed character with carriage return) + */ +static pj_status_t telnet_sess_send_with_format(cli_telnet_sess *sess, const pj_str_t *str) +{ + pj_scanner scanner; + pj_str_t out_str; + static const pj_str_t CR_LF = {("\r\n"), 2}; + int str_len = 0; + char *str_begin = 0; + + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, str->ptr, str->slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + + str_begin = scanner.begin; + + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_ch(&scanner, '\n', &out_str); + str_len = (int)(scanner.curptr - str_begin); + if (*scanner.curptr == '\n') { + if ((str_len > 1) && (out_str.ptr[str_len - 2] == '\r')) { + continue; + } else { + int str_pos = (int)(str_begin - scanner.begin); + + if (str_len > 0) { + pj_str_t s; + pj_strset(&s, &str->ptr[str_pos], str_len); + telnet_sess_send(sess, &s); + } + telnet_sess_send(sess, &CR_LF); + + if (!pj_scan_is_eof(&scanner)) { + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + str_begin = scanner.curptr; + } + } + } else { + pj_str_t s; + int str_pos = (int)(str_begin - scanner.begin); + + pj_strset(&s, &str->ptr[str_pos], str_len); + telnet_sess_send(sess, &s); + } + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return (PJ_GET_EXCEPTION()); + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + +static pj_status_t telnet_sess_send2(cli_telnet_sess *sess, const unsigned char *str, int len) +{ + pj_str_t s; + + pj_strset(&s, (char *)str, len); + return telnet_sess_send(sess, &s); +} + +static void telnet_sess_destroy(pj_cli_sess *sess) +{ + cli_telnet_sess *tsess = (cli_telnet_sess *)sess; + pj_mutex_t *mutex = ((cli_telnet_fe *)sess->fe)->mutex; + + pj_mutex_lock(mutex); + pj_list_erase(sess); + pj_mutex_unlock(mutex); + + pj_mutex_lock(tsess->smutex); + pj_mutex_unlock(tsess->smutex); + pj_activesock_close(tsess->asock); + pj_mutex_destroy(tsess->smutex); + pj_pool_release(tsess->pool); +} + +static void telnet_fe_write_log(pj_cli_front_end *fe, int level, const char *data, pj_size_t len) +{ + cli_telnet_fe *tfe = (cli_telnet_fe *)fe; + pj_cli_sess *sess; + + pj_mutex_lock(tfe->mutex); + + sess = tfe->sess_head.next; + while (sess != &tfe->sess_head) { + cli_telnet_sess *tsess = (cli_telnet_sess *)sess; + + sess = sess->next; + if (tsess->base.log_level >= level) { + pj_str_t s; + + pj_strset(&s, (char *)data, len); + telnet_sess_send_with_format(tsess, &s); + } + } + + pj_mutex_unlock(tfe->mutex); +} + +static void telnet_fe_destroy(pj_cli_front_end *fe) +{ + cli_telnet_fe *tfe = (cli_telnet_fe *)fe; + pj_cli_sess *sess; + + tfe->is_quitting = PJ_TRUE; + if (tfe->worker_thread) { + pj_thread_join(tfe->worker_thread); + } + + pj_mutex_lock(tfe->mutex); + + /* Destroy all the sessions */ + sess = tfe->sess_head.next; + while (sess != &tfe->sess_head) { + (*sess->op->destroy)(sess); + sess = tfe->sess_head.next; + } + + pj_mutex_unlock(tfe->mutex); + + if (tfe->asock) { + pj_activesock_close(tfe->asock); + tfe->asock = NULL; + } + + if (tfe->own_ioqueue && tfe->cfg.ioqueue) { + pj_ioqueue_destroy(tfe->cfg.ioqueue); + tfe->cfg.ioqueue = NULL; + } + + if (tfe->worker_thread) { + pj_thread_destroy(tfe->worker_thread); + tfe->worker_thread = NULL; + } + + pj_mutex_destroy(tfe->mutex); + + pj_pool_release(tfe->pool); +} + +static int poll_worker_thread(void *p) +{ + cli_telnet_fe *fe = (cli_telnet_fe *)p; + + while (!fe->is_quitting) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(fe->cfg.ioqueue, &delay); + } + + return 0; +} + +static pj_bool_t telnet_sess_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *op_key, pj_ssize_t sent) +{ + cli_telnet_sess *sess = (cli_telnet_sess *)pj_activesock_get_user_data(asock); + + PJ_UNUSED_ARG(op_key); + + if (sent <= 0) { + TRACE_((THIS_FILE, "Error On data send")); + pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + + pj_mutex_lock(sess->smutex); + + if (sess->buf_len) { + int len = sess->buf_len; + + sess->buf_len = 0; + if (telnet_sess_send2(sess, sess->buf, len) != PJ_SUCCESS) { + pj_mutex_unlock(sess->smutex); + pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + } + + pj_mutex_unlock(sess->smutex); + + return PJ_TRUE; +} + +static pj_bool_t telnet_sess_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + cli_telnet_sess *sess = (cli_telnet_sess *)pj_activesock_get_user_data(asock); + cli_telnet_fe *tfe = (cli_telnet_fe *)sess->base.fe; + unsigned char *cdata = (unsigned char *)data; + pj_status_t is_valid = PJ_TRUE; + + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(remainder); + + if (tfe->is_quitting) + return PJ_FALSE; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + TRACE_((THIS_FILE, "Error on data read %d", status)); + pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + + pj_mutex_lock(sess->smutex); + + switch (sess->parse_state) { + case ST_CR: + sess->parse_state = ST_NORMAL; + if (*cdata == 0 || *cdata == '\n') { + pj_mutex_unlock(sess->smutex); + is_valid = handle_return(sess); + if (!is_valid) { + // handle_return() can only return PJ_FALSE if + // pj_cli_sess_exec() returns PJ_CLI_EEXIT, + // in which case CLI session has been ended by + // cmd_handler() of CLI_CMD_EXIT. + // + // pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + pj_mutex_lock(sess->smutex); + } + break; + case ST_NORMAL: + if (*cdata == IAC) { + sess->parse_state = ST_IAC; + } else if (*cdata == 127) { + is_valid = handle_backspace(sess, cdata); + } else if (*cdata == 27) { + sess->parse_state = ST_ESC; + } else { + if (recv_buf_insert(sess->rcmd, cdata)) { + if (*cdata == '\r') { + sess->parse_state = ST_CR; + } else if ((*cdata == '\t') || (*cdata == '?')) { + is_valid = handle_tab(sess); + } else if (*cdata > 31 && *cdata < 127) { + is_valid = handle_alfa_num(sess, cdata); + } + } else { + is_valid = PJ_FALSE; + } + } + break; + case ST_ESC: + if (*cdata == 91) { + sess->parse_state = ST_VT100; + } else { + sess->parse_state = ST_NORMAL; + } + break; + case ST_VT100: + sess->parse_state = ST_NORMAL; + is_valid = process_vt100_cmd(sess, cdata); + break; + case ST_IAC: + switch ((unsigned)*cdata) { + case DO: + sess->parse_state = ST_DO; + break; + case DONT: + sess->parse_state = ST_DONT; + break; + case WILL: + sess->parse_state = ST_WILL; + break; + case WONT: + sess->parse_state = ST_WONT; + break; + default: + sess->parse_state = ST_NORMAL; + break; + } + break; + case ST_DO: + receive_do(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + case ST_DONT: + receive_dont(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + case ST_WILL: + receive_will(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + case ST_WONT: + receive_wont(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + default: + sess->parse_state = ST_NORMAL; + break; + } + if (!is_valid) { + send_bell(sess); + } + + pj_mutex_unlock(sess->smutex); + + return PJ_TRUE; +} + +static pj_bool_t telnet_fe_on_accept(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status) +{ + cli_telnet_fe *fe = (cli_telnet_fe *)pj_activesock_get_user_data(asock); + + pj_status_t sstatus; + pj_pool_t *pool; + cli_telnet_sess *sess = NULL; + pj_activesock_cb asock_cb; + + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + if (fe->is_quitting) + return PJ_FALSE; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + TRACE_((THIS_FILE, "Error on data accept (status=%d)", status)); + if (status == PJ_ESOCKETSTOP) { + sstatus = telnet_restart(fe); + if (sstatus != PJ_SUCCESS) { + if (fe->own_ioqueue && fe->cfg.ioqueue) { + pj_ioqueue_destroy(fe->cfg.ioqueue); + fe->cfg.ioqueue = NULL; + } + TRACE_((THIS_FILE, "Error restarting telnet (status=%d)", status)); + } + } + + return PJ_FALSE; + } + + /* An incoming connection is accepted, create a new session */ + pool = pj_pool_create(fe->pool->factory, "telnet_sess", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + if (!pool) { + TRACE_((THIS_FILE, "Not enough memory to create a new telnet session")); + return PJ_TRUE; + } + + sess = PJ_POOL_ZALLOC_T(pool, cli_telnet_sess); + sess->pool = pool; + sess->base.fe = &fe->base; + sess->base.log_level = fe->cfg.log_level; + sess->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op); + sess->base.op->destroy = &telnet_sess_destroy; + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &telnet_sess_on_data_read; + asock_cb.on_data_sent = &telnet_sess_on_data_sent; + sess->rcmd = PJ_POOL_ZALLOC_T(pool, telnet_recv_buf); + sess->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history); + pj_list_init(sess->history); + sess->active_history = sess->history; + + sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_sess", &sess->smutex); + if (sstatus != PJ_SUCCESS) + goto on_exit; + + sstatus = + pj_activesock_create(pool, newsock, pj_SOCK_STREAM(), NULL, fe->cfg.ioqueue, &asock_cb, sess, &sess->asock); + if (sstatus != PJ_SUCCESS) { + TRACE_((THIS_FILE, "Failure creating active socket")); + goto on_exit; + } + + pj_memset(sess->telnet_option, 0, sizeof(sess->telnet_option)); + set_local_option(sess, TRANSMIT_BINARY, PJ_TRUE); + set_local_option(sess, STATUS, PJ_TRUE); + set_local_option(sess, SUPPRESS_GA, PJ_TRUE); + set_local_option(sess, TIMING_MARK, PJ_TRUE); + set_local_option(sess, TERM_SPEED, PJ_TRUE); + set_local_option(sess, TERM_TYPE, PJ_TRUE); + + set_peer_option(sess, TRANSMIT_BINARY, PJ_TRUE); + set_peer_option(sess, SUPPRESS_GA, PJ_TRUE); + set_peer_option(sess, STATUS, PJ_TRUE); + set_peer_option(sess, TIMING_MARK, PJ_TRUE); + set_peer_option(sess, TERM_ECHO, PJ_TRUE); + + send_cmd_do(sess, SUPPRESS_GA); + send_cmd_will(sess, TERM_ECHO); + send_cmd_will(sess, STATUS); + send_cmd_will(sess, SUPPRESS_GA); + + /* Send prompt string */ + telnet_sess_send(sess, &fe->cfg.prompt_str); + + /* Start reading for input from the new telnet session */ + sstatus = pj_activesock_start_read(sess->asock, pool, 1, 0); + if (sstatus != PJ_SUCCESS) { + TRACE_((THIS_FILE, "Failure reading active socket")); + goto on_exit; + } + + pj_ioqueue_op_key_init(&sess->op_key, sizeof(sess->op_key)); + pj_mutex_lock(fe->mutex); + pj_list_push_back(&fe->sess_head, &sess->base); + pj_mutex_unlock(fe->mutex); + + return PJ_TRUE; + +on_exit: + if (sess->asock) + pj_activesock_close(sess->asock); + else + pj_sock_close(newsock); + + if (sess->smutex) + pj_mutex_destroy(sess->smutex); + + pj_pool_release(pool); + + return PJ_TRUE; +} + +PJ_DEF(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli, pj_cli_telnet_cfg *param, pj_cli_front_end **p_fe) +{ + cli_telnet_fe *fe; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(cli, PJ_EINVAL); + + pool = + pj_pool_create(pj_cli_get_param(cli)->pf, "telnet_fe", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + fe = PJ_POOL_ZALLOC_T(pool, cli_telnet_fe); + if (!fe) + return PJ_ENOMEM; + + fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op); + + if (!param) + pj_cli_telnet_cfg_default(&fe->cfg); + else + pj_memcpy(&fe->cfg, param, sizeof(*param)); + + pj_list_init(&fe->sess_head); + fe->base.cli = cli; + fe->base.type = PJ_CLI_TELNET_FRONT_END; + fe->base.op->on_write_log = &telnet_fe_write_log; + fe->base.op->on_destroy = &telnet_fe_destroy; + fe->pool = pool; + + if (!fe->cfg.ioqueue) { + /* Create own ioqueue if application doesn't supply one */ + status = pj_ioqueue_create(pool, 8, &fe->cfg.ioqueue); + if (status != PJ_SUCCESS) + goto on_exit; + fe->own_ioqueue = PJ_TRUE; + } + + status = pj_mutex_create_recursive(pool, "mutex_telnet_fe", &fe->mutex); + if (status != PJ_SUCCESS) + goto on_exit; + + /* Start telnet daemon */ + status = telnet_start(fe); + if (status != PJ_SUCCESS) + goto on_exit; + + pj_cli_register_front_end(cli, &fe->base); + + if (p_fe) + *p_fe = &fe->base; + + TRACE_((THIS_FILE, "Telnet started")); + + return PJ_SUCCESS; + +on_exit: + if (fe->own_ioqueue && fe->cfg.ioqueue) { + pj_ioqueue_destroy(fe->cfg.ioqueue); + fe->cfg.ioqueue = NULL; + } + + if (fe->mutex) { + pj_mutex_destroy(fe->mutex); + fe->mutex = NULL; + } + + pj_pool_release(pool); + return status; +} + +static pj_status_t telnet_start(cli_telnet_fe *fe) +{ + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_activesock_cb asock_cb; + pj_sockaddr_in addr; + pj_status_t status; + int val; + int restart_retry; + unsigned msec; + + /* Start telnet daemon */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock); + + if (status != PJ_SUCCESS) + goto on_exit; + + pj_sockaddr_in_init(&addr, NULL, fe->cfg.port); + + val = 1; + status = pj_sock_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (THIS_FILE, status, "Failed setting socket options")); + } + + /* The loop is silly, but what else can we do? */ + for (msec = MIN_WAIT_ON_TELNET_RESTART, restart_retry = 0; restart_retry < MAX_RETRY_ON_TELNET_RESTART; + ++restart_retry, msec = (msec < MAX_WAIT_ON_TELNET_RESTART ? msec * 2 : MAX_WAIT_ON_TELNET_RESTART)) { + status = pj_sock_bind(sock, &addr, sizeof(addr)); + if (status != PJ_STATUS_FROM_OS(EADDRINUSE)) + break; + PJ_LOG(4, (THIS_FILE, "Address is still in use, retrying..")); + pj_thread_sleep(msec); + } + + if (status == PJ_SUCCESS) { + int addr_len = sizeof(addr); + + status = pj_sock_getsockname(sock, &addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_exit; + + fe->cfg.port = pj_sockaddr_in_get_port(&addr); + + if (fe->cfg.prompt_str.slen == 0) { + pj_str_t prompt_sign = {"> ", 2}; + char *prompt_data = pj_pool_alloc(fe->pool, pj_gethostname()->slen + 2); + fe->cfg.prompt_str.ptr = prompt_data; + + pj_strcpy(&fe->cfg.prompt_str, pj_gethostname()); + pj_strcat(&fe->cfg.prompt_str, &prompt_sign); + } + } else { + PJ_PERROR(3, (THIS_FILE, status, "Failed binding the socket")); + goto on_exit; + } + + status = pj_sock_listen(sock, 4); + if (status != PJ_SUCCESS) + goto on_exit; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_accept_complete2 = &telnet_fe_on_accept; + status = pj_activesock_create(fe->pool, sock, pj_SOCK_STREAM(), NULL, fe->cfg.ioqueue, &asock_cb, fe, &fe->asock); + if (status != PJ_SUCCESS) + goto on_exit; + + status = pj_activesock_start_accept(fe->asock, fe->pool); + if (status != PJ_SUCCESS) + goto on_exit; + + if (fe->own_ioqueue) { + /* Create our own worker thread */ + status = pj_thread_create(fe->pool, "worker_telnet_fe", &poll_worker_thread, fe, 0, 0, &fe->worker_thread); + if (status != PJ_SUCCESS) + goto on_exit; + } + + return PJ_SUCCESS; + +on_exit: + if (fe->cfg.on_started) { + (*fe->cfg.on_started)(status); + } + + if (fe->asock) { + pj_activesock_close(fe->asock); + fe->asock = NULL; + } else if (sock != PJ_INVALID_SOCKET) { + pj_sock_close(sock); + sock = PJ_INVALID_SOCKET; + } + + return status; +} + +static pj_status_t telnet_restart(cli_telnet_fe *fe) +{ + pj_status_t status; + pj_cli_sess *sess; + + fe->is_quitting = PJ_TRUE; + if (fe->worker_thread) { + pj_thread_join(fe->worker_thread); + pj_thread_destroy(fe->worker_thread); + fe->worker_thread = NULL; + } + + pj_mutex_lock(fe->mutex); + + /* Destroy all the sessions */ + sess = fe->sess_head.next; + while (sess != &fe->sess_head) { + (*sess->op->destroy)(sess); + sess = fe->sess_head.next; + } + + pj_mutex_unlock(fe->mutex); + + /** Close existing activesock **/ + status = pj_activesock_close(fe->asock); + if (status != PJ_SUCCESS) + goto on_exit; + + fe->asock = NULL; + fe->is_quitting = PJ_FALSE; + + /** Start Telnet **/ + status = telnet_start(fe); + if (status != PJ_SUCCESS) + goto on_exit; + + if (fe->cfg.on_started) { + (*fe->cfg.on_started)(PJ_SUCCESS); + } + + TRACE_((THIS_FILE, "Telnet restarted")); + +on_exit: + return status; +} + +PJ_DEF(pj_status_t) pj_cli_telnet_get_info(pj_cli_front_end *fe, pj_cli_telnet_info *info) +{ + pj_sockaddr hostip; + pj_status_t status; + cli_telnet_fe *tfe = (cli_telnet_fe *)fe; + + PJ_ASSERT_RETURN(fe && (fe->type == PJ_CLI_TELNET_FRONT_END) && info, PJ_EINVAL); + + pj_strset(&info->ip_address, info->buf_, 0); + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + return status; + + pj_sockaddr_print(&hostip, info->buf_, sizeof(info->buf_), 0); + pj_strset2(&info->ip_address, info->buf_); + + info->port = tfe->cfg.port; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c new file mode 100755 index 000000000..77adb37ea --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c @@ -0,0 +1,179 @@ +/* + * This is an implementation of CRC32. See ISO 3309 and ITU-T V.42 + * for a formal specification + * + * This file is partly taken from Crypto++ library (http://www.cryptopp.com) + * and http://www.di-mgt.com.au/crypto.html#CRC. + * + * Since the original version of the code is put in public domain, + * this file is put on public domain as well. + */ +#include + +#define CRC32_NEGL 0xffffffffL + +#if defined(PJ_CRC32_HAS_TABLES) && PJ_CRC32_HAS_TABLES != 0 +// crc.cpp - written and placed in the public domain by Wei Dai + +/* Table of CRC-32's of all single byte values (made by makecrc.c) */ +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + +#define CRC32_INDEX(c) (c & 0xff) +#define CRC32_SHIFTED(c) (c >> 8) +#define CRC32_SWAP(c) (c) + +static const pj_uint32_t crc_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, + 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, + 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, + 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, + 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, + 0xb6662d3dL, 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, + 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, + 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, + 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, + 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, + 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, + 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, + 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, + 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, + 0x72076785L, 0x05005713L, 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, + 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, + 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, + 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL}; + +#elif defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0 +#define CRC32_INDEX(c) (c >> 24) +#define CRC32_SHIFTED(c) (c << 8) +#define CRC32_SWAP(c) \ + ((((c)&0xff000000) >> 24) | (((c)&0x00ff0000) >> 8) | (((c)&0x0000ff00) << 8) | (((c)&0x000000ff) << 24)) + +static const pj_uint32_t crc_tab[] = { + 0x00000000L, 0x96300777L, 0x2c610eeeL, 0xba510999L, 0x19c46d07L, 0x8ff46a70L, 0x35a563e9L, 0xa395649eL, 0x3288db0eL, + 0xa4b8dc79L, 0x1ee9d5e0L, 0x88d9d297L, 0x2b4cb609L, 0xbd7cb17eL, 0x072db8e7L, 0x911dbf90L, 0x6410b71dL, 0xf220b06aL, + 0x4871b9f3L, 0xde41be84L, 0x7dd4da1aL, 0xebe4dd6dL, 0x51b5d4f4L, 0xc785d383L, 0x56986c13L, 0xc0a86b64L, 0x7af962fdL, + 0xecc9658aL, 0x4f5c0114L, 0xd96c0663L, 0x633d0ffaL, 0xf50d088dL, 0xc8206e3bL, 0x5e10694cL, 0xe44160d5L, 0x727167a2L, + 0xd1e4033cL, 0x47d4044bL, 0xfd850dd2L, 0x6bb50aa5L, 0xfaa8b535L, 0x6c98b242L, 0xd6c9bbdbL, 0x40f9bcacL, 0xe36cd832L, + 0x755cdf45L, 0xcf0dd6dcL, 0x593dd1abL, 0xac30d926L, 0x3a00de51L, 0x8051d7c8L, 0x1661d0bfL, 0xb5f4b421L, 0x23c4b356L, + 0x9995bacfL, 0x0fa5bdb8L, 0x9eb80228L, 0x0888055fL, 0xb2d90cc6L, 0x24e90bb1L, 0x877c6f2fL, 0x114c6858L, 0xab1d61c1L, + 0x3d2d66b6L, 0x9041dc76L, 0x0671db01L, 0xbc20d298L, 0x2a10d5efL, 0x8985b171L, 0x1fb5b606L, 0xa5e4bf9fL, 0x33d4b8e8L, + 0xa2c90778L, 0x34f9000fL, 0x8ea80996L, 0x18980ee1L, 0xbb0d6a7fL, 0x2d3d6d08L, 0x976c6491L, 0x015c63e6L, 0xf4516b6bL, + 0x62616c1cL, 0xd8306585L, 0x4e0062f2L, 0xed95066cL, 0x7ba5011bL, 0xc1f40882L, 0x57c40ff5L, 0xc6d9b065L, 0x50e9b712L, + 0xeab8be8bL, 0x7c88b9fcL, 0xdf1ddd62L, 0x492dda15L, 0xf37cd38cL, 0x654cd4fbL, 0x5861b24dL, 0xce51b53aL, 0x7400bca3L, + 0xe230bbd4L, 0x41a5df4aL, 0xd795d83dL, 0x6dc4d1a4L, 0xfbf4d6d3L, 0x6ae96943L, 0xfcd96e34L, 0x468867adL, 0xd0b860daL, + 0x732d0444L, 0xe51d0333L, 0x5f4c0aaaL, 0xc97c0dddL, 0x3c710550L, 0xaa410227L, 0x10100bbeL, 0x86200cc9L, 0x25b56857L, + 0xb3856f20L, 0x09d466b9L, 0x9fe461ceL, 0x0ef9de5eL, 0x98c9d929L, 0x2298d0b0L, 0xb4a8d7c7L, 0x173db359L, 0x810db42eL, + 0x3b5cbdb7L, 0xad6cbac0L, 0x2083b8edL, 0xb6b3bf9aL, 0x0ce2b603L, 0x9ad2b174L, 0x3947d5eaL, 0xaf77d29dL, 0x1526db04L, + 0x8316dc73L, 0x120b63e3L, 0x843b6494L, 0x3e6a6d0dL, 0xa85a6a7aL, 0x0bcf0ee4L, 0x9dff0993L, 0x27ae000aL, 0xb19e077dL, + 0x44930ff0L, 0xd2a30887L, 0x68f2011eL, 0xfec20669L, 0x5d5762f7L, 0xcb676580L, 0x71366c19L, 0xe7066b6eL, 0x761bd4feL, + 0xe02bd389L, 0x5a7ada10L, 0xcc4add67L, 0x6fdfb9f9L, 0xf9efbe8eL, 0x43beb717L, 0xd58eb060L, 0xe8a3d6d6L, 0x7e93d1a1L, + 0xc4c2d838L, 0x52f2df4fL, 0xf167bbd1L, 0x6757bca6L, 0xdd06b53fL, 0x4b36b248L, 0xda2b0dd8L, 0x4c1b0aafL, 0xf64a0336L, + 0x607a0441L, 0xc3ef60dfL, 0x55df67a8L, 0xef8e6e31L, 0x79be6946L, 0x8cb361cbL, 0x1a8366bcL, 0xa0d26f25L, 0x36e26852L, + 0x95770cccL, 0x03470bbbL, 0xb9160222L, 0x2f260555L, 0xbe3bbac5L, 0x280bbdb2L, 0x925ab42bL, 0x046ab35cL, 0xa7ffd7c2L, + 0x31cfd0b5L, 0x8b9ed92cL, 0x1daede5bL, 0xb0c2649bL, 0x26f263ecL, 0x9ca36a75L, 0x0a936d02L, 0xa906099cL, 0x3f360eebL, + 0x85670772L, 0x13570005L, 0x824abf95L, 0x147ab8e2L, 0xae2bb17bL, 0x381bb60cL, 0x9b8ed292L, 0x0dbed5e5L, 0xb7efdc7cL, + 0x21dfdb0bL, 0xd4d2d386L, 0x42e2d4f1L, 0xf8b3dd68L, 0x6e83da1fL, 0xcd16be81L, 0x5b26b9f6L, 0xe177b06fL, 0x7747b718L, + 0xe65a0888L, 0x706a0fffL, 0xca3b0666L, 0x5c0b0111L, 0xff9e658fL, 0x69ae62f8L, 0xd3ff6b61L, 0x45cf6c16L, 0x78e20aa0L, + 0xeed20dd7L, 0x5483044eL, 0xc2b30339L, 0x612667a7L, 0xf71660d0L, 0x4d476949L, 0xdb776e3eL, 0x4a6ad1aeL, 0xdc5ad6d9L, + 0x660bdf40L, 0xf03bd837L, 0x53aebca9L, 0xc59ebbdeL, 0x7fcfb247L, 0xe9ffb530L, 0x1cf2bdbdL, 0x8ac2bacaL, 0x3093b353L, + 0xa6a3b424L, 0x0536d0baL, 0x9306d7cdL, 0x2957de54L, 0xbf67d923L, 0x2e7a66b3L, 0xb84a61c4L, 0x021b685dL, 0x942b6f2aL, + 0x37be0bb4L, 0xa18e0cc3L, 0x1bdf055aL, 0x8def022dL}; + +#else +#error "Endianness not defined" +#endif + +PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx) +{ + ctx->crc_state = 0; +} + +PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, const pj_uint8_t *data, pj_size_t nbytes) +{ + pj_uint32_t crc = ctx->crc_state ^ CRC32_NEGL; + + for (; (((unsigned long)(pj_ssize_t)data) & 0x03) && nbytes > 0; --nbytes) { + crc = crc_tab[CRC32_INDEX(crc) ^ *data++] ^ CRC32_SHIFTED(crc); + } + + while (nbytes >= 4) { + crc ^= *(const pj_uint32_t *)data; + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + nbytes -= 4; + data += 4; + } + + while (nbytes--) { + crc = crc_tab[CRC32_INDEX(crc) ^ *data++] ^ CRC32_SHIFTED(crc); + } + + ctx->crc_state = crc ^ CRC32_NEGL; + + return ctx->crc_state; +} + +PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx) +{ + return CRC32_SWAP(ctx->crc_state); +} + +#else + +PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx) +{ + ctx->crc_state = CRC32_NEGL; +} + +PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, const pj_uint8_t *octets, pj_size_t len) + +{ + pj_uint32_t crc = ctx->crc_state; + + while (len--) { + pj_uint32_t temp; + int j; + + temp = (pj_uint32_t)((crc & 0xFF) ^ *octets++); + for (j = 0; j < 8; j++) { + if (temp & 0x1) + temp = (temp >> 1) ^ 0xEDB88320; + else + temp >>= 1; + } + crc = (crc >> 8) ^ temp; + } + ctx->crc_state = crc; + + return crc ^ CRC32_NEGL; +} + +PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx) +{ + ctx->crc_state ^= CRC32_NEGL; + return ctx->crc_state; +} + +#endif + +PJ_DEF(pj_uint32_t) pj_crc32_calc(const pj_uint8_t *data, pj_size_t nbytes) +{ + pj_crc32_context ctx; + + pj_crc32_init(&ctx); + pj_crc32_update(&ctx, data, nbytes); + return pj_crc32_final(&ctx); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c new file mode 100755 index 000000000..966696872 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +PJ_DEF(const char *) pj_dns_get_type_name(int type) +{ + switch (type) { + case PJ_DNS_TYPE_A: + return "A"; + case PJ_DNS_TYPE_AAAA: + return "AAAA"; + case PJ_DNS_TYPE_SRV: + return "SRV"; + case PJ_DNS_TYPE_NS: + return "NS"; + case PJ_DNS_TYPE_CNAME: + return "CNAME"; + case PJ_DNS_TYPE_PTR: + return "PTR"; + case PJ_DNS_TYPE_MX: + return "MX"; + case PJ_DNS_TYPE_TXT: + return "TXT"; + case PJ_DNS_TYPE_NAPTR: + return "NAPTR"; + } + return "(Unknown)"; +} + +static void write16(pj_uint8_t *p, pj_uint16_t val) +{ + p[0] = (pj_uint8_t)(val >> 8); + p[1] = (pj_uint8_t)(val & 0xFF); +} + +/** + * Initialize a DNS query transaction. + */ +PJ_DEF(pj_status_t) pj_dns_make_query(void *packet, unsigned *size, pj_uint16_t id, int qtype, const pj_str_t *name) +{ + pj_uint8_t *p = (pj_uint8_t *)packet; + const char *startlabel, *endlabel, *endname; + pj_size_t d; + + /* Sanity check */ + PJ_ASSERT_RETURN(packet && size && qtype && name, PJ_EINVAL); + + /* Calculate total number of bytes required. */ + d = sizeof(pj_dns_hdr) + name->slen + 4; + + /* Check that size is sufficient. */ + PJ_ASSERT_RETURN(*size >= d, PJLIB_UTIL_EDNSQRYTOOSMALL); + + /* Initialize header */ + pj_assert(sizeof(pj_dns_hdr) == 12); + pj_bzero(p, sizeof(struct pj_dns_hdr)); + write16(p + 0, id); + write16(p + 2, (pj_uint16_t)PJ_DNS_SET_RD(1)); + write16(p + 4, (pj_uint16_t)1); + + /* Initialize query */ + p = ((pj_uint8_t *)packet) + sizeof(pj_dns_hdr); + + /* Tokenize name */ + startlabel = endlabel = name->ptr; + endname = name->ptr + name->slen; + while (endlabel != endname) { + while (endlabel != endname && *endlabel != '.') + ++endlabel; + *p++ = (pj_uint8_t)(endlabel - startlabel); + pj_memcpy(p, startlabel, endlabel - startlabel); + p += (endlabel - startlabel); + if (endlabel != endname && *endlabel == '.') + ++endlabel; + startlabel = endlabel; + } + *p++ = '\0'; + + /* Set type */ + write16(p, (pj_uint16_t)qtype); + p += 2; + + /* Set class (IN=1) */ + write16(p, 1); + p += 2; + + /* Done, calculate length */ + *size = (unsigned)(p - (pj_uint8_t *)packet); + + return 0; +} + +/* Get a name length (note: name consists of multiple labels and + * it may contain pointers when name compression is applied) + */ +static pj_status_t get_name_len(int rec_counter, const pj_uint8_t *pkt, const pj_uint8_t *start, const pj_uint8_t *max, + int *parsed_len, int *name_len) +{ + const pj_uint8_t *p; + pj_status_t status; + + /* Limit the number of recursion */ + if (rec_counter > 10) { + /* Too many name recursion */ + return PJLIB_UTIL_EDNSINNAMEPTR; + } + + if (start >= max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + *name_len = *parsed_len = 0; + p = start; + while (*p) { + if ((*p & 0xc0) == 0xc0) { + /* Compression is found! */ + int ptr_len = 0; + int dummy; + pj_uint16_t offset; + + /* Get the 14bit offset */ + pj_memcpy(&offset, p, 2); + offset ^= pj_htons((pj_uint16_t)(0xc0 << 8)); + offset = pj_ntohs(offset); + + /* Check that offset is valid */ + if (offset >= max - pkt) + return PJLIB_UTIL_EDNSINNAMEPTR; + + /* Get the name length from that offset. */ + status = get_name_len(rec_counter + 1, pkt, pkt + offset, max, &dummy, &ptr_len); + if (status != PJ_SUCCESS) + return status; + + *parsed_len += 2; + *name_len += ptr_len; + + return PJ_SUCCESS; + } else { + unsigned label_len = *p; + + /* Check that label length is valid. + * Each label consists of an octet length (of size 1) followed + * by the octet of the specified length (label_len). Then it + * must be followed by either another label's octet length or + * a zero length octet (that terminates the sequence). + */ + if (p + 1 + label_len + 1 > max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + p += (label_len + 1); + *parsed_len += (label_len + 1); + + if (*p != 0) + ++label_len; + + *name_len += label_len; + } + } + ++p; + (*parsed_len)++; + + return PJ_SUCCESS; +} + +/* Parse and copy name (note: name consists of multiple labels and + * it may contain pointers when compression is applied). + */ +static pj_status_t get_name(int rec_counter, const pj_uint8_t *pkt, const pj_uint8_t *start, const pj_uint8_t *max, + pj_str_t *name) +{ + const pj_uint8_t *p; + pj_status_t status; + + /* Limit the number of recursion */ + if (rec_counter > 10) { + /* Too many name recursion */ + return PJLIB_UTIL_EDNSINNAMEPTR; + } + + if (start >= max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + p = start; + while (*p) { + if ((*p & 0xc0) == 0xc0) { + /* Compression is found! */ + pj_uint16_t offset; + + /* Get the 14bit offset */ + pj_memcpy(&offset, p, 2); + offset ^= pj_htons((pj_uint16_t)(0xc0 << 8)); + offset = pj_ntohs(offset); + + /* Check that offset is valid */ + if (offset >= max - pkt) + return PJLIB_UTIL_EDNSINNAMEPTR; + + /* Retrieve the name from that offset. */ + status = get_name(rec_counter + 1, pkt, pkt + offset, max, name); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; + } else { + unsigned label_len = *p; + + /* Check that label length is valid. + * Each label consists of an octet length (of size 1) followed + * by the octet of the specified length (label_len). Then it + * must be followed by either another label's octet length or + * a zero length octet (that terminates the sequence). + */ + if (p + 1 + label_len + 1 > max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + pj_memcpy(name->ptr + name->slen, p + 1, label_len); + name->slen += label_len; + + p += label_len + 1; + if (*p != 0) { + *(name->ptr + name->slen) = '.'; + ++name->slen; + } + } + } + + return PJ_SUCCESS; +} + +/* Parse query records. */ +static pj_status_t parse_query(pj_dns_parsed_query *q, pj_pool_t *pool, const pj_uint8_t *pkt, const pj_uint8_t *start, + const pj_uint8_t *max, int *parsed_len) +{ + const pj_uint8_t *p = start; + int name_len, name_part_len; + pj_status_t status; + + /* Get the length of the name */ + status = get_name_len(0, pkt, start, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + q->name.ptr = (char *)pj_pool_alloc(pool, name_len + 4); + q->name.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, start, max, &q->name); + if (status != PJ_SUCCESS) + return status; + + p = (start + name_part_len); + + /* Check the size can accomodate next few fields. */ + if (p + 4 > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Get the type */ + pj_memcpy(&q->type, p, 2); + q->type = pj_ntohs(q->type); + p += 2; + + /* Get the class */ + pj_memcpy(&q->dnsclass, p, 2); + q->dnsclass = pj_ntohs(q->dnsclass); + p += 2; + + *parsed_len = (int)(p - start); + + return PJ_SUCCESS; +} + +/* Parse RR records */ +static pj_status_t parse_rr(pj_dns_parsed_rr *rr, pj_pool_t *pool, const pj_uint8_t *pkt, const pj_uint8_t *start, + const pj_uint8_t *max, int *parsed_len) +{ + const pj_uint8_t *p = start; + int name_len, name_part_len; + pj_status_t status; + + /* Get the length of the name */ + status = get_name_len(0, pkt, start, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + rr->name.ptr = (char *)pj_pool_alloc(pool, name_len + 4); + rr->name.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, start, max, &rr->name); + if (status != PJ_SUCCESS) + return status; + + p = (start + name_part_len); + + /* Check the size can accomodate next few fields. */ + if (p + 10 > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Get the type */ + pj_memcpy(&rr->type, p, 2); + rr->type = pj_ntohs(rr->type); + p += 2; + + /* Get the class */ + pj_memcpy(&rr->dnsclass, p, 2); + rr->dnsclass = pj_ntohs(rr->dnsclass); + p += 2; + + /* Class MUST be IN */ + if (rr->dnsclass != 1) { + /* Class is not IN, return error only if type is known (see #1889) */ + if (rr->type == PJ_DNS_TYPE_A || rr->type == PJ_DNS_TYPE_AAAA || rr->type == PJ_DNS_TYPE_CNAME || + rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR || rr->type == PJ_DNS_TYPE_SRV) { + return PJLIB_UTIL_EDNSINCLASS; + } + } + + /* Get TTL */ + pj_memcpy(&rr->ttl, p, 4); + rr->ttl = pj_ntohl(rr->ttl); + p += 4; + + /* Get rdlength */ + pj_memcpy(&rr->rdlength, p, 2); + rr->rdlength = pj_ntohs(rr->rdlength); + p += 2; + + /* Check that length is valid */ + if (p + rr->rdlength > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Parse some well known records */ + if (rr->type == PJ_DNS_TYPE_A) { + if (p + 4 > max) + return PJLIB_UTIL_EDNSINSIZE; + pj_memcpy(&rr->rdata.a.ip_addr, p, 4); + p += 4; + + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + if (p + 16 > max) + return PJLIB_UTIL_EDNSINSIZE; + pj_memcpy(&rr->rdata.aaaa.ip_addr, p, 16); + p += 16; + + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { + + /* Get the length of the target name */ + status = get_name_len(0, pkt, p, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + rr->rdata.cname.name.ptr = (char *)pj_pool_alloc(pool, name_len); + rr->rdata.cname.name.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, p, max, &rr->rdata.cname.name); + if (status != PJ_SUCCESS) + return status; + + p += name_part_len; + + } else if (rr->type == PJ_DNS_TYPE_SRV) { + if (p + 6 > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Priority */ + pj_memcpy(&rr->rdata.srv.prio, p, 2); + rr->rdata.srv.prio = pj_ntohs(rr->rdata.srv.prio); + p += 2; + + /* Weight */ + pj_memcpy(&rr->rdata.srv.weight, p, 2); + rr->rdata.srv.weight = pj_ntohs(rr->rdata.srv.weight); + p += 2; + + /* Port */ + pj_memcpy(&rr->rdata.srv.port, p, 2); + rr->rdata.srv.port = pj_ntohs(rr->rdata.srv.port); + p += 2; + + /* Get the length of the target name */ + status = get_name_len(0, pkt, p, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + rr->rdata.srv.target.ptr = (char *)pj_pool_alloc(pool, name_len); + rr->rdata.srv.target.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, p, max, &rr->rdata.srv.target); + if (status != PJ_SUCCESS) + return status; + p += name_part_len; + + } else { + /* Copy the raw data */ + rr->data = pj_pool_alloc(pool, rr->rdlength); + pj_memcpy(rr->data, p, rr->rdlength); + + p += rr->rdlength; + } + + *parsed_len = (int)(p - start); + return PJ_SUCCESS; +} + +/* + * Parse raw DNS packet into DNS packet structure. + */ +PJ_DEF(pj_status_t) +pj_dns_parse_packet(pj_pool_t *pool, const void *packet, unsigned size, pj_dns_parsed_packet **p_res) +{ + pj_dns_parsed_packet *res; + const pj_uint8_t *start, *end; + pj_status_t status; + unsigned i; + + /* Sanity checks */ + PJ_ASSERT_RETURN(pool && packet && size && p_res, PJ_EINVAL); + + /* Packet size must be at least as big as the header */ + if (size < sizeof(pj_dns_hdr)) + return PJLIB_UTIL_EDNSINSIZE; + + /* Create the structure */ + res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + + /* Copy the DNS header, and convert endianness to host byte order */ + pj_memcpy(&res->hdr, packet, sizeof(pj_dns_hdr)); + res->hdr.id = pj_ntohs(res->hdr.id); + res->hdr.flags = pj_ntohs(res->hdr.flags); + res->hdr.qdcount = pj_ntohs(res->hdr.qdcount); + res->hdr.anscount = pj_ntohs(res->hdr.anscount); + res->hdr.nscount = pj_ntohs(res->hdr.nscount); + res->hdr.arcount = pj_ntohs(res->hdr.arcount); + + /* Mark start and end of payload */ + start = ((const pj_uint8_t *)packet) + sizeof(pj_dns_hdr); + end = ((const pj_uint8_t *)packet) + size; + + /* Parse query records (if any). + */ + if (res->hdr.qdcount) { + res->q = (pj_dns_parsed_query *)pj_pool_zalloc(pool, res->hdr.qdcount * sizeof(pj_dns_parsed_query)); + for (i = 0; i < res->hdr.qdcount; ++i) { + int parsed_len = 0; + + status = parse_query(&res->q[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Parse answer, if any */ + if (res->hdr.anscount) { + res->ans = (pj_dns_parsed_rr *)pj_pool_zalloc(pool, res->hdr.anscount * sizeof(pj_dns_parsed_rr)); + + for (i = 0; i < res->hdr.anscount; ++i) { + int parsed_len; + + status = parse_rr(&res->ans[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Parse authoritative NS records, if any */ + if (res->hdr.nscount) { + res->ns = (pj_dns_parsed_rr *)pj_pool_zalloc(pool, res->hdr.nscount * sizeof(pj_dns_parsed_rr)); + + for (i = 0; i < res->hdr.nscount; ++i) { + int parsed_len; + + status = parse_rr(&res->ns[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Parse additional RR answer, if any */ + if (res->hdr.arcount) { + res->arr = (pj_dns_parsed_rr *)pj_pool_zalloc(pool, res->hdr.arcount * sizeof(pj_dns_parsed_rr)); + + for (i = 0; i < res->hdr.arcount; ++i) { + int parsed_len; + + status = parse_rr(&res->arr[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Looks like everything is okay */ + *p_res = res; + + return PJ_SUCCESS; +} + +/* Perform name compression scheme. + * If a name is already in the nametable, when no need to duplicate + * the string with the pool, but rather just use the pointer there. + */ +static void apply_name_table(unsigned *count, pj_str_t nametable[], const pj_str_t *src, pj_pool_t *pool, pj_str_t *dst) +{ + unsigned i; + + /* Scan strings in nametable */ + for (i = 0; i < *count; ++i) { + if (pj_stricmp(&nametable[i], src) == 0) + break; + } + + /* If name is found in nametable, use the pointer in the nametable */ + if (i != *count) { + dst->ptr = nametable[i].ptr; + dst->slen = nametable[i].slen; + return; + } + + /* Otherwise duplicate the string, and insert new name in nametable */ + pj_strdup(pool, dst, src); + + if (*count < PJ_DNS_MAX_NAMES_IN_NAMETABLE) { + nametable[*count].ptr = dst->ptr; + nametable[*count].slen = dst->slen; + + ++(*count); + } +} + +static void copy_query(pj_pool_t *pool, pj_dns_parsed_query *dst, const pj_dns_parsed_query *src, + unsigned *nametable_count, pj_str_t nametable[]) +{ + pj_memcpy(dst, src, sizeof(*src)); + apply_name_table(nametable_count, nametable, &src->name, pool, &dst->name); +} + +static void copy_rr(pj_pool_t *pool, pj_dns_parsed_rr *dst, const pj_dns_parsed_rr *src, unsigned *nametable_count, + pj_str_t nametable[]) +{ + pj_memcpy(dst, src, sizeof(*src)); + apply_name_table(nametable_count, nametable, &src->name, pool, &dst->name); + + if (src->data) { + dst->data = pj_pool_alloc(pool, src->rdlength); + pj_memcpy(dst->data, src->data, src->rdlength); + } + + if (src->type == PJ_DNS_TYPE_SRV) { + apply_name_table(nametable_count, nametable, &src->rdata.srv.target, pool, &dst->rdata.srv.target); + } else if (src->type == PJ_DNS_TYPE_A) { + dst->rdata.a.ip_addr.s_addr = src->rdata.a.ip_addr.s_addr; + } else if (src->type == PJ_DNS_TYPE_AAAA) { + pj_memcpy(&dst->rdata.aaaa.ip_addr, &src->rdata.aaaa.ip_addr, sizeof(pj_in6_addr)); + } else if (src->type == PJ_DNS_TYPE_CNAME) { + pj_strdup(pool, &dst->rdata.cname.name, &src->rdata.cname.name); + } else if (src->type == PJ_DNS_TYPE_NS) { + pj_strdup(pool, &dst->rdata.ns.name, &src->rdata.ns.name); + } else if (src->type == PJ_DNS_TYPE_PTR) { + pj_strdup(pool, &dst->rdata.ptr.name, &src->rdata.ptr.name); + } +} + +/* + * Duplicate DNS packet. + */ +PJ_DEF(void) +pj_dns_packet_dup(pj_pool_t *pool, const pj_dns_parsed_packet *p, unsigned options, pj_dns_parsed_packet **p_dst) +{ + pj_dns_parsed_packet *dst; + unsigned nametable_count = 0; +#if PJ_DNS_MAX_NAMES_IN_NAMETABLE + pj_str_t nametable[PJ_DNS_MAX_NAMES_IN_NAMETABLE]; +#else + pj_str_t *nametable = NULL; +#endif + unsigned i; + + PJ_ASSERT_ON_FAIL(pool && p && p_dst, return ); + + /* Create packet and copy header */ + *p_dst = dst = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + pj_memcpy(&dst->hdr, &p->hdr, sizeof(p->hdr)); + + /* Initialize section counts in the target packet to zero. + * If memory allocation fails during copying process, the target packet + * should have a correct section counts. + */ + dst->hdr.qdcount = 0; + dst->hdr.anscount = 0; + dst->hdr.nscount = 0; + dst->hdr.arcount = 0; + + /* Copy query section */ + if (p->hdr.qdcount && (options & PJ_DNS_NO_QD) == 0) { + dst->q = (pj_dns_parsed_query *)pj_pool_alloc(pool, p->hdr.qdcount * sizeof(pj_dns_parsed_query)); + for (i = 0; i < p->hdr.qdcount; ++i) { + copy_query(pool, &dst->q[i], &p->q[i], &nametable_count, nametable); + ++dst->hdr.qdcount; + } + } + + /* Copy answer section */ + if (p->hdr.anscount && (options & PJ_DNS_NO_ANS) == 0) { + dst->ans = (pj_dns_parsed_rr *)pj_pool_alloc(pool, p->hdr.anscount * sizeof(pj_dns_parsed_rr)); + for (i = 0; i < p->hdr.anscount; ++i) { + copy_rr(pool, &dst->ans[i], &p->ans[i], &nametable_count, nametable); + ++dst->hdr.anscount; + } + } + + /* Copy NS section */ + if (p->hdr.nscount && (options & PJ_DNS_NO_NS) == 0) { + dst->ns = (pj_dns_parsed_rr *)pj_pool_alloc(pool, p->hdr.nscount * sizeof(pj_dns_parsed_rr)); + for (i = 0; i < p->hdr.nscount; ++i) { + copy_rr(pool, &dst->ns[i], &p->ns[i], &nametable_count, nametable); + ++dst->hdr.nscount; + } + } + + /* Copy additional info section */ + if (p->hdr.arcount && (options & PJ_DNS_NO_AR) == 0) { + dst->arr = (pj_dns_parsed_rr *)pj_pool_alloc(pool, p->hdr.arcount * sizeof(pj_dns_parsed_rr)); + for (i = 0; i < p->hdr.arcount; ++i) { + copy_rr(pool, &dst->arr[i], &p->arr[i], &nametable_count, nametable); + ++dst->hdr.arcount; + } + } +} + +PJ_DEF(void) +pj_dns_init_srv_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, unsigned prio, + unsigned weight, unsigned port, const pj_str_t *target) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_SRV; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.srv.prio = (pj_uint16_t)prio; + rec->rdata.srv.weight = (pj_uint16_t)weight; + rec->rdata.srv.port = (pj_uint16_t)port; + rec->rdata.srv.target = *target; +} + +PJ_DEF(void) +pj_dns_init_cname_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_str_t *name) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_CNAME; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.cname.name = *name; +} + +PJ_DEF(void) +pj_dns_init_a_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in_addr *ip_addr) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_A; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.a.ip_addr = *ip_addr; +} + +PJ_DEF(void) +pj_dns_init_aaaa_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in6_addr *ip_addr) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_AAAA; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.aaaa.ip_addr = *ip_addr; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c new file mode 100755 index 000000000..d20db8ec2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#define THIS_FILE "dns_dump.c" +#define LEVEL 3 + +static const char *spell_ttl(char *buf, int size, unsigned ttl) +{ +#define DAY (3600 * 24) +#define HOUR (3600) +#define MINUTE (60) + + char *p = buf; + int len; + + if (ttl > DAY) { + len = pj_ansi_snprintf(p, size, "%dd ", ttl / DAY); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl %= DAY; + } + + if (ttl > HOUR) { + len = pj_ansi_snprintf(p, size, "%dh ", ttl / HOUR); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl %= HOUR; + } + + if (ttl > MINUTE) { + len = pj_ansi_snprintf(p, size, "%dm ", ttl / MINUTE); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl %= MINUTE; + } + + if (ttl > 0) { + len = pj_ansi_snprintf(p, size, "%ds ", ttl); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl = 0; + } + + *p = '\0'; + return buf; +} + +static void dump_query(unsigned index, const pj_dns_parsed_query *q) +{ + PJ_LOG(3, (THIS_FILE, " %d. Name: %.*s", index, (int)q->name.slen, q->name.ptr)); + PJ_LOG(3, (THIS_FILE, " Type: %s (%d)", pj_dns_get_type_name(q->type), q->type)); + PJ_LOG(3, (THIS_FILE, " Class: %s (%d)", (q->dnsclass == 1 ? "IN" : ""), q->dnsclass)); +} + +static void dump_answer(unsigned index, const pj_dns_parsed_rr *rr) +{ + const pj_str_t root_name = {"", 6}; + const pj_str_t *name = &rr->name; + char ttl_words[32]; + char addr[PJ_INET6_ADDRSTRLEN]; + + if (name->slen == 0) + name = &root_name; + + PJ_LOG(3, (THIS_FILE, " %d. %s record (type=%d)", index, pj_dns_get_type_name(rr->type), rr->type)); + PJ_LOG(3, (THIS_FILE, " Name: %.*s", (int)name->slen, name->ptr)); + PJ_LOG(3, (THIS_FILE, " TTL: %u (%s)", rr->ttl, spell_ttl(ttl_words, sizeof(ttl_words), rr->ttl))); + PJ_LOG(3, (THIS_FILE, " Data length: %u", rr->rdlength)); + + if (rr->type == PJ_DNS_TYPE_SRV) { + PJ_LOG(3, (THIS_FILE, " SRV: prio=%d, weight=%d %.*s:%d", rr->rdata.srv.prio, rr->rdata.srv.weight, + (int)rr->rdata.srv.target.slen, rr->rdata.srv.target.ptr, rr->rdata.srv.port)); + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { + PJ_LOG(3, (THIS_FILE, " Name: %.*s", (int)rr->rdata.cname.name.slen, rr->rdata.cname.name.ptr)); + } else if (rr->type == PJ_DNS_TYPE_A) { + PJ_LOG(3, (THIS_FILE, " IP address: %s", + pj_inet_ntop2(pj_AF_INET(), &rr->rdata.a.ip_addr, addr, sizeof(addr)))); + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + PJ_LOG(3, (THIS_FILE, " IPv6 address: %s", + pj_inet_ntop2(pj_AF_INET6(), &rr->rdata.aaaa.ip_addr, addr, sizeof(addr)))); + } +} + +PJ_DEF(void) pj_dns_dump_packet(const pj_dns_parsed_packet *res) +{ + unsigned i; + + PJ_ASSERT_ON_FAIL(res != NULL, return ); + + /* Header part */ + PJ_LOG(3, (THIS_FILE, "Domain Name System packet (%s):", (PJ_DNS_GET_QR(res->hdr.flags) ? "response" : "query"))); + PJ_LOG(3, (THIS_FILE, " Transaction ID: %d", res->hdr.id)); + PJ_LOG(3, + (THIS_FILE, " Flags: opcode=%d, authoritative=%d, truncated=%d, rcode=%d", PJ_DNS_GET_OPCODE(res->hdr.flags), + PJ_DNS_GET_AA(res->hdr.flags), PJ_DNS_GET_TC(res->hdr.flags), PJ_DNS_GET_RCODE(res->hdr.flags))); + PJ_LOG(3, (THIS_FILE, " Nb of queries: %d", res->hdr.qdcount)); + PJ_LOG(3, (THIS_FILE, " Nb of answer RR: %d", res->hdr.anscount)); + PJ_LOG(3, (THIS_FILE, " Nb of authority RR: %d", res->hdr.nscount)); + PJ_LOG(3, (THIS_FILE, " Nb of additional RR: %d", res->hdr.arcount)); + PJ_LOG(3, (THIS_FILE, "")); + + /* Dump queries */ + if (res->hdr.qdcount) { + PJ_LOG(3, (THIS_FILE, " Queries:")); + + for (i = 0; i < res->hdr.qdcount; ++i) { + dump_query(i, &res->q[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } + + /* Dump answers */ + if (res->hdr.anscount) { + PJ_LOG(3, (THIS_FILE, " Answers RR:")); + + for (i = 0; i < res->hdr.anscount; ++i) { + dump_answer(i, &res->ans[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } + + /* Dump NS sections */ + if (res->hdr.nscount) { + PJ_LOG(3, (THIS_FILE, " NS Authority RR:")); + + for (i = 0; i < res->hdr.nscount; ++i) { + dump_answer(i, &res->ns[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } + + /* Dump Additional info sections */ + if (res->hdr.arcount) { + PJ_LOG(3, (THIS_FILE, " Additional Info RR:")); + + for (i = 0; i < res->hdr.arcount; ++i) { + dump_answer(i, &res->arr[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c new file mode 100755 index 000000000..2408ae436 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "dns_server.c" +#define MAX_ANS 16 +#define MAX_PKT 1500 +#define MAX_LABEL 32 + +struct label_tab { + unsigned count; + + struct { + unsigned pos; + pj_str_t label; + } a[MAX_LABEL]; +}; + +struct rr { + PJ_DECL_LIST_MEMBER(struct rr); + pj_dns_parsed_rr rec; +}; + +struct pj_dns_server { + pj_pool_t *pool; + pj_pool_factory *pf; + pj_activesock_t *asock; + pj_ioqueue_op_key_t send_key; + struct rr rr_list; +}; + +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + +PJ_DEF(pj_status_t) +pj_dns_server_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, int af, unsigned port, unsigned flags, + pj_dns_server **p_srv) +{ + pj_pool_t *pool; + pj_dns_server *srv; + pj_sockaddr sock_addr; + pj_activesock_cb sock_cb; + pj_status_t status; + + PJ_ASSERT_RETURN(pf && ioqueue && p_srv && flags == 0, PJ_EINVAL); + PJ_ASSERT_RETURN(af == pj_AF_INET() || af == pj_AF_INET6(), PJ_EINVAL); + + pool = pj_pool_create(pf, "dnsserver", 256, 256, NULL); + srv = (pj_dns_server *)PJ_POOL_ZALLOC_T(pool, pj_dns_server); + srv->pool = pool; + srv->pf = pf; + pj_list_init(&srv->rr_list); + + pj_bzero(&sock_addr, sizeof(sock_addr)); + sock_addr.addr.sa_family = (pj_uint16_t)af; + pj_sockaddr_set_port(&sock_addr, (pj_uint16_t)port); + + pj_bzero(&sock_cb, sizeof(sock_cb)); + sock_cb.on_data_recvfrom = &on_data_recvfrom; + + status = pj_activesock_create_udp(pool, &sock_addr, NULL, ioqueue, &sock_cb, srv, &srv->asock, NULL); + if (status != PJ_SUCCESS) + goto on_error; + + pj_ioqueue_op_key_init(&srv->send_key, sizeof(srv->send_key)); + + status = pj_activesock_start_recvfrom(srv->asock, pool, MAX_PKT, 0); + if (status != PJ_SUCCESS) + goto on_error; + + *p_srv = srv; + return PJ_SUCCESS; + +on_error: + pj_dns_server_destroy(srv); + return status; +} + +PJ_DEF(pj_status_t) pj_dns_server_destroy(pj_dns_server *srv) +{ + PJ_ASSERT_RETURN(srv, PJ_EINVAL); + + if (srv->asock) { + pj_activesock_close(srv->asock); + srv->asock = NULL; + } + + pj_pool_safe_release(&srv->pool); + + return PJ_SUCCESS; +} + +static struct rr *find_rr(pj_dns_server *srv, unsigned dns_class, unsigned type /* pj_dns_type */, const pj_str_t *name) +{ + struct rr *r; + + r = srv->rr_list.next; + while (r != &srv->rr_list) { + if (r->rec.dnsclass == dns_class && r->rec.type == type && pj_stricmp(&r->rec.name, name) == 0) { + return r; + } + r = r->next; + } + + return NULL; +} + +PJ_DEF(pj_status_t) pj_dns_server_add_rec(pj_dns_server *srv, unsigned count, const pj_dns_parsed_rr rr_param[]) +{ + unsigned i; + + PJ_ASSERT_RETURN(srv && count && rr_param, PJ_EINVAL); + + for (i = 0; i < count; ++i) { + struct rr *rr; + + PJ_ASSERT_RETURN(find_rr(srv, rr_param[i].dnsclass, rr_param[i].type, &rr_param[i].name) == NULL, PJ_EEXISTS); + + rr = (struct rr *)PJ_POOL_ZALLOC_T(srv->pool, struct rr); + pj_memcpy(&rr->rec, &rr_param[i], sizeof(pj_dns_parsed_rr)); + + pj_list_push_back(&srv->rr_list, rr); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_dns_server_del_rec(pj_dns_server *srv, int dns_class, pj_dns_type type, const pj_str_t *name) +{ + struct rr *rr; + + PJ_ASSERT_RETURN(srv && type && name, PJ_EINVAL); + + rr = find_rr(srv, dns_class, type, name); + if (!rr) + return PJ_ENOTFOUND; + + pj_list_erase(rr); + + return PJ_SUCCESS; +} + +static void write16(pj_uint8_t *p, pj_uint16_t val) +{ + p[0] = (pj_uint8_t)(val >> 8); + p[1] = (pj_uint8_t)(val & 0xFF); +} + +static void write32(pj_uint8_t *p, pj_uint32_t val) +{ + val = pj_htonl(val); + pj_memcpy(p, &val, 4); +} + +static int print_name(pj_uint8_t *pkt, int size, pj_uint8_t *pos, const pj_str_t *name, struct label_tab *tab) +{ + pj_uint8_t *p = pos; + const char *endlabel, *endname; + unsigned i; + pj_str_t label; + + /* Check if name is in the table */ + for (i = 0; i < tab->count; ++i) { + if (pj_strcmp(&tab->a[i].label, name) == 0) + break; + } + + if (i != tab->count) { + write16(p, (pj_uint16_t)(tab->a[i].pos | (0xc0 << 8))); + return 2; + } else { + if (tab->count < MAX_LABEL) { + tab->a[tab->count].pos = (unsigned)(p - pkt); + tab->a[tab->count].label.ptr = (char *)(p + 1); + tab->a[tab->count].label.slen = name->slen; + ++tab->count; + } + } + + endlabel = name->ptr; + endname = name->ptr + name->slen; + + label.ptr = (char *)name->ptr; + + while (endlabel != endname) { + + while (endlabel != endname && *endlabel != '.') + ++endlabel; + + label.slen = (endlabel - label.ptr); + + if (size < label.slen + 1) + return -1; + + *p = (pj_uint8_t)label.slen; + pj_memcpy(p + 1, label.ptr, label.slen); + + size -= (int)(label.slen + 1); + p += (label.slen + 1); + + if (endlabel != endname && *endlabel == '.') + ++endlabel; + label.ptr = (char *)endlabel; + } + + if (size == 0) + return -1; + + *p++ = '\0'; + + return (int)(p - pos); +} + +static int print_rr(pj_uint8_t *pkt, int size, pj_uint8_t *pos, const pj_dns_parsed_rr *rr, struct label_tab *tab) +{ + pj_uint8_t *p = pos; + int len; + + len = print_name(pkt, size, pos, &rr->name, tab); + if (len < 0) + return -1; + + p += len; + size -= len; + + if (size < 8) + return -1; + + pj_assert(rr->dnsclass == 1); + + write16(p + 0, (pj_uint16_t)rr->type); /* type */ + write16(p + 2, (pj_uint16_t)rr->dnsclass); /* class */ + write32(p + 4, rr->ttl); /* TTL */ + + p += 8; + size -= 8; + + if (rr->type == PJ_DNS_TYPE_A) { + + if (size < 6) + return -1; + + /* RDLEN is 4 */ + write16(p, 4); + + /* Address */ + pj_memcpy(p + 2, &rr->rdata.a.ip_addr, 4); + + p += 6; + size -= 6; + + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + + if (size < 18) + return -1; + + /* RDLEN is 16 */ + write16(p, 16); + + /* Address */ + pj_memcpy(p + 2, &rr->rdata.aaaa.ip_addr, 16); + + p += 18; + size -= 18; + + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { + + if (size < 4) + return -1; + + len = print_name(pkt, size - 2, p + 2, &rr->rdata.cname.name, tab); + if (len < 0) + return -1; + + write16(p, (pj_uint16_t)len); + + p += (len + 2); + size -= (len + 2); + + } else if (rr->type == PJ_DNS_TYPE_SRV) { + + if (size < 10) + return -1; + + write16(p + 2, rr->rdata.srv.prio); /* Priority */ + write16(p + 4, rr->rdata.srv.weight); /* Weight */ + write16(p + 6, rr->rdata.srv.port); /* Port */ + + /* Target */ + len = print_name(pkt, size - 8, p + 8, &rr->rdata.srv.target, tab); + if (len < 0) + return -1; + + /* RDLEN */ + write16(p, (pj_uint16_t)(len + 6)); + + p += (len + 8); + size -= (len + 8); + + } else { + pj_assert(!"Not supported"); + return -1; + } + + return (int)(p - pos); +} + +static int print_packet(const pj_dns_parsed_packet *rec, pj_uint8_t *pkt, int size) +{ + pj_uint8_t *p = pkt; + struct label_tab tab; + int i, len; + + tab.count = 0; + + pj_assert(sizeof(pj_dns_hdr) == 12); + if (size < (int)sizeof(pj_dns_hdr)) + return -1; + + /* Initialize header */ + write16(p + 0, rec->hdr.id); + write16(p + 2, rec->hdr.flags); + write16(p + 4, rec->hdr.qdcount); + write16(p + 6, rec->hdr.anscount); + write16(p + 8, rec->hdr.nscount); + write16(p + 10, rec->hdr.arcount); + + p = pkt + sizeof(pj_dns_hdr); + size -= sizeof(pj_dns_hdr); + + /* Print queries */ + for (i = 0; i < rec->hdr.qdcount; ++i) { + + len = print_name(pkt, size, p, &rec->q[i].name, &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + + if (size < 4) + return -1; + + /* Set type */ + write16(p + 0, (pj_uint16_t)rec->q[i].type); + + /* Set class (IN=1) */ + pj_assert(rec->q[i].dnsclass == 1); + write16(p + 2, rec->q[i].dnsclass); + + p += 4; + } + + /* Print answers */ + for (i = 0; i < rec->hdr.anscount; ++i) { + len = print_rr(pkt, size, p, &rec->ans[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + /* Print NS records */ + for (i = 0; i < rec->hdr.nscount; ++i) { + len = print_rr(pkt, size, p, &rec->ns[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + /* Print additional records */ + for (i = 0; i < rec->hdr.arcount; ++i) { + len = print_rr(pkt, size, p, &rec->arr[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + return (int)(p - pkt); +} + +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status) +{ + pj_dns_server *srv; + pj_pool_t *pool; + pj_dns_parsed_packet *req; + pj_dns_parsed_packet ans; + struct rr *rr; + pj_ssize_t pkt_len; + unsigned i; + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + srv = (pj_dns_server *)pj_activesock_get_user_data(asock); + pool = pj_pool_create(srv->pf, "dnssrvrx", 512, 256, NULL); + + status = pj_dns_parse_packet(pool, data, (unsigned)size, &req); + if (status != PJ_SUCCESS) { + char addrinfo[PJ_INET6_ADDRSTRLEN + 10]; + pj_sockaddr_print(src_addr, addrinfo, sizeof(addrinfo), 3); + PJ_PERROR(4, (THIS_FILE, status, "Error parsing query from %s", addrinfo)); + goto on_return; + } + + /* Init answer */ + pj_bzero(&ans, sizeof(ans)); + ans.hdr.id = req->hdr.id; + ans.hdr.qdcount = 1; + ans.q = (pj_dns_parsed_query *)PJ_POOL_ALLOC_T(pool, pj_dns_parsed_query); + pj_memcpy(ans.q, req->q, sizeof(pj_dns_parsed_query)); + + if (req->hdr.qdcount != 1) { + ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_FORMERR); + goto send_pkt; + } + + if (req->q[0].dnsclass != PJ_DNS_CLASS_IN) { + ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NOTIMPL); + goto send_pkt; + } + + /* Find the record */ + rr = find_rr(srv, req->q->dnsclass, req->q->type, &req->q->name); + if (rr == NULL) { + ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); + goto send_pkt; + } + + /* Init answer record */ + ans.hdr.anscount = 0; + ans.ans = (pj_dns_parsed_rr *)pj_pool_calloc(pool, MAX_ANS, sizeof(pj_dns_parsed_rr)); + + /* DNS SRV query needs special treatment since it returns multiple + * records + */ + if (req->q->type == PJ_DNS_TYPE_SRV) { + struct rr *r; + + r = srv->rr_list.next; + while (r != &srv->rr_list) { + if (r->rec.dnsclass == req->q->dnsclass && r->rec.type == PJ_DNS_TYPE_SRV && + pj_stricmp(&r->rec.name, &req->q->name) == 0 && ans.hdr.anscount < MAX_ANS) { + pj_memcpy(&ans.ans[ans.hdr.anscount], &r->rec, sizeof(pj_dns_parsed_rr)); + ++ans.hdr.anscount; + } + r = r->next; + } + } else { + /* Otherwise just copy directly from the server record */ + pj_memcpy(&ans.ans[ans.hdr.anscount], &rr->rec, sizeof(pj_dns_parsed_rr)); + ++ans.hdr.anscount; + } + + /* For each CNAME entry, add A entry */ + for (i = 0; i < ans.hdr.anscount && ans.hdr.anscount < MAX_ANS; ++i) { + if (ans.ans[i].type == PJ_DNS_TYPE_CNAME) { + struct rr *r; + + r = find_rr(srv, ans.ans[i].dnsclass, PJ_DNS_TYPE_A, &ans.ans[i].name); + pj_memcpy(&ans.ans[ans.hdr.anscount], &r->rec, sizeof(pj_dns_parsed_rr)); + ++ans.hdr.anscount; + } + } + +send_pkt: + pkt_len = print_packet(&ans, (pj_uint8_t *)data, MAX_PKT); + if (pkt_len < 1) { + PJ_LOG(4, (THIS_FILE, "Error: answer too large")); + goto on_return; + } + + status = pj_activesock_sendto(srv->asock, &srv->send_key, data, &pkt_len, 0, src_addr, addr_len); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_PERROR(4, (THIS_FILE, status, "Error sending answer")); + goto on_return; + } + +on_return: + pj_pool_release(pool); + return PJ_TRUE; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c new file mode 100755 index 000000000..e19106d80 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* PJLIB_UTIL's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 +static const struct { + int code; + const char *msg; +} err_str[] = { + /* STUN errors */ + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNRESOLVE, "Unable to resolve STUN server"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINMSGTYPE, "Unknown STUN message type"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINMSGLEN, "Invalid STUN message length"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINATTRLEN, "STUN attribute length error"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINATTRTYPE, "Invalid STUN attribute type"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNININDEX, "Invalid STUN server/socket index"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOBINDRES, "No STUN binding response in the message"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNRECVERRATTR, "Received STUN error attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOMAP, "No STUN mapped address attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOTRESPOND, "Received no response from STUN server"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNSYMMETRIC, "Symetric NAT detected by STUN"), + + /* XML errors */ + PJ_BUILD_ERR(PJLIB_UTIL_EINXML, "Invalid XML message"), + + /* JSON errors */ + PJ_BUILD_ERR(PJLIB_UTIL_EINJSON, "Invalid JSON document"), + + /* DNS errors */ + PJ_BUILD_ERR(PJLIB_UTIL_EDNSQRYTOOSMALL, "DNS query packet buffer is too small"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINSIZE, "Invalid DNS packet length"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINCLASS, "Invalid DNS class"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINNAMEPTR, "Invalid DNS name pointer"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINNSADDR, "Invalid DNS nameserver address"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSNONS, "No nameserver is in DNS resolver"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSNOWORKINGNS, "No working DNS nameserver"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSNOANSWERREC, "No answer record in the DNS response"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINANSWER, "Invalid DNS answer"), + + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_FORMERR, "DNS \"Format error\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_SERVFAIL, "DNS \"Server failure\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NXDOMAIN, "DNS \"Name Error\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NOTIMPL, "DNS \"Not Implemented\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_REFUSED, "DNS \"Refused\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_YXDOMAIN, "DNS \"The name exists\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_YXRRSET, "DNS \"The RRset (name, type) exists\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NXRRSET, "DNS \"The RRset (name, type) does not exist\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NOTAUTH, "DNS \"Not authorized\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NOTZONE, "DNS \"The zone specified is not a zone\""), + + /* STUN */ + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNTOOMANYATTR, "Too many STUN attributes"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNUNKNOWNATTR, "Unknown STUN attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINADDRLEN, "Invalid STUN socket address length"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOTRESPONSE, "Expecting STUN response message"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINVALIDID, "STUN transaction ID mismatch"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOHANDLER, "Unable to find STUN handler for the request"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNMSGINTPOS, "Found non-FINGERPRINT attr. after MESSAGE-INTEGRITY"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNFINGERPOS, "Found STUN attribute after FINGERPRINT"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOUSERNAME, "Missing STUN USERNAME attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNMSGINT, "Missing/invalid STUN MESSAGE-INTEGRITY attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNDUPATTR, "Found duplicate STUN attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOREALM, "Missing STUN REALM attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNONCE, "Missing/stale STUN NONCE attribute value"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNTSXFAILED, "STUN transaction terminates with failure"), + + /* HTTP Client */ + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINURL, "Invalid URL format"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINPORT, "Invalid URL port number"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINCHDR, "Incomplete response header received"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINSBUF, "Insufficient buffer"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPLOST, "Connection lost"), + + /* CLI */ + PJ_BUILD_ERR(PJ_CLI_EEXIT, "Exit current session"), + PJ_BUILD_ERR(PJ_CLI_EMISSINGARG, "Missing argument"), + PJ_BUILD_ERR(PJ_CLI_ETOOMANYARGS, "Too many arguments"), + PJ_BUILD_ERR(PJ_CLI_EINVARG, "Invalid argument"), + PJ_BUILD_ERR(PJ_CLI_EBADNAME, "Command name already exists"), + PJ_BUILD_ERR(PJ_CLI_EBADID, "Command id already exists"), + PJ_BUILD_ERR(PJ_CLI_EBADXML, "Invalid XML format"), + PJ_BUILD_ERR(PJ_CLI_ETELNETLOST, "Connection lost"), +}; +#endif /* PJ_HAS_ERROR_STRING */ + +/* + * pjlib_util_strerror() + */ +pj_str_t pjlib_util_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + if (statcode >= PJLIB_UTIL_ERRNO_START && statcode < PJLIB_UTIL_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n / 2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid + 1; + n -= (half + 1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char *)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown pjlib-util error %d", statcode); + if (errstr.slen < 1 || errstr.slen >= (pj_ssize_t)bufsize) + errstr.slen = bufsize - 1; + return errstr; +} + +PJ_DEF(pj_status_t) pjlib_util_init(void) +{ + pj_status_t status; + + status = pj_register_strerror(PJLIB_UTIL_ERRNO_START, PJ_ERRNO_SPACE_SIZE, &pjlib_util_strerror); + pj_assert(status == PJ_SUCCESS); + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c new file mode 100755 index 000000000..5fbe0071a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c @@ -0,0 +1,644 @@ +/* + * pj_getopt entry points + * + * modified by Mike Borella + */ + +#include +#include + +/* Internal only. Users should not call this directly. */ +static int _getopt_internal(int argc, char *const *argv, const char *shortopts, const struct pj_getopt_option *longopts, + int *longind, int long_only); + +/* pj_getopt_long and pj_getopt_long_only entry points for GNU pj_getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 + +int pj_getopt_long(int argc, char *const *argv, const char *options, const struct pj_getopt_option *long_options, + int *opt_index) +{ + return _getopt_internal(argc, argv, options, long_options, opt_index, 0); +} + +/* Like pj_getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int pj_getopt(int argc, char *const *argv, const char *optstring) +{ + return _getopt_internal(argc, argv, optstring, (const struct pj_getopt_option *)0, (int *)0, 0); +} + +#define _(msgid) (msgid) + +/* This version of `pj_getopt' appears to the caller like standard Unix `pj_getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `pj_getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +/* For communication from `pj_getopt' to the caller. + When `pj_getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *pj_optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `pj_getopt'. + + On entry to `pj_getopt', zero means this is the first call; initialize. + + When `pj_getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `pj_optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int pj_optind = 1; + +/* Formerly, initialization of pj_getopt depended on pj_optind==0, which + causes problems with re-calling pj_getopt as programs generally don't + know that. */ + +static int __getopt_initialized = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own pj_getopt implementation. */ + +int pj_optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `pj_getopt' to return -1 with `pj_optind' != ARGC. */ + +static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +static char *my_index(const char *str, int chr) +{ + while (*str) { + if (*str == chr) + return (char *)str; + str++; + } + return 0; +} + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#define SWAP_FLAGS(ch1, ch2) + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,pj_optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void exchange(char **argv) +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = pj_optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) { + if (top - middle > middle - bottom) { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS(bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } else { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS(bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (pj_optind - last_nonopt); + last_nonopt = pj_optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char *_getopt_initialize(int argc, char *const *argv, const char *optstring) +{ + PJ_UNUSED_ARG(argc); + PJ_UNUSED_ARG(argv); + + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = pj_optind; + + nextchar = NULL; + + // posixly_correct = getenv ("POSIXLY_CORRECT"); + posixly_correct = NULL; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') { + ordering = RETURN_IN_ORDER; + ++optstring; + } else if (optstring[0] == '+') { + ordering = REQUIRE_ORDER; + ++optstring; + } else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `pj_getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `pj_getopt' finds another option character, it returns that character, + updating `pj_optind' and `nextchar' so that the next call to `pj_getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `pj_getopt' returns -1. + Then `pj_optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `pj_opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `pj_optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `pj_optarg', otherwise `pj_optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `pj_getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct pj_getopt_option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +static int _getopt_internal(int argc, char *const *argv, const char *optstring, const struct pj_getopt_option *longopts, + int *longind, int long_only) +{ + pj_optarg = NULL; + + if (pj_optind == 0 || !__getopt_initialized) { + if (pj_optind == 0) + pj_optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize(argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[pj_optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#define NONOPTION_P (argv[pj_optind][0] != '-' || argv[pj_optind][1] == '\0') + + if (nextchar == NULL || *nextchar == '\0') { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > pj_optind) + last_nonopt = pj_optind; + if (first_nonopt > pj_optind) + first_nonopt = pj_optind; + + if (ordering == PERMUTE) { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != pj_optind) + exchange((char **)argv); + else if (last_nonopt != pj_optind) + first_nonopt = pj_optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (pj_optind < argc && NONOPTION_P) + pj_optind++; + last_nonopt = pj_optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (pj_optind != argc && !pj_ansi_strcmp(argv[pj_optind], "--")) { + pj_optind++; + + if (first_nonopt != last_nonopt && last_nonopt != pj_optind) + exchange((char **)argv); + else if (first_nonopt == last_nonopt) + first_nonopt = pj_optind; + last_nonopt = argc; + + pj_optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (pj_optind == argc) { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + pj_optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) { + if (ordering == REQUIRE_ORDER) + return -1; + pj_optarg = argv[pj_optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[pj_optind] + 1 + (longopts != NULL && argv[pj_optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL && (argv[pj_optind][1] == '-' || + (long_only && (argv[pj_optind][2] || !my_index(optstring, argv[pj_optind][1]))))) { + char *nameend; + const struct pj_getopt_option *p; + const struct pj_getopt_option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, nextchar, nameend - nextchar)) { + if ((unsigned int)(nameend - nextchar) == (unsigned int)strlen(p->name)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) { + nextchar += strlen(nextchar); + pj_optind++; + pj_optopt = 0; + return '?'; + } + + if (pfound != NULL) { + option_index = indfound; + pj_optind++; + if (*nameend) { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + pj_optarg = nameend + 1; + else { + nextchar += strlen(nextchar); + + pj_optopt = pfound->val; + return '?'; + } + } else if (pfound->has_arg == 1) { + if (pj_optind < argc) + pj_optarg = argv[pj_optind++]; + else { + nextchar += strlen(nextchar); + pj_optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen(nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not pj_getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[pj_optind][1] == '-' || my_index(optstring, *nextchar) == NULL) { + nextchar = (char *)""; + pj_optind++; + pj_optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index(optstring, c); + + /* Increment `pj_optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++pj_optind; + + if (temp == NULL || c == ':') { + pj_optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') { + char *nameend; + const struct pj_getopt_option *p; + const struct pj_getopt_option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') { + pj_optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + pj_optind++; + } else if (pj_optind == argc) { + pj_optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } else + /* We already incremented `pj_optind' once; + increment it again when taking next ARGV-elt as argument. */ + pj_optarg = argv[pj_optind++]; + + /* pj_optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = pj_optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, nextchar, nameend - nextchar)) { + if ((unsigned int)(nameend - nextchar) == strlen(p->name)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) { + nextchar += strlen(nextchar); + pj_optind++; + return '?'; + } + if (pfound != NULL) { + option_index = indfound; + if (*nameend) { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + pj_optarg = nameend + 1; + else { + nextchar += strlen(nextchar); + return '?'; + } + } else if (pfound->has_arg == 1) { + if (pj_optind < argc) + pj_optarg = argv[pj_optind++]; + else { + nextchar += strlen(nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen(nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') { + if (temp[2] == ':') { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') { + pj_optarg = nextchar; + pj_optind++; + } else + pj_optarg = NULL; + nextchar = NULL; + } else { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') { + pj_optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + pj_optind++; + } else if (pj_optind == argc) { + pj_optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } else + /* We already incremented `pj_optind' once; + increment it again when taking next ARGV-elt as argument. */ + pj_optarg = argv[pj_optind++]; + nextchar = NULL; + } + } + return c; + } +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c new file mode 100755 index 000000000..b66a385a2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pj_hmac_md5_init(pj_hmac_md5_context *hctx, const pj_uint8_t *key, unsigned key_len) +{ + pj_uint8_t k_ipad[64]; + pj_uint8_t tk[16]; + int i; + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + pj_md5_context tctx; + + pj_md5_init(&tctx); + pj_md5_update(&tctx, key, key_len); + pj_md5_final(&tctx, tk); + + key = tk; + key_len = 16; + } + + /* + * HMAC = H(K XOR opad, H(K XOR ipad, text)) + */ + + /* start out by storing key in pads */ + pj_bzero(k_ipad, sizeof(k_ipad)); + pj_bzero(hctx->k_opad, sizeof(hctx->k_opad)); + pj_memcpy(k_ipad, key, key_len); + pj_memcpy(hctx->k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + hctx->k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + pj_md5_init(&hctx->context); + pj_md5_update(&hctx->context, k_ipad, 64); +} + +PJ_DEF(void) pj_hmac_md5_update(pj_hmac_md5_context *hctx, const pj_uint8_t *input, unsigned input_len) +{ + pj_md5_update(&hctx->context, input, input_len); +} + +PJ_DEF(void) pj_hmac_md5_final(pj_hmac_md5_context *hctx, pj_uint8_t digest[16]) +{ + pj_md5_final(&hctx->context, digest); + + /* + * perform outer MD5 + */ + pj_md5_init(&hctx->context); + pj_md5_update(&hctx->context, hctx->k_opad, 64); + pj_md5_update(&hctx->context, digest, 16); + pj_md5_final(&hctx->context, digest); +} + +PJ_DEF(void) +pj_hmac_md5(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, pj_uint8_t digest[16]) +{ + pj_hmac_md5_context ctx; + + pj_hmac_md5_init(&ctx, key, key_len); + pj_hmac_md5_update(&ctx, input, input_len); + pj_hmac_md5_final(&ctx, digest); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c new file mode 100755 index 000000000..abea013b5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pj_hmac_sha1_init(pj_hmac_sha1_context *hctx, const pj_uint8_t *key, unsigned key_len) +{ + pj_uint8_t k_ipad[64]; + pj_uint8_t tk[20]; + unsigned i; + + /* if key is longer than 64 bytes reset it to key=SHA1(key) */ + if (key_len > 64) { + pj_sha1_context tctx; + + pj_sha1_init(&tctx); + pj_sha1_update(&tctx, key, key_len); + pj_sha1_final(&tctx, tk); + + key = tk; + key_len = 20; + } + + /* + * HMAC = H(K XOR opad, H(K XOR ipad, text)) + */ + + /* start out by storing key in pads */ + pj_bzero(k_ipad, sizeof(k_ipad)); + pj_bzero(hctx->k_opad, sizeof(hctx->k_opad)); + pj_memcpy(k_ipad, key, key_len); + pj_memcpy(hctx->k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + hctx->k_opad[i] ^= 0x5c; + } + /* + * perform inner SHA1 + */ + pj_sha1_init(&hctx->context); + pj_sha1_update(&hctx->context, k_ipad, 64); +} + +PJ_DEF(void) pj_hmac_sha1_update(pj_hmac_sha1_context *hctx, const pj_uint8_t *input, unsigned input_len) +{ + pj_sha1_update(&hctx->context, input, input_len); +} + +PJ_DEF(void) pj_hmac_sha1_final(pj_hmac_sha1_context *hctx, pj_uint8_t digest[20]) +{ + pj_sha1_final(&hctx->context, digest); + + /* + * perform outer SHA1 + */ + pj_sha1_init(&hctx->context); + pj_sha1_update(&hctx->context, hctx->k_opad, 64); + pj_sha1_update(&hctx->context, digest, 20); + pj_sha1_final(&hctx->context, digest); +} + +PJ_DEF(void) +pj_hmac_sha1(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, + pj_uint8_t digest[20]) +{ + pj_hmac_sha1_context ctx; + + pj_hmac_sha1_init(&ctx, key, key_len); + pj_hmac_sha1_update(&ctx, input, input_len); + pj_hmac_sha1_final(&ctx, digest); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c new file mode 100755 index 000000000..a6986a836 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c @@ -0,0 +1,1507 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "http_client.c" + +#if 0 + /* Enable some tracing */ +#define TRACE_(arg) PJ_LOG(3, arg) +#else +#define TRACE_(arg) +#endif + +#define NUM_PROTOCOL 2 +#define HTTP_1_0 "1.0" +#define HTTP_1_1 "1.1" +#define CONTENT_LENGTH "Content-Length" +/* Buffer size for sending/receiving messages. */ +#define BUF_SIZE 2048 +/* Initial data buffer size to store the data in case content- + * length is not specified in the server's response. + */ +#define INITIAL_DATA_BUF_SIZE 2048 +#define INITIAL_POOL_SIZE 1024 +#define POOL_INCREMENT_SIZE 512 + +enum http_protocol { PROTOCOL_HTTP, PROTOCOL_HTTPS }; + +static const char *http_protocol_names[NUM_PROTOCOL] = {"HTTP", "HTTPS"}; + +static const unsigned int http_default_port[NUM_PROTOCOL] = {80, 443}; + +enum http_method { HTTP_GET, HTTP_PUT, HTTP_DELETE }; + +static const char *http_method_names[3] = {"GET", "PUT", "DELETE"}; + +enum http_state { + IDLE, + CONNECTING, + SENDING_REQUEST, + SENDING_REQUEST_BODY, + REQUEST_SENT, + READING_RESPONSE, + READING_DATA, + READING_COMPLETE, + ABORTING, +}; + +enum auth_state { + AUTH_NONE, /* Not authenticating */ + AUTH_RETRYING, /* New request with auth has been submitted */ + AUTH_DONE /* Done retrying the request with auth. */ +}; + +struct pj_http_req { + pj_str_t url; /* Request URL */ + pj_http_url hurl; /* Parsed request URL */ + pj_sockaddr addr; /* The host's socket address */ + pj_http_req_param param; /* HTTP request parameters */ + pj_pool_t *pool; /* Pool to allocate memory from */ + pj_timer_heap_t *timer; /* Timer for timeout management */ + pj_ioqueue_t *ioqueue; /* Ioqueue to use */ + pj_http_req_callback cb; /* Callbacks */ + pj_activesock_t *asock; /* Active socket */ + pj_status_t error; /* Error status */ + pj_str_t buffer; /* Buffer to send/receive msgs */ + enum http_state state; /* State of the HTTP request */ + enum auth_state auth_state; /* Authentication state */ + pj_timer_entry timer_entry; /* Timer entry */ + pj_bool_t resolved; /* Whether URL's host is resolved */ + pj_http_resp response; /* HTTP response */ + pj_ioqueue_op_key_t op_key; + struct tcp_state { + /* Total data sent so far if the data is sent in segments (i.e. + * if on_send_data() is not NULL and if param.reqdata.total_size > 0) + */ + pj_size_t tot_chunk_size; + /* Size of data to be sent (in a single activesock operation).*/ + pj_size_t send_size; + /* Data size sent so far. */ + pj_size_t current_send_size; + /* Total data received so far. */ + pj_size_t current_read_size; + } tcp_state; +}; + +/* Start sending the request */ +static pj_status_t http_req_start_sending(pj_http_req *hreq); +/* Start reading the response */ +static pj_status_t http_req_start_reading(pj_http_req *hreq); +/* End the request */ +static pj_status_t http_req_end_request(pj_http_req *hreq); +/* Parse the header data and populate the header fields with the result. */ +static pj_status_t http_headers_parse(char *hdata, pj_size_t size, pj_http_headers *headers); +/* Parse the response */ +static pj_status_t http_response_parse(pj_pool_t *pool, pj_http_resp *response, void *data, pj_size_t size, + pj_size_t *remainder); +/* Restart the request with authentication */ +static void restart_req_with_auth(pj_http_req *hreq); +/* Parse authentication challenge */ +static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, pj_http_auth_chal *chal); + +static pj_uint16_t get_http_default_port(const pj_str_t *protocol) +{ + int i; + + for (i = 0; i < NUM_PROTOCOL; i++) { + if (!pj_stricmp2(protocol, http_protocol_names[i])) { + return (pj_uint16_t)http_default_port[i]; + } + } + return 0; +} + +static const char *get_protocol(const pj_str_t *protocol) +{ + int i; + + for (i = 0; i < NUM_PROTOCOL; i++) { + if (!pj_stricmp2(protocol, http_protocol_names[i])) { + return http_protocol_names[i]; + } + } + + /* Should not happen */ + pj_assert(0); + return NULL; +} + +/* Syntax error handler for parser. */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); // syntax error +} + +/* Callback when connection is established to the server */ +static pj_bool_t http_on_connect(pj_activesock_t *asock, pj_status_t status) +{ + pj_http_req *hreq = (pj_http_req *)pj_activesock_get_user_data(asock); + + if (hreq->state == ABORTING || hreq->state == IDLE) + return PJ_FALSE; + + if (status != PJ_SUCCESS) { + hreq->error = status; + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + /* OK, we are connected. Start sending the request */ + hreq->state = SENDING_REQUEST; + http_req_start_sending(hreq); + return PJ_TRUE; +} + +static pj_bool_t http_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *op_key, pj_ssize_t sent) +{ + pj_http_req *hreq = (pj_http_req *)pj_activesock_get_user_data(asock); + + PJ_UNUSED_ARG(op_key); + + if (hreq->state == ABORTING || hreq->state == IDLE) + return PJ_FALSE; + + if (sent <= 0) { + hreq->error = (sent < 0 ? (pj_status_t)-sent : PJLIB_UTIL_EHTTPLOST); + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + hreq->tcp_state.current_send_size += sent; + TRACE_( + (THIS_FILE, "\nData sent: %d out of %d bytes", hreq->tcp_state.current_send_size, hreq->tcp_state.send_size)); + if (hreq->tcp_state.current_send_size == hreq->tcp_state.send_size) { + /* Find out whether there is a request body to send. */ + if (hreq->param.reqdata.total_size > 0 || hreq->param.reqdata.size > 0) { + if (hreq->state == SENDING_REQUEST) { + /* Start sending the request body */ + hreq->state = SENDING_REQUEST_BODY; + hreq->tcp_state.tot_chunk_size = 0; + pj_assert(hreq->param.reqdata.total_size == 0 || + (hreq->param.reqdata.total_size > 0 && hreq->param.reqdata.size == 0)); + } else { + /* Continue sending the next chunk of the request body */ + hreq->tcp_state.tot_chunk_size += hreq->tcp_state.send_size; + if (hreq->tcp_state.tot_chunk_size == hreq->param.reqdata.total_size || + hreq->param.reqdata.total_size == 0) { + /* Finish sending all the chunks, start reading + * the response. + */ + hreq->state = REQUEST_SENT; + http_req_start_reading(hreq); + return PJ_TRUE; + } + } + if (hreq->param.reqdata.total_size > 0 && hreq->cb.on_send_data) { + /* Call the callback for the application to provide + * the next chunk of data to be sent. + */ + (*hreq->cb.on_send_data)(hreq, &hreq->param.reqdata.data, &hreq->param.reqdata.size); + /* Make sure the total data size given by the user does not + * exceed what the user originally said. + */ + pj_assert(hreq->tcp_state.tot_chunk_size + hreq->param.reqdata.size <= hreq->param.reqdata.total_size); + } + http_req_start_sending(hreq); + } else { + /* No request body, proceed to reading the server's response. */ + hreq->state = REQUEST_SENT; + http_req_start_reading(hreq); + } + } + return PJ_TRUE; +} + +static pj_bool_t http_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_http_req *hreq = (pj_http_req *)pj_activesock_get_user_data(asock); + + TRACE_((THIS_FILE, "\nData received: %d bytes", size)); + + if (hreq->state == ABORTING || hreq->state == IDLE) + return PJ_FALSE; + + if (hreq->state == READING_RESPONSE) { + pj_status_t st; + pj_size_t rem; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + hreq->error = status; + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + /* Parse the response. */ + st = http_response_parse(hreq->pool, &hreq->response, data, size, &rem); + if (st == PJLIB_UTIL_EHTTPINCHDR) { + /* If we already use up all our buffer and still + * hasn't received the whole header, return error + */ + if (size == BUF_SIZE) { + hreq->error = PJ_ETOOBIG; // response header size is too big + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + /* Keep the data if we do not get the whole response header */ + *remainder = size; + } else { + hreq->state = READING_DATA; + if (st != PJ_SUCCESS) { + /* Server replied with an invalid (or unknown) response + * format. We'll just pass the whole (unparsed) response + * to the user. + */ + hreq->response.data = data; + hreq->response.size = size - rem; + } + + /* If code is 401 or 407, find and parse WWW-Authenticate or + * Proxy-Authenticate header + */ + if (hreq->response.status_code == 401 || hreq->response.status_code == 407) { + const pj_str_t STR_WWW_AUTH = {"WWW-Authenticate", 16}; + const pj_str_t STR_PROXY_AUTH = {"Proxy-Authenticate", 18}; + pj_http_resp *response = &hreq->response; + pj_http_headers *hdrs = &response->headers; + unsigned i; + + status = PJ_ENOTFOUND; + for (i = 0; i < hdrs->count; i++) { + if (!pj_stricmp(&hdrs->header[i].name, &STR_WWW_AUTH) || + !pj_stricmp(&hdrs->header[i].name, &STR_PROXY_AUTH)) { + status = parse_auth_chal(hreq->pool, &hdrs->header[i].value, &response->auth_chal); + break; + } + } + + /* Check if we should perform authentication */ + if (status == PJ_SUCCESS && hreq->auth_state == AUTH_NONE && hreq->response.auth_chal.scheme.slen && + hreq->param.auth_cred.username.slen && + (hreq->param.auth_cred.scheme.slen == 0 || + !pj_stricmp(&hreq->response.auth_chal.scheme, &hreq->param.auth_cred.scheme)) && + (hreq->param.auth_cred.realm.slen == 0 || + !pj_stricmp(&hreq->response.auth_chal.realm, &hreq->param.auth_cred.realm))) { + /* Yes, authentication is required and we have been + * configured with credential. + */ + restart_req_with_auth(hreq); + if (hreq->auth_state == AUTH_RETRYING) { + /* We'll be resending the request with auth. This + * connection has been closed. + */ + return PJ_FALSE; + } + } + } + + /* We already received the response header, call the + * appropriate callback. + */ + if (hreq->cb.on_response) + (*hreq->cb.on_response)(hreq, &hreq->response); + hreq->response.data = NULL; + hreq->response.size = 0; + + if (rem > 0 || hreq->response.content_length == 0) + return http_on_data_read(asock, (rem == 0 ? NULL : (char *)data + size - rem), rem, PJ_SUCCESS, NULL); + } + + return PJ_TRUE; + } + + if (hreq->state != READING_DATA) + return PJ_FALSE; + if (hreq->cb.on_data_read) { + /* If application wishes to receive the data once available, call + * its callback. + */ + if (size > 0) + (*hreq->cb.on_data_read)(hreq, data, size); + } else { + if (hreq->response.size == 0) { + /* If we know the content length, allocate the data based + * on that, otherwise we'll use initial buffer size and grow + * it later if necessary. + */ + hreq->response.size = + (hreq->response.content_length == -1 ? INITIAL_DATA_BUF_SIZE : hreq->response.content_length); + hreq->response.data = pj_pool_alloc(hreq->pool, hreq->response.size); + } + + /* If the size of data received exceeds its current size, + * grow the buffer by a factor of 2. + */ + if (hreq->tcp_state.current_read_size + size > hreq->response.size) { + void *olddata = hreq->response.data; + + hreq->response.data = pj_pool_alloc(hreq->pool, hreq->response.size << 1); + pj_memcpy(hreq->response.data, olddata, hreq->response.size); + hreq->response.size <<= 1; + } + + /* Append the response data. */ + pj_memcpy((char *)hreq->response.data + hreq->tcp_state.current_read_size, data, size); + } + hreq->tcp_state.current_read_size += size; + + /* If the total data received so far is equal to the content length + * or if it's already EOF. + */ + if ((hreq->response.content_length >= 0 && + (pj_ssize_t)hreq->tcp_state.current_read_size >= hreq->response.content_length) || + (status == PJ_EEOF && hreq->response.content_length == -1)) { + /* Finish reading */ + http_req_end_request(hreq); + hreq->response.size = hreq->tcp_state.current_read_size; + + /* HTTP request is completed, call the callback. */ + if (hreq->cb.on_complete) { + (*hreq->cb.on_complete)(hreq, PJ_SUCCESS, &hreq->response); + } + + return PJ_FALSE; + } + + /* Error status or premature EOF. */ + if ((status != PJ_SUCCESS && status != PJ_EPENDING && status != PJ_EEOF) || + (status == PJ_EEOF && hreq->response.content_length > -1)) { + hreq->error = status; + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + return PJ_TRUE; +} + +/* Callback to be called when query has timed out */ +static void on_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pj_http_req *hreq = (pj_http_req *)entry->user_data; + + PJ_UNUSED_ARG(timer_heap); + + /* Recheck that the request is still not completed, since there is a + * slight possibility of race condition (timer elapsed while at the + * same time response arrives). + */ + if (hreq->state == READING_COMPLETE) { + /* Yeah, we finish on time */ + return; + } + + /* Invalidate id. */ + hreq->timer_entry.id = 0; + + /* Request timed out. */ + hreq->error = PJ_ETIMEDOUT; + pj_http_req_cancel(hreq, PJ_TRUE); +} + +/* Parse authentication challenge */ +static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, pj_http_auth_chal *chal) +{ + pj_scanner scanner; + const pj_str_t REALM_STR = {"realm", 5}, NONCE_STR = {"nonce", 5}, ALGORITHM_STR = {"algorithm", 9}, + STALE_STR = {"stale", 5}, QOP_STR = {"qop", 3}, OPAQUE_STR = {"opaque", 6}; + pj_status_t status = PJ_SUCCESS; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, input->ptr, input->slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + PJ_TRY + { + /* Get auth scheme */ + if (*scanner.curptr == '"') { + pj_scan_get_quote(&scanner, '"', '"', &chal->scheme); + chal->scheme.ptr++; + chal->scheme.slen -= 2; + } else { + pj_scan_get_until_chr(&scanner, " \t\r\n", &chal->scheme); + } + + /* Loop parsing all parameters */ + for (;;) { + const char *end_param = ", \t\r\n;"; + pj_str_t name, value; + + /* Get pair of parameter name and value */ + value.ptr = NULL; + value.slen = 0; + pj_scan_get_until_chr(&scanner, "=, \t\r\n", &name); + if (*scanner.curptr == '=') { + pj_scan_get_char(&scanner); + if (!pj_scan_is_eof(&scanner)) { + if (*scanner.curptr == '"' || *scanner.curptr == '\'') { + int quote_char = *scanner.curptr; + pj_scan_get_quote(&scanner, quote_char, quote_char, &value); + value.ptr++; + value.slen -= 2; + } else if (!strchr(end_param, *scanner.curptr)) { + pj_scan_get_until_chr(&scanner, end_param, &value); + } + } + value = pj_str_unescape(pool, &value); + } + + if (!pj_stricmp(&name, &REALM_STR)) { + chal->realm = value; + + } else if (!pj_stricmp(&name, &NONCE_STR)) { + chal->nonce = value; + + } else if (!pj_stricmp(&name, &ALGORITHM_STR)) { + chal->algorithm = value; + + } else if (!pj_stricmp(&name, &OPAQUE_STR)) { + chal->opaque = value; + + } else if (!pj_stricmp(&name, &QOP_STR)) { + chal->qop = value; + + } else if (!pj_stricmp(&name, &STALE_STR)) { + chal->stale = value.slen && (*value.ptr != '0') && (*value.ptr != 'f') && (*value.ptr != 'F'); + } + + /* Eat comma */ + if (!pj_scan_is_eof(&scanner) && *scanner.curptr == ',') + pj_scan_get_char(&scanner); + else + break; + } + } + PJ_CATCH_ANY + { + status = PJ_GET_EXCEPTION(); + pj_bzero(chal, sizeof(*chal)); + TRACE_((THIS_FILE, "Error: parsing of auth header failed")); + } + PJ_END; + pj_scan_fini(&scanner); + return status; +} + +/* The same as #pj_http_headers_add_elmt() with char * as + * its parameters. + */ +PJ_DEF(pj_status_t) pj_http_headers_add_elmt2(pj_http_headers *headers, char *name, char *val) +{ + pj_str_t f, v; + pj_cstr(&f, name); + pj_cstr(&v, val); + return pj_http_headers_add_elmt(headers, &f, &v); +} + +PJ_DEF(pj_status_t) pj_http_headers_add_elmt(pj_http_headers *headers, pj_str_t *name, pj_str_t *val) +{ + PJ_ASSERT_RETURN(headers && name && val, PJ_FALSE); + if (headers->count >= PJ_HTTP_HEADER_SIZE) + return PJ_ETOOMANY; + pj_strassign(&headers->header[headers->count].name, name); + pj_strassign(&headers->header[headers->count++].value, val); + return PJ_SUCCESS; +} + +static pj_status_t http_response_parse(pj_pool_t *pool, pj_http_resp *response, void *data, pj_size_t size, + pj_size_t *remainder) +{ + pj_size_t i; + char *cptr; + char *end_status, *newdata; + pj_scanner scanner; + pj_str_t s; + const pj_str_t STR_CONTENT_LENGTH = {CONTENT_LENGTH, 14}; + pj_status_t status; + + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(response, PJ_EINVAL); + if (size < 2) + return PJLIB_UTIL_EHTTPINCHDR; + /* Detect whether we already receive the response's status-line + * and its headers. We're looking for a pair of CRLFs. A pair of + * LFs is also supported although it is not RFC standard. + */ + cptr = (char *)data; + for (i = 1, cptr++; i < size; i++, cptr++) { + if (*cptr == '\n') { + if (*(cptr - 1) == '\n') + break; + if (*(cptr - 1) == '\r') { + if (i >= 3 && *(cptr - 2) == '\n' && *(cptr - 3) == '\r') + break; + } + } + } + if (i == size) + return PJLIB_UTIL_EHTTPINCHDR; + *remainder = size - 1 - i; + + pj_bzero(response, sizeof(*response)); + response->content_length = -1; + + newdata = (char *)pj_pool_alloc(pool, i); + pj_memcpy(newdata, data, i); + + /* Parse the status-line. */ + pj_scan_init(&scanner, newdata, i, 0, &on_syntax_error); + PJ_TRY + { + pj_scan_get_until_ch(&scanner, ' ', &response->version); + pj_scan_advance_n(&scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(&scanner, ' ', &s); + response->status_code = (pj_uint16_t)pj_strtoul(&s); + pj_scan_advance_n(&scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(&scanner, '\n', &response->reason); + if (response->reason.ptr[response->reason.slen - 1] == '\r') + response->reason.slen--; + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + end_status = scanner.curptr; + pj_scan_fini(&scanner); + + /* Parse the response headers. */ + size = i - 2 - (end_status - newdata); + if (size > 0) { + status = http_headers_parse(end_status + 1, size, &response->headers); + } else { + status = PJ_SUCCESS; + } + + /* Find content-length header field. */ + for (i = 0; i < response->headers.count; i++) { + if (!pj_stricmp(&response->headers.header[i].name, &STR_CONTENT_LENGTH)) { + response->content_length = pj_strtoul(&response->headers.header[i].value); + /* If content length is zero, make sure that it is because the + * header value is really zero and not due to parsing error. + */ + if (response->content_length == 0) { + if (pj_strcmp2(&response->headers.header[i].value, "0")) { + response->content_length = -1; + } + } + break; + } + } + + return status; +} + +static pj_status_t http_headers_parse(char *hdata, pj_size_t size, pj_http_headers *headers) +{ + pj_scanner scanner; + pj_str_t s, s2; + pj_status_t status; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(headers, PJ_EINVAL); + + pj_scan_init(&scanner, hdata, size, 0, &on_syntax_error); + + /* Parse each line of header field consisting of header field name and + * value, separated by ":" and any number of white spaces. + */ + PJ_TRY + { + do { + pj_scan_get_until_chr(&scanner, ":\n", &s); + if (*scanner.curptr == ':') { + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + pj_scan_get_until_ch(&scanner, '\n', &s2); + if (s2.ptr[s2.slen - 1] == '\r') + s2.slen--; + status = pj_http_headers_add_elmt(headers, &s, &s2); + if (status != PJ_SUCCESS) + PJ_THROW(status); + } + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + /* Finish parsing */ + if (pj_scan_is_eof(&scanner)) + break; + } while (1); + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_http_req_param_default(pj_http_req_param *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + param->addr_family = pj_AF_INET(); + pj_strset2(¶m->method, (char *)http_method_names[HTTP_GET]); + pj_strset2(¶m->version, (char *)HTTP_1_0); + param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT; + pj_time_val_normalize(¶m->timeout); + param->max_retries = 3; +} + +/* Get the location of '@' character to indicate the end of + * user:passwd part of an URI. If user:passwd part is not + * present, NULL will be returned. + */ +static char *get_url_at_pos(const char *str, pj_size_t len) +{ + const char *end = str + len; + const char *p = str; + + /* skip scheme: */ + while (p != end && *p != '/') + ++p; + if (p != end && *p == '/') + ++p; + if (p != end && *p == '/') + ++p; + if (p == end) + return NULL; + + for (; p != end; ++p) { + switch (*p) { + case '/': + return NULL; + case '@': + return (char *)p; + } + } + + return NULL; +} + +PJ_DEF(pj_status_t) pj_http_req_parse_url(const pj_str_t *url, pj_http_url *hurl) +{ + pj_scanner scanner; + pj_size_t len = url->slen; + PJ_USE_EXCEPTION; + + if (!len) + return -1; + + pj_bzero(hurl, sizeof(*hurl)); + pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error); + + PJ_TRY + { + pj_str_t s; + + /* Exhaust any whitespaces. */ + pj_scan_skip_whitespace(&scanner); + + /* Parse the protocol */ + pj_scan_get_until_ch(&scanner, ':', &s); + if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTP])) { + pj_strset2(&hurl->protocol, (char *)http_protocol_names[PROTOCOL_HTTP]); + } else if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTPS])) { + pj_strset2(&hurl->protocol, (char *)http_protocol_names[PROTOCOL_HTTPS]); + } else { + PJ_THROW(PJ_ENOTSUP); // unsupported protocol + } + + if (pj_scan_strcmp(&scanner, "://", 3)) { + PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name + } + pj_scan_advance_n(&scanner, 3, PJ_FALSE); + + if (get_url_at_pos(url->ptr, url->slen)) { + /* Parse username and password */ + pj_scan_get_until_chr(&scanner, ":@", &hurl->username); + if (*scanner.curptr == ':') { + pj_scan_get_char(&scanner); + pj_scan_get_until_chr(&scanner, "@", &hurl->passwd); + } else { + hurl->passwd.slen = 0; + } + pj_scan_get_char(&scanner); + } + + /* Parse the host and port number (if any) */ + pj_scan_get_until_chr(&scanner, ":/", &s); + pj_strassign(&hurl->host, &s); + if (hurl->host.slen == 0) + PJ_THROW(PJ_EINVAL); + if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') { + /* No port number specified */ + /* Assume default http/https port number */ + hurl->port = get_http_default_port(&hurl->protocol); + pj_assert(hurl->port > 0); + } else { + pj_scan_advance_n(&scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(&scanner, '/', &s); + /* Parse the port number */ + hurl->port = (pj_uint16_t)pj_strtoul(&s); + if (!hurl->port) + PJ_THROW(PJLIB_UTIL_EHTTPINPORT); // invalid port number + } + + if (!pj_scan_is_eof(&scanner)) { + hurl->path.ptr = scanner.curptr; + hurl->path.slen = scanner.end - scanner.curptr; + } else { + /* no path, append '/' */ + pj_cstr(&hurl->path, "/"); + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_http_req_set_timeout(pj_http_req *http_req, const pj_time_val *timeout) +{ + pj_memcpy(&http_req->param.timeout, timeout, sizeof(*timeout)); +} + +PJ_DEF(pj_status_t) +pj_http_req_create(pj_pool_t *pool, const pj_str_t *url, pj_timer_heap_t *timer, pj_ioqueue_t *ioqueue, + const pj_http_req_param *param, const pj_http_req_callback *hcb, pj_http_req **http_req) +{ + pj_pool_t *own_pool; + pj_http_req *hreq; + char *at_pos; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && url && timer && ioqueue && hcb && http_req, PJ_EINVAL); + + *http_req = NULL; + own_pool = pj_pool_create(pool->factory, NULL, INITIAL_POOL_SIZE, POOL_INCREMENT_SIZE, NULL); + hreq = PJ_POOL_ZALLOC_T(own_pool, struct pj_http_req); + if (!hreq) + return PJ_ENOMEM; + + /* Initialization */ + hreq->pool = own_pool; + hreq->ioqueue = ioqueue; + hreq->timer = timer; + hreq->asock = NULL; + pj_memcpy(&hreq->cb, hcb, sizeof(*hcb)); + hreq->state = IDLE; + hreq->resolved = PJ_FALSE; + hreq->buffer.ptr = NULL; + pj_timer_entry_init(&hreq->timer_entry, 0, hreq, &on_timeout); + + /* Initialize parameter */ + if (param) { + pj_memcpy(&hreq->param, param, sizeof(*param)); + /* TODO: validate the param here + * Should we validate the method as well? If yes, based on all HTTP + * methods or based on supported methods only? For the later, one + * drawback would be that you can't use this if the method is not + * officially supported + */ + PJ_ASSERT_RETURN(hreq->param.addr_family == pj_AF_UNSPEC() || hreq->param.addr_family == pj_AF_INET() || + hreq->param.addr_family == pj_AF_INET6(), + PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(!pj_strcmp2(&hreq->param.version, HTTP_1_0) || !pj_strcmp2(&hreq->param.version, HTTP_1_1), + PJ_ENOTSUP); + pj_time_val_normalize(&hreq->param.timeout); + } else { + pj_http_req_param_default(&hreq->param); + } + + /* Parse the URL */ + if (!pj_strdup_with_null(hreq->pool, &hreq->url, url)) { + pj_pool_release(hreq->pool); + return PJ_ENOMEM; + } + status = pj_http_req_parse_url(&hreq->url, &hreq->hurl); + if (status != PJ_SUCCESS) { + pj_pool_release(hreq->pool); + return status; // Invalid URL supplied + } + + /* If URL contains username/password, move them to credential and + * remove them from the URL. + */ + if ((at_pos = get_url_at_pos(hreq->url.ptr, hreq->url.slen)) != NULL) { + pj_str_t tmp; + char *user_pos = pj_strchr(&hreq->url, '/'); + int removed_len; + + /* Save credential first, unescape the string */ + tmp = pj_str_unescape(hreq->pool, &hreq->hurl.username); + ; + pj_strdup(hreq->pool, &hreq->param.auth_cred.username, &tmp); + + tmp = pj_str_unescape(hreq->pool, &hreq->hurl.passwd); + pj_strdup(hreq->pool, &hreq->param.auth_cred.data, &tmp); + + hreq->hurl.username.ptr = hreq->hurl.passwd.ptr = NULL; + hreq->hurl.username.slen = hreq->hurl.passwd.slen = 0; + + /* Remove "username:password@" from the URL */ + pj_assert(user_pos != 0 && user_pos < at_pos); + user_pos += 2; + removed_len = (int)(at_pos + 1 - user_pos); + pj_memmove(user_pos, at_pos + 1, hreq->url.ptr + hreq->url.slen - at_pos - 1); + hreq->url.slen -= removed_len; + + /* Need to adjust hostname and path pointers due to memmove*/ + if (hreq->hurl.host.ptr > user_pos && hreq->hurl.host.ptr < user_pos + hreq->url.slen) { + hreq->hurl.host.ptr -= removed_len; + } + /* path may come from a string constant, don't shift it if so */ + if (hreq->hurl.path.ptr > user_pos && hreq->hurl.path.ptr < user_pos + hreq->url.slen) { + hreq->hurl.path.ptr -= removed_len; + } + } + + *http_req = hreq; + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pj_http_req_is_running(const pj_http_req *http_req) +{ + PJ_ASSERT_RETURN(http_req, PJ_FALSE); + return (http_req->state != IDLE); +} + +PJ_DEF(void *) pj_http_req_get_user_data(pj_http_req *http_req) +{ + PJ_ASSERT_RETURN(http_req, NULL); + return http_req->param.user_data; +} + +static pj_status_t start_http_req(pj_http_req *http_req, pj_bool_t notify_on_fail) +{ + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_status_t status; + pj_activesock_cb asock_cb; + int retry = 0; + + PJ_ASSERT_RETURN(http_req, PJ_EINVAL); + /* Http request is not idle, a request was initiated before and + * is still in progress + */ + PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY); + + /* Reset few things to make sure restarting works */ + http_req->error = 0; + http_req->response.headers.count = 0; + pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state)); + + if (!http_req->resolved) { + /* Resolve the Internet address of the host */ + status = + pj_sockaddr_init(http_req->param.addr_family, &http_req->addr, &http_req->hurl.host, http_req->hurl.port); + if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(&http_req->addr) || + (http_req->param.addr_family == pj_AF_INET() && http_req->addr.ipv4.sin_addr.s_addr == PJ_INADDR_NONE)) { + goto on_return; + } + http_req->resolved = PJ_TRUE; + } + + status = pj_sock_socket(http_req->param.addr_family, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_return; // error creating socket + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &http_on_data_read; + asock_cb.on_data_sent = &http_on_data_sent; + asock_cb.on_connect_complete = &http_on_connect; + + do { + pj_sockaddr_in bound_addr; + pj_uint16_t port = 0; + + /* If we are using port restriction. + * Get a random port within the range + */ + if (http_req->param.source_port_range_start != 0) { + port = (pj_uint16_t)(http_req->param.source_port_range_start + + (pj_rand() % http_req->param.source_port_range_size)); + } + + pj_sockaddr_in_init(&bound_addr, NULL, port); + status = pj_sock_bind(sock, &bound_addr, sizeof(bound_addr)); + + } while (status != PJ_SUCCESS && (retry++ < http_req->param.max_retries)); + + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (THIS_FILE, status, "Unable to bind to the requested port")); + pj_sock_close(sock); + goto on_return; + } + + // TODO: should we set whole data to 0 by default? + // or add it in the param? + status = pj_activesock_create(http_req->pool, sock, pj_SOCK_STREAM(), NULL, http_req->ioqueue, &asock_cb, http_req, + &http_req->asock); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + goto on_return; // error creating activesock + } + + /* Schedule timeout timer for the request */ + pj_assert(http_req->timer_entry.id == 0); + http_req->timer_entry.id = 1; + status = pj_timer_heap_schedule(http_req->timer, &http_req->timer_entry, &http_req->param.timeout); + if (status != PJ_SUCCESS) { + http_req->timer_entry.id = 0; + goto on_return; // error scheduling timer + } + + /* Connect to host */ + http_req->state = CONNECTING; + status = pj_activesock_start_connect(http_req->asock, http_req->pool, (pj_sockaddr_t *)&(http_req->addr), + pj_sockaddr_get_len(&http_req->addr)); + if (status == PJ_SUCCESS) { + http_req->state = SENDING_REQUEST; + status = http_req_start_sending(http_req); + if (status != PJ_SUCCESS) + goto on_return; + } else if (status != PJ_EPENDING) { + goto on_return; // error connecting + } + + return PJ_SUCCESS; + +on_return: + http_req->error = status; + if (notify_on_fail) + pj_http_req_cancel(http_req, PJ_TRUE); + else + http_req_end_request(http_req); + + return status; +} + +/* Starts an asynchronous HTTP request to the URL specified. */ +PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req) +{ + return start_http_req(http_req, PJ_FALSE); +} + +/* Respond to basic authentication challenge */ +static pj_status_t auth_respond_basic(pj_http_req *hreq) +{ + /* Basic authentication: + * credentials = "Basic" basic-credentials + * basic-credentials = base64-user-pass + * base64-user-pass = + * user-pass = userid ":" password + * + * Sample: + * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + */ + pj_str_t user_pass; + pj_http_header_elmt *phdr; + int len; + + /* Use send buffer to store userid ":" password */ + user_pass.ptr = hreq->buffer.ptr; + pj_strcpy(&user_pass, &hreq->param.auth_cred.username); + pj_strcat2(&user_pass, ":"); + pj_strcat(&user_pass, &hreq->param.auth_cred.data); + + /* Create Authorization header */ + phdr = &hreq->param.headers.header[hreq->param.headers.count++]; + pj_bzero(phdr, sizeof(*phdr)); + if (hreq->response.status_code == 401) + phdr->name = pj_str("Authorization"); + else + phdr->name = pj_str("Proxy-Authorization"); + + len = (int)(PJ_BASE256_TO_BASE64_LEN(user_pass.slen) + 10); + phdr->value.ptr = (char *)pj_pool_alloc(hreq->pool, len); + phdr->value.slen = 0; + + pj_strcpy2(&phdr->value, "Basic "); + len -= (int)phdr->value.slen; + pj_base64_encode((pj_uint8_t *)user_pass.ptr, (int)user_pass.slen, phdr->value.ptr + phdr->value.slen, &len); + phdr->value.slen += len; + + return PJ_SUCCESS; +} + +/** Length of digest string. */ +#define MD5_STRLEN 32 +/* A macro just to get rid of type mismatch between char and unsigned char */ +#define MD5_APPEND(pms, buf, len) pj_md5_update(pms, (const pj_uint8_t *)buf, (unsigned)len) + +/* Transform digest to string. + * output must be at least PJSIP_MD5STRLEN+1 bytes. + * + * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED! + */ +static void digest2str(const unsigned char digest[], char *output) +{ + int i; + for (i = 0; i < 16; ++i) { + pj_val_to_hex_digit(digest[i], output); + output += 2; + } +} + +static void auth_create_digest_response(pj_str_t *result, const pj_http_auth_cred *cred, const pj_str_t *nonce, + const pj_str_t *nc, const pj_str_t *cnonce, const pj_str_t *qop, + const pj_str_t *uri, const pj_str_t *realm, const pj_str_t *method) +{ + char ha1[MD5_STRLEN]; + char ha2[MD5_STRLEN]; + unsigned char digest[16]; + pj_md5_context pms; + + pj_assert(result->slen >= MD5_STRLEN); + + TRACE_((THIS_FILE, "Begin creating digest")); + + if (cred->data_type == 0) { + /*** + *** ha1 = MD5(username ":" realm ":" password) + ***/ + pj_md5_init(&pms); + MD5_APPEND(&pms, cred->username.ptr, cred->username.slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, realm->ptr, realm->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, cred->data.ptr, cred->data.slen); + pj_md5_final(&pms, digest); + + digest2str(digest, ha1); + + } else if (cred->data_type == 1) { + pj_assert(cred->data.slen == 32); + pj_memcpy(ha1, cred->data.ptr, cred->data.slen); + } else { + pj_assert(!"Invalid data_type"); + } + + TRACE_((THIS_FILE, " ha1=%.32s", ha1)); + + /*** + *** ha2 = MD5(method ":" req_uri) + ***/ + pj_md5_init(&pms); + MD5_APPEND(&pms, method->ptr, method->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, uri->ptr, uri->slen); + pj_md5_final(&pms, digest); + digest2str(digest, ha2); + + TRACE_((THIS_FILE, " ha2=%.32s", ha2)); + + /*** + *** When qop is not used: + *** response = MD5(ha1 ":" nonce ":" ha2) + *** + *** When qop=auth is used: + *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) + ***/ + pj_md5_init(&pms); + MD5_APPEND(&pms, ha1, MD5_STRLEN); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, nonce->ptr, nonce->slen); + if (qop && qop->slen != 0) { + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, nc->ptr, nc->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, cnonce->ptr, cnonce->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, qop->ptr, qop->slen); + } + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, ha2, MD5_STRLEN); + + /* This is the final response digest. */ + pj_md5_final(&pms, digest); + + /* Convert digest to string and store in chal->response. */ + result->slen = MD5_STRLEN; + digest2str(digest, result->ptr); + + TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); + TRACE_((THIS_FILE, "Digest created")); +} + +/* Find out if qop offer contains "auth" token */ +static pj_bool_t auth_has_qop(pj_pool_t *pool, const pj_str_t *qop_offer) +{ + pj_str_t qop; + char *p; + + pj_strdup_with_null(pool, &qop, qop_offer); + p = qop.ptr; + while (*p) { + *p = (char)pj_tolower(*p); + ++p; + } + + p = qop.ptr; + while (*p) { + if (*p == 'a' && *(p + 1) == 'u' && *(p + 2) == 't' && *(p + 3) == 'h') { + int e = *(p + 4); + if (e == '"' || e == ',' || e == 0) + return PJ_TRUE; + else + p += 4; + } else { + ++p; + } + } + + return PJ_FALSE; +} + +#define STR_PREC(s) (int)(s).slen, (s).ptr + +/* Respond to digest authentication */ +static pj_status_t auth_respond_digest(pj_http_req *hreq) +{ + const pj_http_auth_chal *chal = &hreq->response.auth_chal; + const pj_http_auth_cred *cred = &hreq->param.auth_cred; + pj_http_header_elmt *phdr; + char digest_response_buf[MD5_STRLEN]; + int len; + pj_str_t digest_response; + + /* Check algorithm is supported. We only support MD5 */ + if (chal->algorithm.slen != 0 && pj_stricmp2(&chal->algorithm, "MD5")) { + TRACE_((THIS_FILE, "Error: Unsupported digest algorithm \"%.*s\"", chal->algorithm.slen, chal->algorithm.ptr)); + return PJ_ENOTSUP; + } + + /* Add Authorization header */ + phdr = &hreq->param.headers.header[hreq->param.headers.count++]; + pj_bzero(phdr, sizeof(*phdr)); + if (hreq->response.status_code == 401) + phdr->name = pj_str("Authorization"); + else + phdr->name = pj_str("Proxy-Authorization"); + + /* Allocate space for the header */ + len = (int)(8 + /* Digest */ + 16 + hreq->param.auth_cred.username.slen + /* username= */ + 12 + chal->realm.slen + /* realm= */ + 12 + chal->nonce.slen + /* nonce= */ + 8 + hreq->hurl.path.slen + /* uri= */ + 16 + /* algorithm=MD5 */ + 16 + MD5_STRLEN + /* response= */ + 12 + /* qop=auth */ + 8 + /* nc=.. */ + 30 + /* cnonce= */ + 12 + chal->opaque.slen + /* opaque=".." */ + 0); + phdr->value.ptr = (char *)pj_pool_alloc(hreq->pool, len); + + /* Configure buffer to temporarily store the digest */ + digest_response.ptr = digest_response_buf; + digest_response.slen = MD5_STRLEN; + + if (chal->qop.slen == 0) { + const pj_str_t STR_MD5 = {"MD5", 3}; + int max_len; + + /* Server doesn't require quality of protection. */ + auth_create_digest_response(&digest_response, cred, &chal->nonce, NULL, NULL, NULL, &hreq->hurl.path, + &chal->realm, &hreq->param.method); + + max_len = len; + len = pj_ansi_snprintf(phdr->value.ptr, max_len, + "Digest username=\"%.*s\", " + "realm=\"%.*s\", " + "nonce=\"%.*s\", " + "uri=\"%.*s\", " + "algorithm=%.*s, " + "response=\"%.*s\"", + STR_PREC(cred->username), STR_PREC(chal->realm), STR_PREC(chal->nonce), + STR_PREC(hreq->hurl.path), STR_PREC(STR_MD5), STR_PREC(digest_response)); + if (len < 0 || len >= max_len) + return PJ_ETOOSMALL; + phdr->value.slen = len; + + } else if (auth_has_qop(hreq->pool, &chal->qop)) { + /* Server requires quality of protection. + * We respond with selecting "qop=auth" protection. + */ + const pj_str_t STR_MD5 = {"MD5", 3}; + const pj_str_t qop = pj_str("auth"); + const pj_str_t nc = pj_str("00000001"); + const pj_str_t cnonce = pj_str("b39971"); + int max_len; + + auth_create_digest_response(&digest_response, cred, &chal->nonce, &nc, &cnonce, &qop, &hreq->hurl.path, + &chal->realm, &hreq->param.method); + max_len = len; + len = pj_ansi_snprintf(phdr->value.ptr, max_len, + "Digest username=\"%.*s\", " + "realm=\"%.*s\", " + "nonce=\"%.*s\", " + "uri=\"%.*s\", " + "algorithm=%.*s, " + "response=\"%.*s\", " + "qop=%.*s, " + "nc=%.*s, " + "cnonce=\"%.*s\"", + STR_PREC(cred->username), STR_PREC(chal->realm), STR_PREC(chal->nonce), + STR_PREC(hreq->hurl.path), STR_PREC(STR_MD5), STR_PREC(digest_response), STR_PREC(qop), + STR_PREC(nc), STR_PREC(cnonce)); + if (len < 0 || len >= max_len) + return PJ_ETOOSMALL; + phdr->value.slen = len; + + if (chal->opaque.slen) { + pj_strcat2(&phdr->value, ", opaque=\""); + pj_strcat(&phdr->value, &chal->opaque); + pj_strcat2(&phdr->value, "\""); + } + + } else { + /* Server requires quality protection that we don't support. */ + TRACE_((THIS_FILE, "Error: Unsupported qop offer %.*s", chal->qop.slen, chal->qop.ptr)); + return PJ_ENOTSUP; + } + + return PJ_SUCCESS; +} + +static void restart_req_with_auth(pj_http_req *hreq) +{ + pj_http_auth_chal *chal = &hreq->response.auth_chal; + pj_http_auth_cred *cred = &hreq->param.auth_cred; + pj_status_t status; + + if (hreq->param.headers.count >= PJ_HTTP_HEADER_SIZE) { + TRACE_((THIS_FILE, "Error: no place to put Authorization header")); + hreq->auth_state = AUTH_DONE; + return; + } + + /* If credential specifies specific scheme, make sure they match */ + if (cred->scheme.slen && pj_stricmp(&chal->scheme, &cred->scheme)) { + status = PJ_ENOTSUP; + TRACE_((THIS_FILE, "Error: auth schemes mismatch")); + goto on_error; + } + + /* If credential specifies specific realm, make sure they match */ + if (cred->realm.slen && pj_stricmp(&chal->realm, &cred->realm)) { + status = PJ_ENOTSUP; + TRACE_((THIS_FILE, "Error: auth realms mismatch")); + goto on_error; + } + + if (!pj_stricmp2(&chal->scheme, "basic")) { + status = auth_respond_basic(hreq); + } else if (!pj_stricmp2(&chal->scheme, "digest")) { + status = auth_respond_digest(hreq); + } else { + TRACE_((THIS_FILE, "Error: unsupported HTTP auth scheme")); + status = PJ_ENOTSUP; + } + + if (status != PJ_SUCCESS) + goto on_error; + + http_req_end_request(hreq); + + status = start_http_req(hreq, PJ_TRUE); + if (status != PJ_SUCCESS) + goto on_error; + + hreq->auth_state = AUTH_RETRYING; + return; + +on_error: + hreq->auth_state = AUTH_DONE; +} + +/* snprintf() to a pj_str_t struct with an option to append the + * result at the back of the string. + */ +static void str_snprintf(pj_str_t *s, size_t size, pj_bool_t append, const char *format, ...) +{ + va_list arg; + int retval; + + va_start(arg, format); + if (!append) + s->slen = 0; + size -= s->slen; + retval = pj_ansi_vsnprintf(s->ptr + s->slen, size, format, arg); + s->slen += ((retval < (int)size) ? retval : size - 1); + va_end(arg); +} + +static pj_status_t http_req_start_sending(pj_http_req *hreq) +{ + pj_status_t status; + pj_str_t pkt; + pj_ssize_t len; + pj_size_t i; + + PJ_ASSERT_RETURN(hreq->state == SENDING_REQUEST || hreq->state == SENDING_REQUEST_BODY, PJ_EBUG); + + if (hreq->state == SENDING_REQUEST) { + /* Prepare the request data */ + if (!hreq->buffer.ptr) + hreq->buffer.ptr = (char *)pj_pool_alloc(hreq->pool, BUF_SIZE); + pj_strassign(&pkt, &hreq->buffer); + pkt.slen = 0; + /* Start-line */ + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s %.*s %s/%.*s\r\n", STR_PREC(hreq->param.method), + STR_PREC(hreq->hurl.path), get_protocol(&hreq->hurl.protocol), STR_PREC(hreq->param.version)); + /* Header field "Host" */ + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "Host: %.*s:%d\r\n", STR_PREC(hreq->hurl.host), hreq->hurl.port); + if (!pj_strcmp2(&hreq->param.method, http_method_names[HTTP_PUT])) { + char buf[16]; + + /* Header field "Content-Length" */ + pj_utoa(hreq->param.reqdata.total_size ? (unsigned long)hreq->param.reqdata.total_size + : (unsigned long)hreq->param.reqdata.size, + buf); + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%s: %s\r\n", CONTENT_LENGTH, buf); + } + + /* Append user-specified headers */ + for (i = 0; i < hreq->param.headers.count; i++) { + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s: %.*s\r\n", STR_PREC(hreq->param.headers.header[i].name), + STR_PREC(hreq->param.headers.header[i].value)); + } + if (pkt.slen >= BUF_SIZE - 1) { + status = PJLIB_UTIL_EHTTPINSBUF; + goto on_return; + } + + pj_strcat2(&pkt, "\r\n"); + pkt.ptr[pkt.slen] = 0; + TRACE_((THIS_FILE, "%s", pkt.ptr)); + } else { + pkt.ptr = (char *)hreq->param.reqdata.data; + pkt.slen = hreq->param.reqdata.size; + } + + /* Send the request */ + len = pj_strlen(&pkt); + pj_ioqueue_op_key_init(&hreq->op_key, sizeof(hreq->op_key)); + hreq->tcp_state.send_size = len; + hreq->tcp_state.current_send_size = 0; + status = pj_activesock_send(hreq->asock, &hreq->op_key, pkt.ptr, &len, 0); + + if (status == PJ_SUCCESS) { + http_on_data_sent(hreq->asock, &hreq->op_key, len); + } else if (status != PJ_EPENDING) { + goto on_return; // error sending data + } + + return PJ_SUCCESS; + +on_return: + http_req_end_request(hreq); + return status; +} + +static pj_status_t http_req_start_reading(pj_http_req *hreq) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(hreq->state == REQUEST_SENT, PJ_EBUG); + + /* Receive the response */ + hreq->state = READING_RESPONSE; + hreq->tcp_state.current_read_size = 0; + pj_assert(hreq->buffer.ptr); + status = pj_activesock_start_read2(hreq->asock, hreq->pool, BUF_SIZE, (void **)&hreq->buffer.ptr, 0); + if (status != PJ_SUCCESS) { + /* Error reading */ + http_req_end_request(hreq); + return status; + } + + return PJ_SUCCESS; +} + +static pj_status_t http_req_end_request(pj_http_req *hreq) +{ + if (hreq->asock) { + pj_activesock_close(hreq->asock); + hreq->asock = NULL; + } + + /* Cancel query timeout timer. */ + if (hreq->timer_entry.id != 0) { + pj_timer_heap_cancel(hreq->timer, &hreq->timer_entry); + /* Invalidate id. */ + hreq->timer_entry.id = 0; + } + + hreq->state = IDLE; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_http_req_cancel(pj_http_req *http_req, pj_bool_t notify) +{ + http_req->state = ABORTING; + + http_req_end_request(http_req); + + if (notify && http_req->cb.on_complete) { + (*http_req->cb.on_complete)(http_req, (!http_req->error ? PJ_ECANCELLED : http_req->error), NULL); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_http_req_destroy(pj_http_req *http_req) +{ + PJ_ASSERT_RETURN(http_req, PJ_EINVAL); + + /* If there is any pending request, cancel it */ + if (http_req->state != IDLE) { + pj_http_req_cancel(http_req, PJ_FALSE); + } + + pj_pool_release(http_req->pool); + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c new file mode 100755 index 000000000..64e8d9b5d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define EL_INIT(p_el, nm, typ) \ + do { \ + if (nm) { \ + p_el->name = *nm; \ + } else { \ + p_el->name.ptr = (char *)""; \ + p_el->name.slen = 0; \ + } \ + p_el->type = typ; \ + } while (0) + +struct write_state; +struct parse_state; + +#define NO_NAME 1 + +static pj_status_t elem_write(const pj_json_elem *elem, struct write_state *st, unsigned flags); +static pj_json_elem *parse_elem_throw(struct parse_state *st, pj_json_elem *elem); + +PJ_DEF(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name) +{ + EL_INIT(el, name, PJ_JSON_VAL_NULL); +} + +PJ_DEF(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name, pj_bool_t val) +{ + EL_INIT(el, name, PJ_JSON_VAL_BOOL); + el->value.is_true = val; +} + +PJ_DEF(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name, float val) +{ + EL_INIT(el, name, PJ_JSON_VAL_NUMBER); + el->value.num = val; +} + +PJ_DEF(void) pj_json_elem_string(pj_json_elem *el, pj_str_t *name, pj_str_t *value) +{ + EL_INIT(el, name, PJ_JSON_VAL_STRING); + el->value.str = *value; +} + +PJ_DEF(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name) +{ + EL_INIT(el, name, PJ_JSON_VAL_ARRAY); + pj_list_init(&el->value.children); +} + +PJ_DEF(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name) +{ + EL_INIT(el, name, PJ_JSON_VAL_OBJ); + pj_list_init(&el->value.children); +} + +PJ_DEF(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child) +{ + pj_assert(el->type == PJ_JSON_VAL_OBJ || el->type == PJ_JSON_VAL_ARRAY); + pj_list_push_back(&el->value.children, child); +} + +struct parse_state { + pj_pool_t *pool; + pj_scanner scanner; + pj_json_err_info *err_info; + pj_cis_t float_spec; /* numbers with dot! */ +}; + +static pj_status_t parse_children(struct parse_state *st, pj_json_elem *parent) +{ + char end_quote = (parent->type == PJ_JSON_VAL_ARRAY) ? ']' : '}'; + + pj_scan_get_char(&st->scanner); + + while (*st->scanner.curptr != end_quote) { + pj_json_elem *child; + + while (*st->scanner.curptr == ',') + pj_scan_get_char(&st->scanner); + + if (*st->scanner.curptr == end_quote) + break; + + child = parse_elem_throw(st, NULL); + if (!child) + return PJLIB_UTIL_EINJSON; + + pj_json_elem_add(parent, child); + } + + pj_scan_get_char(&st->scanner); + return PJ_SUCCESS; +} + +/* Return 0 if success or the index of the invalid char in the string */ +static unsigned parse_quoted_string(struct parse_state *st, pj_str_t *output) +{ + pj_str_t token; + char *op, *ip, *iend; + + pj_scan_get_quote(&st->scanner, '"', '"', &token); + + /* Remove the quote characters */ + token.ptr++; + token.slen -= 2; + + if (pj_strchr(&token, '\\') == NULL) { + *output = token; + return 0; + } + + output->ptr = op = pj_pool_alloc(st->pool, token.slen); + + ip = token.ptr; + iend = token.ptr + token.slen; + + while (ip != iend) { + if (*ip == '\\') { + ++ip; + if (ip == iend) { + goto on_error; + } + if (*ip == 'u') { + ip++; + if (iend - ip < 4) { + ip = iend - 1; + goto on_error; + } + /* Only use the last two hext digits because we're on + * ASCII */ + *op++ = (char)(pj_hex_digit_to_val(ip[2]) * 16 + pj_hex_digit_to_val(ip[3])); + ip += 4; + } else if (*ip == '"' || *ip == '\\' || *ip == '/') { + *op++ = *ip++; + } else if (*ip == 'b') { + *op++ = '\b'; + ip++; + } else if (*ip == 'f') { + *op++ = '\f'; + ip++; + } else if (*ip == 'n') { + *op++ = '\n'; + ip++; + } else if (*ip == 'r') { + *op++ = '\r'; + ip++; + } else if (*ip == 't') { + *op++ = '\t'; + ip++; + } else { + goto on_error; + } + } else { + *op++ = *ip++; + } + } + + output->slen = op - output->ptr; + return 0; + +on_error: + output->slen = op - output->ptr; + return (unsigned)(ip - token.ptr); +} + +static pj_json_elem *parse_elem_throw(struct parse_state *st, pj_json_elem *elem) +{ + pj_str_t name = {NULL, 0}, value = {NULL, 0}; + pj_str_t token; + + if (!elem) + elem = pj_pool_alloc(st->pool, sizeof(*elem)); + + /* Parse name */ + if (*st->scanner.curptr == '"') { + pj_scan_get_char(&st->scanner); + pj_scan_get_until_ch(&st->scanner, '"', &token); + pj_scan_get_char(&st->scanner); + + if (*st->scanner.curptr == ':') { + pj_scan_get_char(&st->scanner); + name = token; + } else { + value = token; + } + } + + if (value.slen) { + /* Element with string value and no name */ + pj_json_elem_string(elem, &name, &value); + return elem; + } + + /* Parse value */ + if (pj_cis_match(&st->float_spec, *st->scanner.curptr) || *st->scanner.curptr == '-') { + float val; + pj_bool_t neg = PJ_FALSE; + + if (*st->scanner.curptr == '-') { + pj_scan_get_char(&st->scanner); + neg = PJ_TRUE; + } + + pj_scan_get(&st->scanner, &st->float_spec, &token); + val = pj_strtof(&token); + if (neg) + val = -val; + + pj_json_elem_number(elem, &name, val); + + } else if (*st->scanner.curptr == '"') { + unsigned err; + char *start = st->scanner.curptr; + + err = parse_quoted_string(st, &token); + if (err) { + st->scanner.curptr = start + err; + return NULL; + } + + pj_json_elem_string(elem, &name, &token); + + } else if (pj_isalpha(*st->scanner.curptr)) { + + if (pj_scan_strcmp(&st->scanner, "false", 5) == 0) { + pj_json_elem_bool(elem, &name, PJ_FALSE); + pj_scan_advance_n(&st->scanner, 5, PJ_TRUE); + } else if (pj_scan_strcmp(&st->scanner, "true", 4) == 0) { + pj_json_elem_bool(elem, &name, PJ_TRUE); + pj_scan_advance_n(&st->scanner, 4, PJ_TRUE); + } else if (pj_scan_strcmp(&st->scanner, "null", 4) == 0) { + pj_json_elem_null(elem, &name); + pj_scan_advance_n(&st->scanner, 4, PJ_TRUE); + } else { + return NULL; + } + + } else if (*st->scanner.curptr == '[') { + pj_json_elem_array(elem, &name); + if (parse_children(st, elem) != PJ_SUCCESS) + return NULL; + + } else if (*st->scanner.curptr == '{') { + pj_json_elem_obj(elem, &name); + if (parse_children(st, elem) != PJ_SUCCESS) + return NULL; + + } else { + return NULL; + } + + return elem; +} + +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(11); +} + +PJ_DEF(pj_json_elem *) pj_json_parse(pj_pool_t *pool, char *buffer, unsigned *size, pj_json_err_info *err_info) +{ + pj_cis_buf_t cis_buf; + struct parse_state st; + pj_json_elem *root; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pool && buffer && size, NULL); + + if (!*size) + return NULL; + + pj_bzero(&st, sizeof(st)); + st.pool = pool; + st.err_info = err_info; + pj_scan_init(&st.scanner, buffer, *size, PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE, &on_syntax_error); + pj_cis_buf_init(&cis_buf); + pj_cis_init(&cis_buf, &st.float_spec); + pj_cis_add_str(&st.float_spec, ".0123456789"); + + PJ_TRY + { + root = parse_elem_throw(&st, NULL); + } + PJ_CATCH_ANY + { + root = NULL; + } + PJ_END + + if (!root && err_info) { + err_info->line = st.scanner.line; + err_info->col = pj_scan_get_col(&st.scanner) + 1; + err_info->err_char = *st.scanner.curptr; + } + + *size = (unsigned)((buffer + *size) - st.scanner.curptr); + + pj_scan_fini(&st.scanner); + + return root; +} + +struct buf_writer_data { + char *pos; + unsigned size; +}; + +static pj_status_t buf_writer(const char *s, unsigned size, void *user_data) +{ + struct buf_writer_data *buf_data = (struct buf_writer_data *)user_data; + if (size + 1 >= buf_data->size) + return PJ_ETOOBIG; + + pj_memcpy(buf_data->pos, s, size); + buf_data->pos += size; + buf_data->size -= size; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_json_write(const pj_json_elem *elem, char *buffer, unsigned *size) +{ + struct buf_writer_data buf_data; + pj_status_t status; + + PJ_ASSERT_RETURN(elem && buffer && size, PJ_EINVAL); + + buf_data.pos = buffer; + buf_data.size = *size; + + status = pj_json_writef(elem, &buf_writer, &buf_data); + if (status != PJ_SUCCESS) + return status; + + *buf_data.pos = '\0'; + *size = (unsigned)(buf_data.pos - buffer); + return PJ_SUCCESS; +} + +#define MAX_INDENT 100 +#ifndef PJ_JSON_NAME_MIN_LEN +#define PJ_JSON_NAME_MIN_LEN 20 +#endif +#define ESC_BUF_LEN 64 +#ifndef PJ_JSON_INDENT_SIZE +#define PJ_JSON_INDENT_SIZE 3 +#endif + +struct write_state { + pj_json_writer writer; + void *user_data; + char indent_buf[MAX_INDENT]; + int indent; + char space[PJ_JSON_NAME_MIN_LEN]; +}; + +#define CHECK(expr) \ + do { \ + status = expr; \ + if (status != PJ_SUCCESS) \ + return status; \ + } while (0) + +static pj_status_t write_string_escaped(const pj_str_t *value, struct write_state *st) +{ + const char *ip = value->ptr; + const char *iend = value->ptr + value->slen; + char buf[ESC_BUF_LEN]; + char *op = buf; + char *oend = buf + ESC_BUF_LEN; + pj_status_t status; + + while (ip != iend) { + /* Write to buffer to speedup writing instead of calling + * the callback one by one for each character. + */ + while (ip != iend && op != oend) { + if (oend - op < 2) + break; + + if (*ip == '"') { + *op++ = '\\'; + *op++ = '"'; + ip++; + } else if (*ip == '\\') { + *op++ = '\\'; + *op++ = '\\'; + ip++; + } else if (*ip == '/') { + *op++ = '\\'; + *op++ = '/'; + ip++; + } else if (*ip == '\b') { + *op++ = '\\'; + *op++ = 'b'; + ip++; + } else if (*ip == '\f') { + *op++ = '\\'; + *op++ = 'f'; + ip++; + } else if (*ip == '\n') { + *op++ = '\\'; + *op++ = 'n'; + ip++; + } else if (*ip == '\r') { + *op++ = '\\'; + *op++ = 'r'; + ip++; + } else if (*ip == '\t') { + *op++ = '\\'; + *op++ = 't'; + ip++; + } else if ((*ip >= 32 && *ip < 127)) { + /* unescaped */ + *op++ = *ip++; + } else { + /* escaped */ + if (oend - op < 6) + break; + *op++ = '\\'; + *op++ = 'u'; + *op++ = '0'; + *op++ = '0'; + pj_val_to_hex_digit(*ip, op); + op += 2; + ip++; + } + } + + CHECK(st->writer(buf, (unsigned)(op - buf), st->user_data)); + op = buf; + } + + return PJ_SUCCESS; +} + +static pj_status_t write_children(const pj_json_list *list, const char quotes[2], struct write_state *st) +{ + unsigned flags = (quotes[0] == '[') ? NO_NAME : 0; + pj_status_t status; + + // CHECK( st->writer( st->indent_buf, st->indent, st->user_data) ); + CHECK(st->writer("es[0], 1, st->user_data)); + CHECK(st->writer(" ", 1, st->user_data)); + + if (!pj_list_empty(list)) { + pj_bool_t indent_added = PJ_FALSE; + pj_json_elem *child = list->next; + + if (child->name.slen == 0) { + /* Simple list */ + while (child != (pj_json_elem *)list) { + status = elem_write(child, st, flags); + if (status != PJ_SUCCESS) + return status; + + if (child->next != (pj_json_elem *)list) + CHECK(st->writer(", ", 2, st->user_data)); + child = child->next; + } + } else { + if (st->indent < sizeof(st->indent_buf)) { + st->indent += PJ_JSON_INDENT_SIZE; + indent_added = PJ_TRUE; + } + CHECK(st->writer("\n", 1, st->user_data)); + while (child != (pj_json_elem *)list) { + status = elem_write(child, st, flags); + if (status != PJ_SUCCESS) + return status; + + if (child->next != (pj_json_elem *)list) + CHECK(st->writer(",\n", 2, st->user_data)); + else + CHECK(st->writer("\n", 1, st->user_data)); + child = child->next; + } + if (indent_added) { + st->indent -= PJ_JSON_INDENT_SIZE; + } + CHECK(st->writer(st->indent_buf, st->indent, st->user_data)); + } + } + CHECK(st->writer("es[1], 1, st->user_data)); + + return PJ_SUCCESS; +} + +static pj_status_t elem_write(const pj_json_elem *elem, struct write_state *st, unsigned flags) +{ + pj_status_t status; + + if (elem->name.slen) { + CHECK(st->writer(st->indent_buf, st->indent, st->user_data)); + if ((flags & NO_NAME) == 0) { + CHECK(st->writer("\"", 1, st->user_data)); + CHECK(write_string_escaped(&elem->name, st)); + CHECK(st->writer("\": ", 3, st->user_data)); + if (elem->name.slen < PJ_JSON_NAME_MIN_LEN /*&& + elem->type != PJ_JSON_VAL_OBJ && + elem->type != PJ_JSON_VAL_ARRAY*/) + { + CHECK(st->writer(st->space, (unsigned)(PJ_JSON_NAME_MIN_LEN - elem->name.slen), st->user_data)); + } + } + } + + switch (elem->type) { + case PJ_JSON_VAL_NULL: + CHECK(st->writer("null", 4, st->user_data)); + break; + case PJ_JSON_VAL_BOOL: + if (elem->value.is_true) + CHECK(st->writer("true", 4, st->user_data)); + else + CHECK(st->writer("false", 5, st->user_data)); + break; + case PJ_JSON_VAL_NUMBER: { + char num_buf[65]; + int len; + + if (elem->value.num == (int)elem->value.num) + len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%d", (int)elem->value.num); + else + len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%f", elem->value.num); + + if (len < 0 || len >= sizeof(num_buf)) + return PJ_ETOOBIG; + CHECK(st->writer(num_buf, len, st->user_data)); + } break; + case PJ_JSON_VAL_STRING: + CHECK(st->writer("\"", 1, st->user_data)); + CHECK(write_string_escaped(&elem->value.str, st)); + CHECK(st->writer("\"", 1, st->user_data)); + break; + case PJ_JSON_VAL_ARRAY: + CHECK(write_children(&elem->value.children, "[]", st)); + break; + case PJ_JSON_VAL_OBJ: + CHECK(write_children(&elem->value.children, "{}", st)); + break; + default: + pj_assert(!"Unhandled value type"); + } + + return PJ_SUCCESS; +} + +#undef CHECK + +PJ_DEF(pj_status_t) pj_json_writef(const pj_json_elem *elem, pj_json_writer writer, void *user_data) +{ + struct write_state st; + + PJ_ASSERT_RETURN(elem && writer, PJ_EINVAL); + + st.writer = writer; + st.user_data = user_data; + st.indent = 0; + pj_memset(st.indent_buf, ' ', MAX_INDENT); + pj_memset(st.space, ' ', PJ_JSON_NAME_MIN_LEN); + + return elem_write(elem, &st, 0); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c new file mode 100755 index 000000000..c8a980c1c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c @@ -0,0 +1,262 @@ +/* + * This is the implementation of MD5 algorithm, based on the code + * written by Colin Plumb. This file is put in public domain. + */ +#include +#include /* pj_memcpy */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0 +#define HIGHFIRST 1 +#endif + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + pj_uint32_t t; + do { + t = (pj_uint32_t)((unsigned)buf[3] << 8 | buf[2]) << 16 | ((unsigned)buf[1] << 8 | buf[0]); + *(pj_uint32_t *)buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +static void MD5Transform(pj_uint32_t buf[4], pj_uint32_t const in[16]); + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +PJ_DEF(void) pj_md5_init(pj_md5_context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +PJ_DEF(void) pj_md5_update(pj_md5_context *ctx, unsigned char const *buf, unsigned len) +{ + pj_uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((pj_uint32_t)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64 - t; + if (len < t) { + pj_memcpy(p, buf, len); + return; + } + pj_memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + pj_memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + pj_memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +PJ_DEF(void) pj_md5_final(pj_md5_context *ctx, unsigned char digest[16]) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + pj_bzero(p, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + + /* Now fill the next block with 56 bytes */ + pj_bzero(ctx->in, 56); + } else { + /* Pad block to 56 bytes */ + pj_bzero(p, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + //((pj_uint32_t *) ctx->in)[14] = ctx->bits[0]; + //((pj_uint32_t *) ctx->in)[15] = ctx->bits[1]; + pj_memcpy(&ctx->in[14 << 2], &ctx->bits[0], sizeof(ctx->bits[0])); + pj_memcpy(&ctx->in[15 << 2], &ctx->bits[1], sizeof(ctx->bits[1])); + + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + pj_memcpy(digest, ctx->buf, 16); + pj_bzero(ctx, sizeof(*ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(pj_uint32_t buf[4], pj_uint32_t const in[16]) +{ + register pj_uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c new file mode 100755 index 000000000..7525ab2bb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define TRACE_(x) PJ_LOG(5, x) +#else +#define TRACE_(x) +#endif + +#pragma pack(1) + +typedef struct pj_pcap_hdr { + pj_uint32_t magic_number; /* magic number */ + pj_uint16_t version_major; /* major version number */ + pj_uint16_t version_minor; /* minor version number */ + pj_int32_t thiszone; /* GMT to local correction */ + pj_uint32_t sigfigs; /* accuracy of timestamps */ + pj_uint32_t snaplen; /* max length of captured packets, in octets */ + pj_uint32_t network; /* data link type */ +} pj_pcap_hdr; + +typedef struct pj_pcap_rec_hdr { + pj_uint32_t ts_sec; /* timestamp seconds */ + pj_uint32_t ts_usec; /* timestamp microseconds */ + pj_uint32_t incl_len; /* number of octets of packet saved in file */ + pj_uint32_t orig_len; /* actual length of packet */ +} pj_pcap_rec_hdr; + +#if 0 +/* gcc insisted on aligning this struct to 32bit on ARM */ +typedef struct pj_pcap_eth_hdr +{ + pj_uint8_t dest[6]; + pj_uint8_t src[6]; + pj_uint8_t len[2]; +} pj_pcap_eth_hdr; +#else +typedef pj_uint8_t pj_pcap_eth_hdr[14]; +#endif + +typedef struct pj_pcap_ip_hdr { + pj_uint8_t v_ihl; + pj_uint8_t tos; + pj_uint16_t len; + pj_uint16_t id; + pj_uint16_t flags_fragment; + pj_uint8_t ttl; + pj_uint8_t proto; + pj_uint16_t csum; + pj_uint32_t ip_src; + pj_uint32_t ip_dst; +} pj_pcap_ip_hdr; + +/* Implementation of pcap file */ +struct pj_pcap_file { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_oshandle_t fd; + pj_bool_t swap; + pj_pcap_hdr hdr; + pj_pcap_filter filter; +}; + +#pragma pack() + +/* Init default filter */ +PJ_DEF(void) pj_pcap_filter_default(pj_pcap_filter *filter) +{ + pj_bzero(filter, sizeof(*filter)); +} + +/* Open pcap file */ +PJ_DEF(pj_status_t) pj_pcap_open(pj_pool_t *pool, const char *path, pj_pcap_file **p_file) +{ + pj_pcap_file *file; + pj_ssize_t sz; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && path && p_file, PJ_EINVAL); + + /* More sanity checks */ + TRACE_(("pcap", "sizeof(pj_pcap_eth_hdr)=%d", sizeof(pj_pcap_eth_hdr))); + PJ_ASSERT_RETURN(sizeof(pj_pcap_eth_hdr) == 14, PJ_EBUG); + TRACE_(("pcap", "sizeof(pj_pcap_ip_hdr)=%d", sizeof(pj_pcap_ip_hdr))); + PJ_ASSERT_RETURN(sizeof(pj_pcap_ip_hdr) == 20, PJ_EBUG); + TRACE_(("pcap", "sizeof(pj_pcap_udp_hdr)=%d", sizeof(pj_pcap_udp_hdr))); + PJ_ASSERT_RETURN(sizeof(pj_pcap_udp_hdr) == 8, PJ_EBUG); + + file = PJ_POOL_ZALLOC_T(pool, pj_pcap_file); + + pj_ansi_strcpy(file->obj_name, "pcap"); + + status = pj_file_open(pool, path, PJ_O_RDONLY, &file->fd); + if (status != PJ_SUCCESS) + return status; + + /* Read file pcap header */ + sz = sizeof(file->hdr); + status = pj_file_read(file->fd, &file->hdr, &sz); + if (status != PJ_SUCCESS) { + pj_file_close(file->fd); + return status; + } + + /* Check magic number */ + if (file->hdr.magic_number == 0xa1b2c3d4) { + file->swap = PJ_FALSE; + } else if (file->hdr.magic_number == 0xd4c3b2a1) { + file->swap = PJ_TRUE; + file->hdr.network = pj_ntohl(file->hdr.network); + } else { + /* Not PCAP file */ + pj_file_close(file->fd); + return PJ_EINVALIDOP; + } + + TRACE_((file->obj_name, "PCAP file %s opened", path)); + + *p_file = file; + return PJ_SUCCESS; +} + +/* Close pcap file */ +PJ_DEF(pj_status_t) pj_pcap_close(pj_pcap_file *file) +{ + PJ_ASSERT_RETURN(file, PJ_EINVAL); + TRACE_((file->obj_name, "PCAP file closed")); + return pj_file_close(file->fd); +} + +/* Setup filter */ +PJ_DEF(pj_status_t) pj_pcap_set_filter(pj_pcap_file *file, const pj_pcap_filter *fil) +{ + PJ_ASSERT_RETURN(file && fil, PJ_EINVAL); + pj_memcpy(&file->filter, fil, sizeof(pj_pcap_filter)); + return PJ_SUCCESS; +} + +/* Read file */ +static pj_status_t read_file(pj_pcap_file *file, void *buf, pj_ssize_t *sz) +{ + pj_status_t status; + status = pj_file_read(file->fd, buf, sz); + if (status != PJ_SUCCESS) + return status; + if (*sz == 0) + return PJ_EEOF; + return PJ_SUCCESS; +} + +static pj_status_t skip(pj_oshandle_t fd, pj_off_t bytes) +{ + pj_status_t status; + status = pj_file_setpos(fd, bytes, PJ_SEEK_CUR); + if (status != PJ_SUCCESS) + return status; + return PJ_SUCCESS; +} + +#define SKIP_PKT() \ + if (rec_incl > sz_read) { \ + status = skip(file->fd, rec_incl - sz_read); \ + if (status != PJ_SUCCESS) \ + return status; \ + } + +/* Read UDP packet */ +PJ_DEF(pj_status_t) +pj_pcap_read_udp(pj_pcap_file *file, pj_pcap_udp_hdr *udp_hdr, pj_uint8_t *udp_payload, pj_size_t *udp_payload_size) +{ + PJ_ASSERT_RETURN(file && udp_payload && udp_payload_size, PJ_EINVAL); + PJ_ASSERT_RETURN(*udp_payload_size, PJ_EINVAL); + + /* Check data link type in PCAP file header */ + if ((file->filter.link && file->hdr.network != (pj_uint32_t)file->filter.link) || + file->hdr.network != PJ_PCAP_LINK_TYPE_ETH) { + /* Link header other than Ethernet is not supported for now */ + return PJ_ENOTSUP; + } + + /* Loop until we have the packet */ + for (;;) { + union { + pj_pcap_rec_hdr rec; + pj_pcap_eth_hdr eth; + pj_pcap_ip_hdr ip; + pj_pcap_udp_hdr udp; + } tmp; + unsigned rec_incl; + pj_ssize_t sz; + pj_size_t sz_read = 0; + char addr[PJ_INET_ADDRSTRLEN]; + pj_status_t status; + + TRACE_((file->obj_name, "Reading packet..")); + pj_bzero(&addr, sizeof(addr)); + + /* Read PCAP packet header */ + sz = sizeof(tmp.rec); + status = read_file(file, &tmp.rec, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "read_file() error: %d", status)); + return status; + } + + rec_incl = tmp.rec.incl_len; + + /* Swap byte ordering */ + if (file->swap) { + tmp.rec.incl_len = pj_ntohl(tmp.rec.incl_len); + tmp.rec.orig_len = pj_ntohl(tmp.rec.orig_len); + tmp.rec.ts_sec = pj_ntohl(tmp.rec.ts_sec); + tmp.rec.ts_usec = pj_ntohl(tmp.rec.ts_usec); + } + + /* Read link layer header */ + switch (file->hdr.network) { + case PJ_PCAP_LINK_TYPE_ETH: + sz = sizeof(tmp.eth); + status = read_file(file, &tmp.eth, &sz); + break; + default: + TRACE_((file->obj_name, "Error: link layer not Ethernet")); + return PJ_ENOTSUP; + } + + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading Eth header: %d", status)); + return status; + } + + sz_read += sz; + + /* Read IP header */ + sz = sizeof(tmp.ip); + status = read_file(file, &tmp.ip, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading IP header: %d", status)); + return status; + } + + sz_read += sz; + + /* Skip if IP source mismatch */ + if (file->filter.ip_src && tmp.ip.ip_src != file->filter.ip_src) { + TRACE_((file->obj_name, "IP source %s mismatch, skipping", + pj_inet_ntop2(pj_AF_INET(), (pj_in_addr *)&tmp.ip.ip_src, addr, sizeof(addr)))); + SKIP_PKT(); + continue; + } + + /* Skip if IP destination mismatch */ + if (file->filter.ip_dst && tmp.ip.ip_dst != file->filter.ip_dst) { + TRACE_((file->obj_name, "IP detination %s mismatch, skipping", + pj_inet_ntop2(pj_AF_INET(), (pj_in_addr *)&tmp.ip.ip_dst, addr, sizeof(addr)))); + SKIP_PKT(); + continue; + } + + /* Skip if proto mismatch */ + if (file->filter.proto && tmp.ip.proto != file->filter.proto) { + TRACE_((file->obj_name, "IP proto %d mismatch, skipping", tmp.ip.proto)); + SKIP_PKT(); + continue; + } + + /* Read transport layer header */ + switch (tmp.ip.proto) { + case PJ_PCAP_PROTO_TYPE_UDP: + sz = sizeof(tmp.udp); + status = read_file(file, &tmp.udp, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading UDP header: %d", status)); + return status; + } + + sz_read += sz; + + /* Skip if source port mismatch */ + if (file->filter.src_port && tmp.udp.src_port != file->filter.src_port) { + TRACE_((file->obj_name, "UDP src port %d mismatch, skipping", pj_ntohs(tmp.udp.src_port))); + SKIP_PKT(); + continue; + } + + /* Skip if destination port mismatch */ + if (file->filter.dst_port && tmp.udp.dst_port != file->filter.dst_port) { + TRACE_((file->obj_name, "UDP dst port %d mismatch, skipping", pj_ntohs(tmp.udp.dst_port))); + SKIP_PKT(); + continue; + } + + /* Copy UDP header if caller wants it */ + if (udp_hdr) { + pj_memcpy(udp_hdr, &tmp.udp, sizeof(*udp_hdr)); + } + + /* Calculate payload size */ + sz = pj_ntohs(tmp.udp.len) - sizeof(tmp.udp); + break; + default: + TRACE_((file->obj_name, "Not UDP, skipping")); + SKIP_PKT(); + continue; + } + + /* Check if payload fits the buffer */ + if (sz > (pj_ssize_t)*udp_payload_size) { + TRACE_((file->obj_name, "Error: packet too large (%d bytes required)", sz)); + SKIP_PKT(); + return PJ_ETOOSMALL; + } + + /* Read the payload */ + status = read_file(file, udp_payload, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading payload: %d", status)); + return status; + } + + sz_read += sz; + + *udp_payload_size = sz; + + // Some layers may have trailer, e.g: link eth2. + /* Check that we've read all the packets */ + // PJ_ASSERT_RETURN(sz_read == rec_incl, PJ_EBUG); + + /* Skip trailer */ + while (sz_read < rec_incl) { + sz = rec_incl - sz_read; + status = read_file(file, &tmp.eth, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading trailer: %d", status)); + return status; + } + sz_read += sz; + } + + return PJ_SUCCESS; + } + + /* Does not reach here */ +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c new file mode 100755 index 000000000..2b7e43366 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c @@ -0,0 +1,1761 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "resolver.c" + +/* Check that maximum DNS nameservers is not too large. + * This has got todo with the datatype to index the nameserver in the query. + */ +#if PJ_DNS_RESOLVER_MAX_NS > 256 +#error "PJ_DNS_RESOLVER_MAX_NS is too large (max=256)" +#endif + +#define RES_HASH_TABLE_SIZE 127 /**< Hash table size (must be 2^n-1 */ +#define PORT 53 /**< Default NS port. */ +#define Q_HASH_TABLE_SIZE 127 /**< Query hash table size */ +#define TIMER_SIZE 127 /**< Initial number of timers. */ +#define MAX_FD 3 /**< Maximum internal sockets. */ + +#define RES_BUF_SZ PJ_DNS_RESOLVER_RES_BUF_SIZE +#define UDPSZ PJ_DNS_RESOLVER_MAX_UDP_SIZE +#define TMP_SZ PJ_DNS_RESOLVER_TMP_BUF_SIZE + +/* Nameserver state */ +enum ns_state { + STATE_PROBING, + STATE_ACTIVE, + STATE_BAD, +}; + +static const char *state_names[3] = {"Probing", "Active", "Bad"}; + +/* + * Each nameserver entry. + * A name server is identified by its socket address (IP and port). + * Each NS will have a flag to indicate whether it's properly functioning. + */ +struct nameserver { + pj_sockaddr addr; /**< Server address. */ + + enum ns_state state; /**< Nameserver state. */ + pj_time_val state_expiry; /**< Time set next state. */ + pj_time_val rt_delay; /**< Response time. */ + + /* For calculating rt_delay: */ + pj_uint16_t q_id; /**< Query ID. */ + pj_time_val sent_time; /**< Time this query is sent. */ +}; + +/* Child query list head + * See comments on pj_dns_async_query below. + */ +struct query_head { + PJ_DECL_LIST_MEMBER(pj_dns_async_query); +}; + +/* Key to look for outstanding query and/or cached response */ +struct res_key { + pj_uint16_t qtype; /**< Query type. */ + char name[PJ_MAX_HOSTNAME]; /**< Name being queried */ +}; + +/* + * This represents each asynchronous query entry. + * This entry will be put in two hash tables, the first one keyed on the DNS + * transaction ID to match response with the query, and the second one keyed + * on "res_key" structure above to match a new request against outstanding + * requests. + * + * An asynchronous entry may have child entries; child entries are subsequent + * queries to the same resource while there is pending query on the same + * DNS resource name and type. When a query has child entries, once the + * response is received (or error occurs), the response will trigger callback + * invocations for all childs entries. + * + * Note: when application cancels the query, the callback member will be + * set to NULL, but for simplicity, the query will be let running. + */ +struct pj_dns_async_query { + PJ_DECL_LIST_MEMBER(pj_dns_async_query); /**< List member. */ + + pj_dns_resolver *resolver; /**< The resolver instance. */ + pj_uint16_t id; /**< Transaction ID. */ + + unsigned transmit_cnt; /**< Number of transmissions. */ + + struct res_key key; /**< Key to index this query. */ + pj_hash_entry_buf hbufid; /**< Hash buffer 1 */ + pj_hash_entry_buf hbufkey; /**< Hash buffer 2 */ + pj_timer_entry timer_entry; /**< Timer to manage timeouts */ + unsigned options; /**< Query options. */ + void *user_data; /**< Application data. */ + pj_dns_callback *cb; /**< Callback to be called. */ + struct query_head child_head; /**< Child queries list head. */ +}; + +/* This structure is used to keep cached response entry. + * The cache is a hash table keyed on "res_key" structure above. + */ +struct cached_res { + PJ_DECL_LIST_MEMBER(struct cached_res); + + pj_pool_t *pool; /**< Cache's pool. */ + struct res_key key; /**< Resource key. */ + pj_hash_entry_buf hbuf; /**< Hash buffer */ + pj_time_val expiry_time; /**< Expiration time. */ + pj_dns_parsed_packet *pkt; /**< The response packet. */ + unsigned ref_cnt; /**< Reference counter. */ +}; + +/* Resolver entry */ +struct pj_dns_resolver { + pj_str_t name; /**< Resolver instance name for id. */ + + /* Internals */ + pj_pool_t *pool; /**< Internal pool. */ + pj_grp_lock_t *grp_lock; /**< Group lock protection. */ + pj_bool_t own_timer; /**< Do we own timer? */ + pj_timer_heap_t *timer; /**< Timer instance. */ + pj_bool_t own_ioqueue; /**< Do we own ioqueue? */ + pj_ioqueue_t *ioqueue; /**< Ioqueue instance. */ + char tmp_pool[TMP_SZ]; /**< Temporary pool buffer. */ + + /* Socket */ + pj_sock_t udp_sock; /**< UDP socket. */ + pj_ioqueue_key_t *udp_key; /**< UDP socket ioqueue key. */ + unsigned char udp_rx_pkt[UDPSZ]; /**< UDP receive buffer. */ + unsigned char udp_tx_pkt[UDPSZ]; /**< UDP transmit buffer. */ + pj_ioqueue_op_key_t udp_op_rx_key; /**< UDP read operation key. */ + pj_ioqueue_op_key_t udp_op_tx_key; /**< UDP write operation key. */ + pj_sockaddr udp_src_addr; /**< Source address of packet */ + int udp_addr_len; /**< Source address length. */ + +#if PJ_HAS_IPV6 + /* IPv6 socket */ + pj_sock_t udp6_sock; /**< UDP socket. */ + pj_ioqueue_key_t *udp6_key; /**< UDP socket ioqueue key. */ + unsigned char udp6_rx_pkt[UDPSZ]; /**< UDP receive buffer. */ + // unsigned char udp6_tx_pkt[UDPSZ];/**< UDP transmit buffer. */ + pj_ioqueue_op_key_t udp6_op_rx_key; /**< UDP read operation key. */ + pj_ioqueue_op_key_t udp6_op_tx_key; /**< UDP write operation key. */ + pj_sockaddr udp6_src_addr; /**< Source address of packet */ + int udp6_addr_len; /**< Source address length. */ +#endif + + /* Settings */ + pj_dns_settings settings; /**< Resolver settings. */ + + /* Nameservers */ + unsigned ns_count; /**< Number of name servers. */ + struct nameserver ns[PJ_DNS_RESOLVER_MAX_NS]; /**< Array of NS. */ + + /* Last DNS transaction ID used. */ + pj_uint16_t last_id; + + /* Hash table for cached response */ + pj_hash_table_t *hrescache; /**< Cached response in hash table */ + + /* Pending asynchronous query, hashed by transaction ID. */ + pj_hash_table_t *hquerybyid; + + /* Pending asynchronous query, hashed by "res_key" */ + pj_hash_table_t *hquerybyres; + + /* Query entries free list */ + struct query_head query_free_nodes; +}; + +/* Callback from ioqueue when packet is received */ +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); + +/* Callback to be called when query has timed out */ +static void on_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry); + +/* Select which nameserver to use */ +static pj_status_t select_nameservers(pj_dns_resolver *resolver, unsigned *count, unsigned servers[]); + +/* Destructor */ +static void dns_resolver_on_destroy(void *member); + +/* Close UDP socket */ +static void close_sock(pj_dns_resolver *resv) +{ + /* Close existing socket */ + if (resv->udp_key != NULL) { + pj_ioqueue_unregister(resv->udp_key); + resv->udp_key = NULL; + resv->udp_sock = PJ_INVALID_SOCKET; + } else if (resv->udp_sock != PJ_INVALID_SOCKET) { + pj_sock_close(resv->udp_sock); + resv->udp_sock = PJ_INVALID_SOCKET; + } + +#if PJ_HAS_IPV6 + if (resv->udp6_key != NULL) { + pj_ioqueue_unregister(resv->udp6_key); + resv->udp6_key = NULL; + resv->udp6_sock = PJ_INVALID_SOCKET; + } else if (resv->udp6_sock != PJ_INVALID_SOCKET) { + pj_sock_close(resv->udp6_sock); + resv->udp6_sock = PJ_INVALID_SOCKET; + } +#endif +} + +/* Initialize UDP socket */ +static pj_status_t init_sock(pj_dns_resolver *resv) +{ + pj_ioqueue_callback socket_cb; + pj_sockaddr bound_addr; + pj_ssize_t rx_pkt_size; + pj_status_t status; + + /* Create the UDP socket */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &resv->udp_sock); + if (status != PJ_SUCCESS) + return status; + + /* Bind to any address/port */ + status = pj_sock_bind_in(resv->udp_sock, 0, 0); + if (status != PJ_SUCCESS) + return status; + + /* Register to ioqueue */ + pj_bzero(&socket_cb, sizeof(socket_cb)); + socket_cb.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock2(resv->pool, resv->ioqueue, resv->udp_sock, resv->grp_lock, resv, &socket_cb, + &resv->udp_key); + if (status != PJ_SUCCESS) + return status; + + pj_ioqueue_op_key_init(&resv->udp_op_rx_key, sizeof(resv->udp_op_rx_key)); + pj_ioqueue_op_key_init(&resv->udp_op_tx_key, sizeof(resv->udp_op_tx_key)); + + /* Start asynchronous read to the UDP socket */ + rx_pkt_size = sizeof(resv->udp_rx_pkt); + resv->udp_addr_len = sizeof(resv->udp_src_addr); + status = pj_ioqueue_recvfrom(resv->udp_key, &resv->udp_op_rx_key, resv->udp_rx_pkt, &rx_pkt_size, + PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp_src_addr, &resv->udp_addr_len); + if (status != PJ_EPENDING) + return status; + +#if PJ_HAS_IPV6 + /* Also setup IPv6 socket */ + + /* Create the UDP socket */ + status = pj_sock_socket(pj_AF_INET6(), pj_SOCK_DGRAM(), 0, &resv->udp6_sock); + if (status != PJ_SUCCESS) { + /* Skip IPv6 socket on system without IPv6 (see ticket #1953) */ + if (status == PJ_STATUS_FROM_OS(OSERR_EAFNOSUPPORT)) { + PJ_LOG(3, (resv->name.ptr, "System does not support IPv6, resolver will " + "ignore any IPv6 nameservers")); + return PJ_SUCCESS; + } + return status; + } + + /* Bind to any address/port */ + pj_sockaddr_init(pj_AF_INET6(), &bound_addr, NULL, 0); + status = pj_sock_bind(resv->udp6_sock, &bound_addr, pj_sockaddr_get_len(&bound_addr)); + if (status != PJ_SUCCESS) + return status; + + /* Register to ioqueue */ + pj_bzero(&socket_cb, sizeof(socket_cb)); + socket_cb.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock2(resv->pool, resv->ioqueue, resv->udp6_sock, resv->grp_lock, resv, &socket_cb, + &resv->udp6_key); + if (status != PJ_SUCCESS) + return status; + + pj_ioqueue_op_key_init(&resv->udp6_op_rx_key, sizeof(resv->udp6_op_rx_key)); + pj_ioqueue_op_key_init(&resv->udp6_op_tx_key, sizeof(resv->udp6_op_tx_key)); + + /* Start asynchronous read to the UDP socket */ + rx_pkt_size = sizeof(resv->udp6_rx_pkt); + resv->udp6_addr_len = sizeof(resv->udp6_src_addr); + status = pj_ioqueue_recvfrom(resv->udp6_key, &resv->udp6_op_rx_key, resv->udp6_rx_pkt, &rx_pkt_size, + PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp6_src_addr, &resv->udp6_addr_len); + if (status != PJ_EPENDING) + return status; +#else + PJ_UNUSED_ARG(bound_addr); +#endif + + return PJ_SUCCESS; +} + +/* Initialize DNS settings with default values */ +PJ_DEF(void) pj_dns_settings_default(pj_dns_settings *s) +{ + pj_bzero(s, sizeof(pj_dns_settings)); + s->qretr_delay = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY; + s->qretr_count = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT; + s->cache_max_ttl = PJ_DNS_RESOLVER_MAX_TTL; + s->good_ns_ttl = PJ_DNS_RESOLVER_GOOD_NS_TTL; + s->bad_ns_ttl = PJ_DNS_RESOLVER_BAD_NS_TTL; +} + +/* + * Create the resolver. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_create(pj_pool_factory *pf, const char *name, unsigned options, pj_timer_heap_t *timer, + pj_ioqueue_t *ioqueue, pj_dns_resolver **p_resolver) +{ + pj_pool_t *pool; + pj_dns_resolver *resv; + pj_status_t status; + + /* Sanity check */ + PJ_ASSERT_RETURN(pf && p_resolver, PJ_EINVAL); + + if (name == NULL) + name = THIS_FILE; + + /* Create and initialize resolver instance */ + pool = pj_pool_create(pf, name, 4000, 4000, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Create pool and name */ + resv = PJ_POOL_ZALLOC_T(pool, struct pj_dns_resolver); + resv->pool = pool; + resv->udp_sock = PJ_INVALID_SOCKET; + pj_strdup2_with_null(pool, &resv->name, name); + + /* Create group lock */ + status = pj_grp_lock_create_w_handler(pool, NULL, resv, &dns_resolver_on_destroy, &resv->grp_lock); + if (status != PJ_SUCCESS) + goto on_error; + + pj_grp_lock_add_ref(resv->grp_lock); + + /* Timer, ioqueue, and settings */ + resv->timer = timer; + resv->ioqueue = ioqueue; + resv->last_id = 1; + + pj_dns_settings_default(&resv->settings); + resv->settings.options = options; + + /* Create the timer heap if one is not specified */ + if (resv->timer == NULL) { + resv->own_timer = PJ_TRUE; + status = pj_timer_heap_create(pool, TIMER_SIZE, &resv->timer); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Create the ioqueue if one is not specified */ + if (resv->ioqueue == NULL) { + resv->own_ioqueue = PJ_TRUE; + status = pj_ioqueue_create(pool, MAX_FD, &resv->ioqueue); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Response cache hash table */ + resv->hrescache = pj_hash_create(pool, RES_HASH_TABLE_SIZE); + + /* Query hash table and free list. */ + resv->hquerybyid = pj_hash_create(pool, Q_HASH_TABLE_SIZE); + resv->hquerybyres = pj_hash_create(pool, Q_HASH_TABLE_SIZE); + pj_list_init(&resv->query_free_nodes); + + /* Initialize the UDP socket */ + status = init_sock(resv); + if (status != PJ_SUCCESS) + goto on_error; + + /* Looks like everything is okay */ + *p_resolver = resv; + return PJ_SUCCESS; + +on_error: + pj_dns_resolver_destroy(resv, PJ_FALSE); + return status; +} + +void dns_resolver_on_destroy(void *member) +{ + pj_dns_resolver *resolver = (pj_dns_resolver *)member; + pj_pool_safe_release(&resolver->pool); +} + +/* + * Destroy DNS resolver instance. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_destroy(pj_dns_resolver *resolver, pj_bool_t notify) +{ + pj_hash_iterator_t it_buf, *it; + PJ_ASSERT_RETURN(resolver, PJ_EINVAL); + + if (notify) { + /* + * Notify pending queries if requested. + */ + it = pj_hash_first(resolver->hquerybyid, &it_buf); + while (it) { + pj_dns_async_query *q = (pj_dns_async_query *)pj_hash_this(resolver->hquerybyid, it); + pj_dns_async_query *cq; + if (q->cb) + (*q->cb)(q->user_data, PJ_ECANCELLED, NULL); + + cq = q->child_head.next; + while (cq != (pj_dns_async_query *)&q->child_head) { + if (cq->cb) + (*cq->cb)(cq->user_data, PJ_ECANCELLED, NULL); + cq = cq->next; + } + it = pj_hash_next(resolver->hquerybyid, it); + } + } + + /* Destroy cached entries */ + it = pj_hash_first(resolver->hrescache, &it_buf); + while (it) { + struct cached_res *cache; + + cache = (struct cached_res *)pj_hash_this(resolver->hrescache, it); + pj_hash_set(NULL, resolver->hrescache, &cache->key, sizeof(cache->key), 0, NULL); + pj_pool_release(cache->pool); + + it = pj_hash_first(resolver->hrescache, &it_buf); + } + + if (resolver->own_timer && resolver->timer) { + pj_timer_heap_destroy(resolver->timer); + resolver->timer = NULL; + } + + close_sock(resolver); + + if (resolver->own_ioqueue && resolver->ioqueue) { + pj_ioqueue_destroy(resolver->ioqueue); + resolver->ioqueue = NULL; + } + + pj_grp_lock_dec_ref(resolver->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Configure name servers for the DNS resolver. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, const pj_str_t servers[], const pj_uint16_t ports[]) +{ + unsigned i; + pj_time_val now; + pj_status_t status; + + PJ_ASSERT_RETURN(resolver && count && servers, PJ_EINVAL); + PJ_ASSERT_RETURN(count < PJ_DNS_RESOLVER_MAX_NS, PJ_EINVAL); + + pj_grp_lock_acquire(resolver->grp_lock); + + if (count > PJ_DNS_RESOLVER_MAX_NS) + count = PJ_DNS_RESOLVER_MAX_NS; + + resolver->ns_count = 0; + pj_bzero(resolver->ns, sizeof(resolver->ns)); + + pj_gettimeofday(&now); + + for (i = 0; i < count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + status = pj_sockaddr_init(pj_AF_INET(), &ns->addr, &servers[i], (pj_uint16_t)(ports ? ports[i] : PORT)); + if (status != PJ_SUCCESS) + status = pj_sockaddr_init(pj_AF_INET6(), &ns->addr, &servers[i], (pj_uint16_t)(ports ? ports[i] : PORT)); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(resolver->grp_lock); + return PJLIB_UTIL_EDNSINNSADDR; + } + + ns->state = STATE_ACTIVE; + ns->state_expiry = now; + ns->rt_delay.sec = 10; + } + + resolver->ns_count = count; + + pj_grp_lock_release(resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * Modify the resolver settings. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver, const pj_dns_settings *st) +{ + PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL); + + pj_grp_lock_acquire(resolver->grp_lock); + pj_memcpy(&resolver->settings, st, sizeof(*st)); + pj_grp_lock_release(resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * Get the resolver current settings. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_get_settings(pj_dns_resolver *resolver, pj_dns_settings *st) +{ + PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL); + + pj_grp_lock_acquire(resolver->grp_lock); + pj_memcpy(st, &resolver->settings, sizeof(*st)); + pj_grp_lock_release(resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * Poll for events from the resolver. + */ +PJ_DEF(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver, const pj_time_val *timeout) +{ + PJ_ASSERT_ON_FAIL(resolver, return ); + + pj_grp_lock_acquire(resolver->grp_lock); + pj_timer_heap_poll(resolver->timer, NULL); + pj_grp_lock_release(resolver->grp_lock); + + pj_ioqueue_poll(resolver->ioqueue, timeout); +} + +/* Get one query node from the free node, if any, or allocate + * a new one. + */ +static pj_dns_async_query *alloc_qnode(pj_dns_resolver *resolver, unsigned options, void *user_data, + pj_dns_callback *cb) +{ + pj_dns_async_query *q; + + /* Merge query options with resolver options */ + options |= resolver->settings.options; + + if (!pj_list_empty(&resolver->query_free_nodes)) { + q = resolver->query_free_nodes.next; + pj_list_erase(q); + pj_bzero(q, sizeof(*q)); + } else { + q = PJ_POOL_ZALLOC_T(resolver->pool, pj_dns_async_query); + } + + /* Init query */ + q->resolver = resolver; + q->options = options; + q->user_data = user_data; + q->cb = cb; + pj_list_init(&q->child_head); + + return q; +} + +/* + * Transmit query. + */ +static pj_status_t transmit_query(pj_dns_resolver *resolver, pj_dns_async_query *q) +{ + unsigned pkt_size; + unsigned i, server_cnt, send_cnt; + unsigned servers[PJ_DNS_RESOLVER_MAX_NS]; + pj_time_val now; + pj_str_t name; + pj_time_val delay; + pj_status_t status; + + /* Select which nameserver(s) to send requests to. */ + server_cnt = PJ_ARRAY_SIZE(servers); + status = select_nameservers(resolver, &server_cnt, servers); + if (status != PJ_SUCCESS) { + return status; + } + + if (server_cnt == 0) { + return PJLIB_UTIL_EDNSNOWORKINGNS; + } + + /* Start retransmit/timeout timer for the query */ + pj_assert(q->timer_entry.id == 0); + q->timer_entry.id = 1; + q->timer_entry.user_data = q; + q->timer_entry.cb = &on_timeout; + + delay.sec = 0; + delay.msec = resolver->settings.qretr_delay; + pj_time_val_normalize(&delay); + status = pj_timer_heap_schedule_w_grp_lock(resolver->timer, &q->timer_entry, &delay, 1, resolver->grp_lock); + if (status != PJ_SUCCESS) { + return status; + } + + /* Check if the socket is available for sending */ + if (pj_ioqueue_is_pending(resolver->udp_key, &resolver->udp_op_tx_key) +#if PJ_HAS_IPV6 + || (resolver->udp6_key && pj_ioqueue_is_pending(resolver->udp6_key, &resolver->udp6_op_tx_key)) +#endif + ) { + ++q->transmit_cnt; + PJ_LOG(4, (resolver->name.ptr, "Socket busy in transmitting DNS %s query for %s%s", + pj_dns_get_type_name(q->key.qtype), q->key.name, + (q->transmit_cnt < resolver->settings.qretr_count ? ", will try again later" : ""))); + return PJ_SUCCESS; + } + + /* Create DNS query packet */ + pkt_size = sizeof(resolver->udp_tx_pkt); + name = pj_str(q->key.name); + status = pj_dns_make_query(resolver->udp_tx_pkt, &pkt_size, q->id, q->key.qtype, &name); + if (status != PJ_SUCCESS) { + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + return status; + } + + /* Get current time. */ + pj_gettimeofday(&now); + + /* Send the packet to name servers */ + send_cnt = 0; + for (i = 0; i < server_cnt; ++i) { + char addr[PJ_INET6_ADDRSTRLEN]; + pj_ssize_t sent = (pj_ssize_t)pkt_size; + struct nameserver *ns = &resolver->ns[servers[i]]; + + if (ns->addr.addr.sa_family == pj_AF_INET()) { + status = pj_ioqueue_sendto(resolver->udp_key, &resolver->udp_op_tx_key, resolver->udp_tx_pkt, &sent, 0, + &ns->addr, pj_sockaddr_get_len(&ns->addr)); + if (status == PJ_SUCCESS || status == PJ_EPENDING) + send_cnt++; + } +#if PJ_HAS_IPV6 + else if (resolver->udp6_key) { + status = pj_ioqueue_sendto(resolver->udp6_key, &resolver->udp6_op_tx_key, resolver->udp_tx_pkt, &sent, 0, + &ns->addr, pj_sockaddr_get_len(&ns->addr)); + if (status == PJ_SUCCESS || status == PJ_EPENDING) + send_cnt++; + } +#endif + else { + continue; + } + + PJ_PERROR(4, (resolver->name.ptr, status, "%s %d bytes to NS %d (%s:%d): DNS %s query for %s", + (q->transmit_cnt == 0 ? "Transmitting" : "Re-transmitting"), (int)pkt_size, servers[i], + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), + pj_dns_get_type_name(q->key.qtype), q->key.name)); + + if (ns->q_id == 0) { + ns->q_id = q->id; + ns->sent_time = now; + } + } + + if (send_cnt == 0) { + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + return PJLIB_UTIL_EDNSNOWORKINGNS; + } + + ++q->transmit_cnt; + + return PJ_SUCCESS; +} + +/* + * Initialize resource key for hash table lookup. + */ +static void init_res_key(struct res_key *key, int type, const pj_str_t *name) +{ + unsigned i; + pj_size_t len; + char *dst = key->name; + const char *src = name->ptr; + + pj_bzero(key, sizeof(struct res_key)); + key->qtype = (pj_uint16_t)type; + + len = name->slen; + if (len > PJ_MAX_HOSTNAME) + len = PJ_MAX_HOSTNAME; + + /* Copy key, in lowercase */ + for (i = 0; i < len; ++i) { + *dst++ = (char)pj_tolower(*src++); + } +} + +/* Allocate new cache entry */ +static struct cached_res *alloc_entry(pj_dns_resolver *resolver) +{ + pj_pool_t *pool; + struct cached_res *cache; + + pool = pj_pool_create(resolver->pool->factory, "dnscache", RES_BUF_SZ, 256, NULL); + cache = PJ_POOL_ZALLOC_T(pool, struct cached_res); + cache->pool = pool; + cache->ref_cnt = 1; + + return cache; +} + +/* Re-allocate cache entry, to free cached packet */ +static void reset_entry(struct cached_res **p_cached) +{ + pj_pool_t *pool; + struct cached_res *cache = *p_cached; + unsigned ref_cnt; + + pool = cache->pool; + ref_cnt = cache->ref_cnt; + + pj_pool_reset(pool); + + cache = PJ_POOL_ZALLOC_T(pool, struct cached_res); + cache->pool = pool; + cache->ref_cnt = ref_cnt; + *p_cached = cache; +} + +/* Put unused/expired cached entry to the free list */ +static void free_entry(pj_dns_resolver *resolver, struct cached_res *cache) +{ + PJ_UNUSED_ARG(resolver); + pj_pool_release(cache->pool); +} + +/* + * Create and start asynchronous DNS query for a single resource. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_start_query(pj_dns_resolver *resolver, const pj_str_t *name, int type, unsigned options, + pj_dns_callback *cb, void *user_data, pj_dns_async_query **p_query) +{ + pj_time_val now; + struct res_key key; + struct cached_res *cache; + pj_dns_async_query *q, *p_q = NULL; + pj_uint32_t hval; + pj_status_t status = PJ_SUCCESS; + + /* Validate arguments */ + PJ_ASSERT_RETURN(resolver && name && type, PJ_EINVAL); + + /* Check name is not too long. */ + PJ_ASSERT_RETURN(name->slen > 0 && name->slen < PJ_MAX_HOSTNAME, PJ_ENAMETOOLONG); + + /* Check type */ + PJ_ASSERT_RETURN(type > 0 && type < 0xFFFF, PJ_EINVAL); + + /* Build resource key for looking up hash tables */ + init_res_key(&key, type, name); + + /* Start working with the resolver */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Get current time. */ + pj_gettimeofday(&now); + + /* First, check if we have cached response for the specified name/type, + * and the cached entry has not expired. + */ + hval = 0; + cache = (struct cached_res *)pj_hash_get(resolver->hrescache, &key, sizeof(key), &hval); + if (cache) { + /* We've found a cached entry. */ + + /* Check for expiration */ + if (PJ_TIME_VAL_GT(cache->expiry_time, now)) { + + /* Log */ + PJ_LOG(5, + (resolver->name.ptr, "Picked up DNS %s record for %.*s from cache, ttl=%d", + pj_dns_get_type_name(type), (int)name->slen, name->ptr, (int)(cache->expiry_time.sec - now.sec))); + + /* Map DNS Rcode in the response into PJLIB status name space */ + status = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags); + status = PJ_STATUS_FROM_DNS_RCODE(status); + + /* Workaround for deadlock problem. Need to increment the cache's + * ref counter first before releasing mutex, so the cache won't be + * destroyed by other thread while in callback. + */ + cache->ref_cnt++; + pj_grp_lock_release(resolver->grp_lock); + + /* This cached response is still valid. Just return this + * response to caller. + */ + if (cb) { + (*cb)(user_data, status, cache->pkt); + } + + /* Done. No host resolution is necessary */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Decrement the ref counter. Also check if it is time to free + * the cache (as it has been expired). + */ + cache->ref_cnt--; + if (cache->ref_cnt <= 0) + free_entry(resolver, cache); + + /* Must return PJ_SUCCESS */ + status = PJ_SUCCESS; + + /* + * We cannot write to *p_query after calling cb because what + * p_query points to may have been freed by cb. + * Refer to ticket #1974. + */ + pj_grp_lock_release(resolver->grp_lock); + return status; + } + + /* At this point, we have a cached entry, but this entry has expired. + * Remove this entry from the cached list. + */ + pj_hash_set(NULL, resolver->hrescache, &key, sizeof(key), 0, NULL); + + /* Also free the cache, if it is not being used (by callback). */ + cache->ref_cnt--; + if (cache->ref_cnt <= 0) + free_entry(resolver, cache); + + /* Must continue with creating a query now */ + } + + /* Next, check if we have pending query on the same resource */ + q = (pj_dns_async_query *)pj_hash_get(resolver->hquerybyres, &key, sizeof(key), NULL); + if (q) { + /* Yes, there's another pending query to the same key. + * Just create a new child query and add this query to + * pending query's child queries. + */ + pj_dns_async_query *nq; + + nq = alloc_qnode(resolver, options, user_data, cb); + pj_list_push_back(&q->child_head, nq); + + /* Done. This child query will be notified once the "parent" + * query completes. + */ + p_q = nq; + status = PJ_SUCCESS; + goto on_return; + } + + /* There's no pending query to the same key, initiate a new one. */ + q = alloc_qnode(resolver, options, user_data, cb); + + /* Save the ID and key */ + /* TODO: dnsext-forgery-resilient: randomize id for security */ + q->id = resolver->last_id++; + if (resolver->last_id == 0) + resolver->last_id = 1; + pj_memcpy(&q->key, &key, sizeof(struct res_key)); + + /* Send the query */ + status = transmit_query(resolver, q); + if (status != PJ_SUCCESS) { + pj_list_push_back(&resolver->query_free_nodes, q); + goto on_return; + } + + /* Add query entry to the hash tables */ + pj_hash_set_np(resolver->hquerybyid, &q->id, sizeof(q->id), 0, q->hbufid, q); + pj_hash_set_np(resolver->hquerybyres, &q->key, sizeof(q->key), 0, q->hbufkey, q); + + p_q = q; + +on_return: + if (p_query) + *p_query = p_q; + + pj_grp_lock_release(resolver->grp_lock); + return status; +} + +/* + * Cancel a pending query. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query, pj_bool_t notify) +{ + pj_dns_callback *cb; + + PJ_ASSERT_RETURN(query, PJ_EINVAL); + + pj_grp_lock_acquire(query->resolver->grp_lock); + + if (query->timer_entry.id == 1) { + pj_timer_heap_cancel_if_active(query->resolver->timer, &query->timer_entry, 0); + } + + cb = query->cb; + query->cb = NULL; + + if (notify) + (*cb)(query->user_data, PJ_ECANCELLED, NULL); + + pj_grp_lock_release(query->resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * DNS response containing A packet. + */ +PJ_DEF(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, pj_dns_a_record *rec) +{ + enum { MAX_SEARCH = 20 }; + pj_str_t hostname, alias = {NULL, 0}, *resname; + pj_size_t bufstart = 0; + pj_size_t bufleft = sizeof(rec->buf_); + unsigned i, ansidx, search_cnt = 0; + + PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); + + /* Init the record */ + pj_bzero(rec, sizeof(pj_dns_a_record)); + + /* Return error if there's error in the packet. */ + if (PJ_DNS_GET_RCODE(pkt->hdr.flags)) + return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags)); + + /* Return error if there's no query section */ + if (pkt->hdr.qdcount == 0) + return PJLIB_UTIL_EDNSINANSWER; + + /* Return error if there's no answer */ + if (pkt->hdr.anscount == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + /* Get the hostname from the query. */ + hostname = pkt->q[0].name; + + /* Copy hostname to the record */ + if (hostname.slen > (int)bufleft) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen); + rec->name.ptr = &rec->buf_[bufstart]; + rec->name.slen = hostname.slen; + + bufstart += hostname.slen; + bufleft -= hostname.slen; + + /* Find the first RR which name matches the hostname */ + for (ansidx = 0; ansidx < pkt->hdr.anscount; ++ansidx) { + if (pj_stricmp(&pkt->ans[ansidx].name, &hostname) == 0) + break; + } + + if (ansidx == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + resname = &hostname; + + /* Keep following CNAME records. */ + while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME && search_cnt++ < MAX_SEARCH) { + resname = &pkt->ans[ansidx].rdata.cname.name; + + if (!alias.slen) + alias = *resname; + + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pj_stricmp(resname, &pkt->ans[i].name) == 0) { + break; + } + } + + if (i == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + ansidx = i; + } + + if (search_cnt >= MAX_SEARCH) + return PJLIB_UTIL_EDNSINANSWER; + + if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A) + return PJLIB_UTIL_EDNSINANSWER; + + /* Copy alias to the record, if present. */ + if (alias.slen) { + if (alias.slen > (int)bufleft) + return PJ_ENAMETOOLONG; + + pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen); + rec->alias.ptr = &rec->buf_[bufstart]; + rec->alias.slen = alias.slen; + + bufstart += alias.slen; + bufleft -= alias.slen; + } + + /* Get the IP addresses. */ + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pkt->ans[i].type == PJ_DNS_TYPE_A && pj_stricmp(&pkt->ans[i].name, resname) == 0 && + rec->addr_count < PJ_DNS_MAX_IP_IN_A_REC) { + rec->addr[rec->addr_count++].s_addr = pkt->ans[i].rdata.a.ip_addr.s_addr; + } + } + + if (rec->addr_count == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + return PJ_SUCCESS; +} + +/* + * DNS response containing A and/or AAAA packet. + */ +PJ_DEF(pj_status_t) pj_dns_parse_addr_response(const pj_dns_parsed_packet *pkt, pj_dns_addr_record *rec) +{ + enum { MAX_SEARCH = 20 }; + pj_str_t hostname, alias = {NULL, 0}, *resname; + pj_size_t bufstart = 0; + pj_size_t bufleft; + unsigned i, ansidx, cnt = 0; + + PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); + + /* Init the record */ + pj_bzero(rec, sizeof(*rec)); + + bufleft = sizeof(rec->buf_); + + /* Return error if there's error in the packet. */ + if (PJ_DNS_GET_RCODE(pkt->hdr.flags)) + return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags)); + + /* Return error if there's no query section */ + if (pkt->hdr.qdcount == 0) + return PJLIB_UTIL_EDNSINANSWER; + + /* Return error if there's no answer */ + if (pkt->hdr.anscount == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + /* Get the hostname from the query. */ + hostname = pkt->q[0].name; + + /* Copy hostname to the record */ + if (hostname.slen > (int)bufleft) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen); + rec->name.ptr = &rec->buf_[bufstart]; + rec->name.slen = hostname.slen; + + bufstart += hostname.slen; + bufleft -= hostname.slen; + + /* Find the first RR which name matches the hostname. */ + for (ansidx = 0; ansidx < pkt->hdr.anscount; ++ansidx) { + if (pj_stricmp(&pkt->ans[ansidx].name, &hostname) == 0) + break; + } + + if (ansidx == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + resname = &hostname; + + /* Keep following CNAME records. */ + while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME && cnt++ < MAX_SEARCH) { + resname = &pkt->ans[ansidx].rdata.cname.name; + + if (!alias.slen) + alias = *resname; + + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pj_stricmp(resname, &pkt->ans[i].name) == 0) + break; + } + + if (i == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + ansidx = i; + } + + if (cnt >= MAX_SEARCH) + return PJLIB_UTIL_EDNSINANSWER; + + if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A && pkt->ans[ansidx].type != PJ_DNS_TYPE_AAAA) { + return PJLIB_UTIL_EDNSINANSWER; + } + + /* Copy alias to the record, if present. */ + if (alias.slen) { + if (alias.slen > (int)bufleft) + return PJ_ENAMETOOLONG; + + pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen); + rec->alias.ptr = &rec->buf_[bufstart]; + rec->alias.slen = alias.slen; + + bufstart += alias.slen; + bufleft -= alias.slen; + } + + /* Get the IP addresses. */ + cnt = 0; + for (i = 0; i < pkt->hdr.anscount && cnt < PJ_DNS_MAX_IP_IN_A_REC; ++i) { + if ((pkt->ans[i].type == PJ_DNS_TYPE_A || pkt->ans[i].type == PJ_DNS_TYPE_AAAA) && + pj_stricmp(&pkt->ans[i].name, resname) == 0) { + if (pkt->ans[i].type == PJ_DNS_TYPE_A) { + rec->addr[cnt].af = pj_AF_INET(); + rec->addr[cnt].ip.v4 = pkt->ans[i].rdata.a.ip_addr; + } else { + rec->addr[cnt].af = pj_AF_INET6(); + rec->addr[cnt].ip.v6 = pkt->ans[i].rdata.aaaa.ip_addr; + } + ++cnt; + } + } + rec->addr_count = cnt; + + if (cnt == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + return PJ_SUCCESS; +} + +/* Set nameserver state */ +static void set_nameserver_state(pj_dns_resolver *resolver, unsigned index, enum ns_state state, const pj_time_val *now) +{ + struct nameserver *ns = &resolver->ns[index]; + enum ns_state old_state = ns->state; + char addr[PJ_INET6_ADDRSTRLEN]; + + ns->state = state; + ns->state_expiry = *now; + + if (state == STATE_PROBING) + ns->state_expiry.sec += ((resolver->settings.qretr_count + 2) * resolver->settings.qretr_delay) / 1000; + else if (state == STATE_ACTIVE) + ns->state_expiry.sec += resolver->settings.good_ns_ttl; + else + ns->state_expiry.sec += resolver->settings.bad_ns_ttl; + + PJ_LOG(5, (resolver->name.ptr, "Nameserver %s:%d state changed %s --> %s", + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), + state_names[old_state], state_names[state])); +} + +/* Select which nameserver(s) to use. Note this may return multiple + * name servers. The algorithm to select which nameservers to be + * sent the request to is as follows: + * - select the first nameserver that is known to be good for the + * last PJ_DNS_RESOLVER_GOOD_NS_TTL interval. + * - for all NSes, if last_known_good >= PJ_DNS_RESOLVER_GOOD_NS_TTL, + * include the NS to re-check again that the server is still good, + * unless the NS is known to be bad in the last PJ_DNS_RESOLVER_BAD_NS_TTL + * interval. + * - for all NSes, if last_known_bad >= PJ_DNS_RESOLVER_BAD_NS_TTL, + * also include the NS to re-check again that the server is still bad. + */ +static pj_status_t select_nameservers(pj_dns_resolver *resolver, unsigned *count, unsigned servers[]) +{ + unsigned i, max_count = *count; + int min; + pj_time_val now; + + pj_assert(max_count > 0); + + *count = 0; + servers[0] = 0xFFFF; + + /* Check that nameservers are configured. */ + if (resolver->ns_count == 0) + return PJLIB_UTIL_EDNSNONS; + + pj_gettimeofday(&now); + + /* Select one Active nameserver with best response time. */ + for (min = -1, i = 0; i < resolver->ns_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (ns->state != STATE_ACTIVE) + continue; + + if (min == -1) + min = i; + else if (PJ_TIME_VAL_LT(ns->rt_delay, resolver->ns[min].rt_delay)) + min = i; + } + if (min != -1) { + servers[0] = min; + ++(*count); + } + + /* Scan nameservers. */ + for (i = 0; i < resolver->ns_count && *count < max_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (PJ_TIME_VAL_LTE(ns->state_expiry, now)) { + if (ns->state == STATE_PROBING) { + set_nameserver_state(resolver, i, STATE_BAD, &now); + } else { + set_nameserver_state(resolver, i, STATE_PROBING, &now); + if ((int)i != min) { + servers[*count] = i; + ++(*count); + } + } + } else if (ns->state == STATE_PROBING && (int)i != min) { + servers[*count] = i; + ++(*count); + } + } + + return PJ_SUCCESS; +} + +/* Update name server status */ +static void report_nameserver_status(pj_dns_resolver *resolver, const pj_sockaddr *ns_addr, + const pj_dns_parsed_packet *pkt) +{ + unsigned i; + int rcode; + pj_uint32_t q_id; + pj_time_val now; + pj_bool_t is_good; + + /* Only mark nameserver as "bad" if it returned non-parseable response or + * it returned the following status codes + */ + if (pkt) { + rcode = PJ_DNS_GET_RCODE(pkt->hdr.flags); + q_id = pkt->hdr.id; + } else { + rcode = 0; + q_id = (pj_uint32_t)-1; + } + + /* Some nameserver is reported to respond with PJ_DNS_RCODE_SERVFAIL for + * missing AAAA record, and the standard doesn't seem to specify that + * SERVFAIL should prevent the server to be contacted again for other + * queries. So let's not mark nameserver as bad for SERVFAIL response. + */ + if (!pkt || /* rcode == PJ_DNS_RCODE_SERVFAIL || */ + rcode == PJ_DNS_RCODE_REFUSED || rcode == PJ_DNS_RCODE_NOTAUTH) { + is_good = PJ_FALSE; + } else { + is_good = PJ_TRUE; + } + + /* Mark time */ + pj_gettimeofday(&now); + + /* Recheck all nameservers. */ + for (i = 0; i < resolver->ns_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (pj_sockaddr_cmp(&ns->addr, ns_addr) == 0) { + if (q_id == ns->q_id) { + /* Calculate response time */ + pj_time_val rt = now; + PJ_TIME_VAL_SUB(rt, ns->sent_time); + ns->rt_delay = rt; + ns->q_id = 0; + } + set_nameserver_state(resolver, i, (is_good ? STATE_ACTIVE : STATE_BAD), &now); + break; + } + } +} + +/* Update response cache */ +static void update_res_cache(pj_dns_resolver *resolver, const struct res_key *key, pj_status_t status, + pj_bool_t set_expiry, const pj_dns_parsed_packet *pkt) +{ + struct cached_res *cache; + pj_uint32_t hval = 0, ttl; + + /* If status is unsuccessful, clear the same entry from the cache */ + if (status != PJ_SUCCESS) { + cache = (struct cached_res *)pj_hash_get(resolver->hrescache, key, sizeof(*key), &hval); + /* Remove the entry before releasing its pool (see ticket #1710) */ + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + + /* Free the entry */ + if (cache && --cache->ref_cnt <= 0) + free_entry(resolver, cache); + } + + /* Calculate expiration time. */ + if (set_expiry) { + if (pkt->hdr.anscount == 0 || status != PJ_SUCCESS) { + /* If we don't have answers for the name, then give a different + * ttl value (note: PJ_DNS_RESOLVER_INVALID_TTL may be zero, + * which means that invalid names won't be kept in the cache) + */ + ttl = PJ_DNS_RESOLVER_INVALID_TTL; + + } else { + /* Otherwise get the minimum TTL from the answers */ + unsigned i; + ttl = 0xFFFFFFFF; + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pkt->ans[i].ttl < ttl) + ttl = pkt->ans[i].ttl; + } + } + } else { + ttl = 0xFFFFFFFF; + } + + /* Apply maximum TTL */ + if (ttl > resolver->settings.cache_max_ttl) + ttl = resolver->settings.cache_max_ttl; + + /* Get a cache response entry */ + cache = (struct cached_res *)pj_hash_get(resolver->hrescache, key, sizeof(*key), &hval); + + /* If TTL is zero, clear the same entry in the hash table */ + if (ttl == 0) { + /* Remove the entry before releasing its pool (see ticket #1710) */ + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + + /* Free the entry */ + if (cache && --cache->ref_cnt <= 0) + free_entry(resolver, cache); + return; + } + + if (cache == NULL) { + cache = alloc_entry(resolver); + } else { + /* Remove the entry before resetting its pool (see ticket #1710) */ + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + + if (cache->ref_cnt > 1) { + /* When cache entry is being used by callback (to app), + * just decrement ref_cnt so it will be freed after + * the callback returns and allocate new entry. + */ + cache->ref_cnt--; + cache = alloc_entry(resolver); + } else { + /* Reset cache to avoid bloated cache pool */ + reset_entry(&cache); + } + } + + /* Duplicate the packet. + * We don't need to keep the NS and AR sections from the packet, + * so exclude from duplication. We do need to keep the Query + * section since DNS A parser needs the query section to know + * the name being requested. + */ + pj_dns_packet_dup(cache->pool, pkt, PJ_DNS_NO_NS | PJ_DNS_NO_AR, &cache->pkt); + + /* Calculate expiration time */ + if (set_expiry) { + pj_gettimeofday(&cache->expiry_time); + cache->expiry_time.sec += ttl; + } else { + cache->expiry_time.sec = 0x7FFFFFFFL; + cache->expiry_time.msec = 0; + } + + /* Copy key to the cached response */ + pj_memcpy(&cache->key, key, sizeof(*key)); + + /* Update the hash table */ + pj_hash_set_np(resolver->hrescache, &cache->key, sizeof(*key), hval, cache->hbuf, cache); +} + +/* Callback to be called when query has timed out */ +static void on_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pj_dns_resolver *resolver; + pj_dns_async_query *q, *cq; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + + q = (pj_dns_async_query *)entry->user_data; + resolver = q->resolver; + + pj_grp_lock_acquire(resolver->grp_lock); + + /* Recheck that this query is still pending, since there is a slight + * possibility of race condition (timer elapsed while at the same time + * response arrives) + */ + if (pj_hash_get(resolver->hquerybyid, &q->id, sizeof(q->id), NULL) == NULL) { + /* Yeah, this query is done. */ + pj_grp_lock_release(resolver->grp_lock); + return; + } + + /* Invalidate id. */ + q->timer_entry.id = 0; + + /* Check to see if we should retransmit instead of time out */ + if (q->transmit_cnt < resolver->settings.qretr_count) { + status = transmit_query(resolver, q); + if (status == PJ_SUCCESS) { + pj_grp_lock_release(resolver->grp_lock); + return; + } else { + /* Error occurs */ + PJ_PERROR(4, (resolver->name.ptr, status, "Error transmitting request")); + + /* Let it fallback to timeout section below */ + } + } + + /* Clear hash table entries */ + pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + + /* Workaround for deadlock problem in #1565 (similar to #1108) */ + pj_grp_lock_release(resolver->grp_lock); + + /* Call application callback, if any. */ + if (q->cb) + (*q->cb)(q->user_data, PJ_ETIMEDOUT, NULL); + + /* Call application callback for child queries. */ + cq = q->child_head.next; + while (cq != (void *)&q->child_head) { + if (cq->cb) + (*cq->cb)(cq->user_data, PJ_ETIMEDOUT, NULL); + cq = cq->next; + } + + /* Workaround for deadlock problem in #1565 (similar to #1108) */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Clear data */ + q->timer_entry.id = 0; + q->user_data = NULL; + + /* Put child entries into recycle list */ + cq = q->child_head.next; + while (cq != (void *)&q->child_head) { + pj_dns_async_query *next = cq->next; + pj_list_push_back(&resolver->query_free_nodes, cq); + cq = next; + } + + /* Put query entry into recycle list */ + pj_list_push_back(&resolver->query_free_nodes, q); + + pj_grp_lock_release(resolver->grp_lock); +} + +/* Callback from ioqueue when packet is received */ +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) +{ + pj_dns_resolver *resolver; + pj_pool_t *pool = NULL; + pj_dns_parsed_packet *dns_pkt; + pj_dns_async_query *q; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_sockaddr *src_addr; + int *src_addr_len; + unsigned char *rx_pkt; + pj_ssize_t rx_pkt_size; + pj_status_t status; + PJ_USE_EXCEPTION; + + resolver = (pj_dns_resolver *)pj_ioqueue_get_user_data(key); + pj_assert(resolver); + +#if PJ_HAS_IPV6 + if (key == resolver->udp6_key) { + src_addr = &resolver->udp6_src_addr; + src_addr_len = &resolver->udp6_addr_len; + rx_pkt = resolver->udp6_rx_pkt; + rx_pkt_size = sizeof(resolver->udp6_rx_pkt); + } else +#endif + { + src_addr = &resolver->udp_src_addr; + src_addr_len = &resolver->udp_addr_len; + rx_pkt = resolver->udp_rx_pkt; + rx_pkt_size = sizeof(resolver->udp_rx_pkt); + } + + pj_grp_lock_acquire(resolver->grp_lock); + + /* Check for errors */ + if (bytes_read < 0) { + status = (pj_status_t)-bytes_read; + PJ_PERROR(4, (resolver->name.ptr, status, "DNS resolver read error")); + + goto read_next_packet; + } + + PJ_LOG(5, (resolver->name.ptr, "Received %d bytes DNS response from %s:%d", (int)bytes_read, + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr))); + + /* Check for zero packet */ + if (bytes_read == 0) + goto read_next_packet; + + /* Create temporary pool from a fixed buffer */ + pool = pj_pool_create_on_buf("restmp", resolver->tmp_pool, sizeof(resolver->tmp_pool)); + + /* Parse DNS response */ + status = -1; + dns_pkt = NULL; + PJ_TRY + { + status = pj_dns_parse_packet(pool, rx_pkt, (unsigned)bytes_read, &dns_pkt); + } + PJ_CATCH_ANY + { + status = PJ_ENOMEM; + } + PJ_END; + + /* Update nameserver status */ + report_nameserver_status(resolver, src_addr, dns_pkt); + + /* Handle parse error */ + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (resolver->name.ptr, status, "Error parsing DNS response from %s:%d", + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr))); + goto read_next_packet; + } + + /* Find the query based on the transaction ID */ + q = (pj_dns_async_query *)pj_hash_get(resolver->hquerybyid, &dns_pkt->hdr.id, sizeof(dns_pkt->hdr.id), NULL); + if (!q) { + PJ_LOG(5, (resolver->name.ptr, "DNS response from %s:%d id=%d discarded", + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr), + (unsigned)dns_pkt->hdr.id)); + goto read_next_packet; + } + + /* Map DNS Rcode in the response into PJLIB status name space */ + status = PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(dns_pkt->hdr.flags)); + + /* Cancel query timeout timer. */ + pj_assert(q->timer_entry.id != 0); + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + q->timer_entry.id = 0; + + /* Clear hash table entries */ + pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + + /* Workaround for deadlock problem in #1108 */ + pj_grp_lock_release(resolver->grp_lock); + + /* Notify applications first, to allow application to modify the + * record before it is saved to the hash table. + */ + if (q->cb) + (*q->cb)(q->user_data, status, dns_pkt); + + /* If query has subqueries, notify subqueries's application callback */ + if (!pj_list_empty(&q->child_head)) { + pj_dns_async_query *child_q; + + child_q = q->child_head.next; + while (child_q != (pj_dns_async_query *)&q->child_head) { + if (child_q->cb) + (*child_q->cb)(child_q->user_data, status, dns_pkt); + child_q = child_q->next; + } + } + + /* Workaround for deadlock problem in #1108 */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Truncated responses MUST NOT be saved (cached). */ + if (PJ_DNS_GET_TC(dns_pkt->hdr.flags) == 0) { + /* Save/update response cache. */ + update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt); + } + + /* Recycle query objects, starting with the child queries */ + if (!pj_list_empty(&q->child_head)) { + pj_dns_async_query *child_q; + + child_q = q->child_head.next; + while (child_q != (pj_dns_async_query *)&q->child_head) { + pj_dns_async_query *next = child_q->next; + pj_list_erase(child_q); + pj_list_push_back(&resolver->query_free_nodes, child_q); + child_q = next; + } + } + pj_list_push_back(&resolver->query_free_nodes, q); + +read_next_packet: + if (pool) { + /* needed just in case PJ_HAS_POOL_ALT_API is set */ + pj_pool_release(pool); + } + + status = pj_ioqueue_recvfrom(key, op_key, rx_pkt, &rx_pkt_size, PJ_IOQUEUE_ALWAYS_ASYNC, src_addr, src_addr_len); + + if (status != PJ_EPENDING && status != PJ_ECANCELLED) { + PJ_PERROR(4, (resolver->name.ptr, status, "DNS resolver ioqueue read error")); + + pj_assert(!"Unhandled error"); + } + + pj_grp_lock_release(resolver->grp_lock); +} + +/* + * Put the specified DNS packet into DNS cache. This function is mainly used + * for testing the resolver, however it can also be used to inject entries + * into the resolver. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_add_entry(pj_dns_resolver *resolver, const pj_dns_parsed_packet *pkt, pj_bool_t set_ttl) +{ + struct res_key key; + + /* Sanity check */ + PJ_ASSERT_RETURN(resolver && pkt, PJ_EINVAL); + + /* Packet must be a DNS response */ + PJ_ASSERT_RETURN(PJ_DNS_GET_QR(pkt->hdr.flags) & 1, PJ_EINVAL); + + /* Make sure there are answers in the packet */ + PJ_ASSERT_RETURN((pkt->hdr.anscount && pkt->ans) || (pkt->hdr.qdcount && pkt->q), PJLIB_UTIL_EDNSNOANSWERREC); + + pj_grp_lock_acquire(resolver->grp_lock); + + /* Build resource key for looking up hash tables */ + pj_bzero(&key, sizeof(struct res_key)); + if (pkt->hdr.anscount) { + /* Make sure name is not too long. */ + PJ_ASSERT_RETURN(pkt->ans[0].name.slen < PJ_MAX_HOSTNAME, PJ_ENAMETOOLONG); + + init_res_key(&key, pkt->ans[0].type, &pkt->ans[0].name); + + } else { + /* Make sure name is not too long. */ + PJ_ASSERT_RETURN(pkt->q[0].name.slen < PJ_MAX_HOSTNAME, PJ_ENAMETOOLONG); + + init_res_key(&key, pkt->q[0].type, &pkt->q[0].name); + } + + /* Insert entry. */ + update_res_cache(resolver, &key, PJ_SUCCESS, set_ttl, pkt); + + pj_grp_lock_release(resolver->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Get the total number of response in the response cache. + */ +PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) +{ + unsigned count; + + PJ_ASSERT_RETURN(resolver, 0); + + pj_grp_lock_acquire(resolver->grp_lock); + count = pj_hash_count(resolver->hrescache); + pj_grp_lock_release(resolver->grp_lock); + + return count; +} + +/* + * Dump resolver state to the log. + */ +PJ_DEF(void) pj_dns_resolver_dump(pj_dns_resolver *resolver, pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + unsigned i; + pj_time_val now; + + pj_grp_lock_acquire(resolver->grp_lock); + + pj_gettimeofday(&now); + + PJ_LOG(3, (resolver->name.ptr, " Dumping resolver state:")); + + PJ_LOG(3, (resolver->name.ptr, " Name servers:")); + for (i = 0; i < resolver->ns_count; ++i) { + char addr[PJ_INET6_ADDRSTRLEN]; + struct nameserver *ns = &resolver->ns[i]; + + PJ_LOG(3, (resolver->name.ptr, " NS %d: %s:%d (state=%s until %ds, rtt=%d ms)", i, + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), + state_names[ns->state], ns->state_expiry.sec - now.sec, PJ_TIME_VAL_MSEC(ns->rt_delay))); + } + + PJ_LOG(3, (resolver->name.ptr, " Nb. of cached responses: %u", pj_hash_count(resolver->hrescache))); + if (detail) { + pj_hash_iterator_t itbuf, *it; + it = pj_hash_first(resolver->hrescache, &itbuf); + while (it) { + struct cached_res *cache; + cache = (struct cached_res *)pj_hash_this(resolver->hrescache, it); + PJ_LOG(3, (resolver->name.ptr, " Type %s: %s", pj_dns_get_type_name(cache->key.qtype), cache->key.name)); + it = pj_hash_next(resolver->hrescache, it); + } + } + PJ_LOG(3, (resolver->name.ptr, " Nb. of pending queries: %u (%u)", pj_hash_count(resolver->hquerybyid), + pj_hash_count(resolver->hquerybyres))); + if (detail) { + pj_hash_iterator_t itbuf, *it; + it = pj_hash_first(resolver->hquerybyid, &itbuf); + while (it) { + struct pj_dns_async_query *q; + q = (pj_dns_async_query *)pj_hash_this(resolver->hquerybyid, it); + PJ_LOG(3, (resolver->name.ptr, " Type %s: %s", pj_dns_get_type_name(q->key.qtype), q->key.name)); + it = pj_hash_next(resolver->hquerybyid, it); + } + } + PJ_LOG(3, (resolver->name.ptr, " Nb. of pending query free nodes: %u", pj_list_size(&resolver->query_free_nodes))); + PJ_LOG(3, (resolver->name.ptr, " Nb. of timer entries: %u", pj_timer_heap_count(resolver->timer))); + PJ_LOG(3, (resolver->name.ptr, " Pool capacity: %d, used size: %d", pj_pool_get_capacity(resolver->pool), + pj_pool_get_used_size(resolver->pool))); + + pj_grp_lock_release(resolver->grp_lock); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c new file mode 100755 index 000000000..358a3c9a4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "scanner.c" + +#define PJ_SCAN_IS_SPACE(c) ((c) == ' ' || (c) == '\t') +#define PJ_SCAN_IS_NEWLINE(c) ((c) == '\r' || (c) == '\n') +#define PJ_SCAN_IS_PROBABLY_SPACE(c) ((c) <= 32) +#define PJ_SCAN_CHECK_EOF(s) (s != scanner->end) + +#if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0 +#include "scanner_cis_bitwise.c" +#else +#include "scanner_cis_uint.c" +#endif + +static void pj_scan_syntax_err(pj_scanner *scanner) +{ + (*scanner->callback)(scanner); +} + +PJ_DEF(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend) +{ + /* Can not set zero. This is the requirement of the parser. */ + pj_assert(cstart > 0); + + while (cstart != cend) { + PJ_CIS_SET(cis, cstart); + ++cstart; + } +} + +PJ_DEF(void) pj_cis_add_alpha(pj_cis_t *cis) +{ + pj_cis_add_range(cis, 'a', 'z' + 1); + pj_cis_add_range(cis, 'A', 'Z' + 1); +} + +PJ_DEF(void) pj_cis_add_num(pj_cis_t *cis) +{ + pj_cis_add_range(cis, '0', '9' + 1); +} + +PJ_DEF(void) pj_cis_add_str(pj_cis_t *cis, const char *str) +{ + while (*str) { + PJ_CIS_SET(cis, *str); + ++str; + } +} + +PJ_DEF(void) pj_cis_add_cis(pj_cis_t *cis, const pj_cis_t *rhs) +{ + int i; + for (i = 0; i < 256; ++i) { + if (PJ_CIS_ISSET(rhs, i)) + PJ_CIS_SET(cis, i); + } +} + +PJ_DEF(void) pj_cis_del_range(pj_cis_t *cis, int cstart, int cend) +{ + while (cstart != cend) { + PJ_CIS_CLR(cis, cstart); + cstart++; + } +} + +PJ_DEF(void) pj_cis_del_str(pj_cis_t *cis, const char *str) +{ + while (*str) { + PJ_CIS_CLR(cis, *str); + ++str; + } +} + +PJ_DEF(void) pj_cis_invert(pj_cis_t *cis) +{ + unsigned i; + /* Can not set zero. This is the requirement of the parser. */ + for (i = 1; i < 256; ++i) { + if (PJ_CIS_ISSET(cis, i)) + PJ_CIS_CLR(cis, i); + else + PJ_CIS_SET(cis, i); + } +} + +PJ_DEF(void) +pj_scan_init(pj_scanner *scanner, char *bufstart, pj_size_t buflen, unsigned options, pj_syn_err_func_ptr callback) +{ + PJ_CHECK_STACK(); + + scanner->begin = scanner->curptr = bufstart; + scanner->end = bufstart + buflen; + scanner->line = 1; + scanner->start_line = scanner->begin; + scanner->callback = callback; + scanner->skip_ws = options; + + if (scanner->skip_ws) + pj_scan_skip_whitespace(scanner); +} + +PJ_DEF(void) pj_scan_fini(pj_scanner *scanner) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(scanner); +} + +PJ_DEF(void) pj_scan_skip_whitespace(pj_scanner *scanner) +{ + register char *s = scanner->curptr; + + while (PJ_SCAN_IS_SPACE(*s)) { + ++s; + } + + if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_NEWLINE)) { + for (;;) { + if (*s == '\r') { + ++s; + if (*s == '\n') + ++s; + ++scanner->line; + scanner->curptr = scanner->start_line = s; + } else if (*s == '\n') { + ++s; + ++scanner->line; + scanner->curptr = scanner->start_line = s; + } else if (PJ_SCAN_IS_SPACE(*s)) { + do { + ++s; + } while (PJ_SCAN_IS_SPACE(*s)); + } else { + break; + } + } + } + + if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_WS_HEADER) == PJ_SCAN_AUTOSKIP_WS_HEADER) { + /* Check for header continuation. */ + scanner->curptr = s; + + if (*s == '\r') { + ++s; + } + if (*s == '\n') { + ++s; + } + scanner->start_line = s; + + if (PJ_SCAN_IS_SPACE(*s)) { + register char *t = s; + do { + ++t; + } while (PJ_SCAN_IS_SPACE(*t)); + + ++scanner->line; + scanner->curptr = t; + } + } else { + scanner->curptr = s; + } +} + +PJ_DEF(void) pj_scan_skip_line(pj_scanner *scanner) +{ + char *s; + + if (pj_scan_is_eof(scanner)) { + return; + } + + s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr); + if (!s) { + scanner->curptr = scanner->end; + } else { + scanner->curptr = scanner->start_line = s + 1; + scanner->line++; + } +} + +PJ_DEF(int) pj_scan_peek(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + + /* Don't need to check EOF with PJ_SCAN_CHECK_EOF(s) */ + while (pj_cis_match(spec, *s)) + ++s; + + pj_strset3(out, scanner->curptr, s); + return *s; +} + +PJ_DEF(int) pj_scan_peek_n(pj_scanner *scanner, pj_size_t len, pj_str_t *out) +{ + char *endpos = scanner->curptr + len; + + if (endpos > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + + pj_strset(out, scanner->curptr, len); + return *endpos; +} + +PJ_DEF(int) pj_scan_peek_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + + while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) + ++s; + + pj_strset3(out, scanner->curptr, s); + return *s; +} + +PJ_DEF(void) pj_scan_get(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + pj_assert(pj_cis_match(spec, 0) == 0); + + if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s)) { + pj_scan_syntax_err(scanner); + return; + } + + do { + ++s; + } while (pj_cis_match(spec, *s)); + /* No need to check EOF here (PJ_SCAN_CHECK_EOF(s)) because + * buffer is NULL terminated and pj_cis_match(spec,0) should be + * false. + */ + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_unescape(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + char *dst = s; + + pj_assert(pj_cis_match(spec, 0) == 0); + + /* Must not match character '%' */ + pj_assert(pj_cis_match(spec, '%') == 0); + + if (pj_scan_is_eof(scanner) || (!pj_cis_match(spec, *s) && *s != '%')) { + pj_scan_syntax_err(scanner); + return; + } + + out->ptr = s; + do { + if (*s == '%') { + if (s + 3 <= scanner->end && pj_isxdigit(*(s + 1)) && pj_isxdigit(*(s + 2))) { + *dst = (pj_uint8_t)((pj_hex_digit_to_val(*(s + 1)) << 4) + pj_hex_digit_to_val(*(s + 2))); + ++dst; + s += 3; + } else { + *dst++ = *s++; + *dst++ = *s++; + break; + } + } + + if (pj_cis_match(spec, *s)) { + char *start = s; + do { + ++s; + } while (pj_cis_match(spec, *s)); + + if (dst != start) + pj_memmove(dst, start, s - start); + dst += (s - start); + } + + } while (*s == '%'); + + scanner->curptr = s; + out->slen = (dst - out->ptr); + + if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_quote(pj_scanner *scanner, int begin_quote, int end_quote, pj_str_t *out) +{ + char beg = (char)begin_quote; + char end = (char)end_quote; + pj_scan_get_quotes(scanner, &beg, &end, 1, out); +} + +PJ_DEF(void) +pj_scan_get_quotes(pj_scanner *scanner, const char *begin_quote, const char *end_quote, int qsize, pj_str_t *out) +{ + register char *s = scanner->curptr; + int qpair = -1; + int i; + + pj_assert(qsize > 0); + + /* Check and eat the begin_quote. */ + for (i = 0; i < qsize; ++i) { + if (*s == begin_quote[i]) { + qpair = i; + break; + } + } + if (qpair == -1) { + pj_scan_syntax_err(scanner); + return; + } + ++s; + + /* Loop until end_quote is found. + */ + do { + /* loop until end_quote is found. */ + while (PJ_SCAN_CHECK_EOF(s) && *s != '\n' && *s != end_quote[qpair]) { + ++s; + } + + /* check that no backslash character precedes the end_quote. */ + if (*s == end_quote[qpair]) { + if (*(s - 1) == '\\') { + char *q = s - 2; + char *r = s - 2; + + while (r != scanner->begin && *r == '\\') { + --r; + } + /* break from main loop if we have odd number of backslashes */ + if (((unsigned)(q - r) & 0x01) == 1) { + break; + } + ++s; + } else { + /* end_quote is not preceeded by backslash. break now. */ + break; + } + } else { + /* loop ended by non-end_quote character. break now. */ + break; + } + } while (1); + + /* Check and eat the end quote. */ + if (*s != end_quote[qpair]) { + pj_scan_syntax_err(scanner); + return; + } + ++s; + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_n(pj_scanner *scanner, unsigned N, pj_str_t *out) +{ + if (scanner->curptr + N > scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + pj_strset(out, scanner->curptr, N); + + scanner->curptr += N; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(int) pj_scan_get_char(pj_scanner *scanner) +{ + register char *s = scanner->curptr; + int chr; + + if (s >= scanner->end || !*s) { + pj_scan_syntax_err(scanner); + return 0; + } + + chr = *s; + + ++s; + scanner->curptr = s; + if (PJ_SCAN_CHECK_EOF(s) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } + return chr; +} + +PJ_DEF(void) pj_scan_get_newline(pj_scanner *scanner) +{ + if (pj_scan_is_eof(scanner) || !PJ_SCAN_IS_NEWLINE(*scanner->curptr)) { + pj_scan_syntax_err(scanner); + return; + } + + /* We have checked scanner->curptr validity above */ + if (*scanner->curptr == '\r') { + ++scanner->curptr; + } + if (!pj_scan_is_eof(scanner) && *scanner->curptr == '\n') { + ++scanner->curptr; + } + + ++scanner->line; + scanner->start_line = scanner->curptr; + + /** + * This probably is a bug, see PROTOS test #2480. + * This would cause scanner to incorrectly eat two new lines, e.g. + * when parsing: + * + * Content-Length: 120\r\n + * \r\n + * ... + * + * When pj_scan_get_newline() is called to parse the first newline + * in the Content-Length header, it will eat the second newline + * too because it thinks that it's a header continuation. + * + * if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) { + * pj_scan_skip_whitespace(scanner); + * } + */ +} + +PJ_DEF(void) pj_scan_get_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) { + ++s; + } + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_until_ch(pj_scanner *scanner, int until_char, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + while (PJ_SCAN_CHECK_EOF(s) && *s != until_char) { + ++s; + } + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_until_chr(pj_scanner *scanner, const char *until_spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + pj_size_t speclen; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + speclen = strlen(until_spec); + while (PJ_SCAN_CHECK_EOF(s) && !memchr(until_spec, *s, speclen)) { + ++s; + } + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_advance_n(pj_scanner *scanner, unsigned N, pj_bool_t skip_ws) +{ + if (scanner->curptr + N > scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + scanner->curptr += N; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(int) pj_scan_strcmp(pj_scanner *scanner, const char *s, int len) +{ + if (scanner->curptr + len > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + return strncmp(scanner->curptr, s, len); +} + +PJ_DEF(int) pj_scan_stricmp(pj_scanner *scanner, const char *s, int len) +{ + if (scanner->curptr + len > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + return pj_ansi_strnicmp(scanner->curptr, s, len); +} + +PJ_DEF(int) pj_scan_stricmp_alnum(pj_scanner *scanner, const char *s, int len) +{ + if (scanner->curptr + len > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + return strnicmp_alnum(scanner->curptr, s, len); +} + +PJ_DEF(void) pj_scan_save_state(const pj_scanner *scanner, pj_scan_state *state) +{ + state->curptr = scanner->curptr; + state->line = scanner->line; + state->start_line = scanner->start_line; +} + +PJ_DEF(void) pj_scan_restore_state(pj_scanner *scanner, pj_scan_state *state) +{ + scanner->curptr = state->curptr; + scanner->line = state->line; + scanner->start_line = state->start_line; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c new file mode 100755 index 000000000..be598f781 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * THIS FILE IS INCLUDED BY scanner.c. + * DO NOT COMPILE THIS FILE ALONE! + */ + +#include + +PJ_DEF(void) pj_cis_buf_init(pj_cis_buf_t *cis_buf) +{ + pj_bzero(cis_buf->cis_buf, sizeof(cis_buf->cis_buf)); + cis_buf->use_mask = 0; +} + +PJ_DEF(pj_status_t) pj_cis_init(pj_cis_buf_t *cis_buf, pj_cis_t *cis) +{ + unsigned i; + + cis->cis_buf = cis_buf->cis_buf; + + for (i = 0; i < PJ_CIS_MAX_INDEX; ++i) { + if ((cis_buf->use_mask & (1 << i)) == 0) { + cis->cis_id = i; + cis_buf->use_mask |= (1 << i); + return PJ_SUCCESS; + } + } + + cis->cis_id = PJ_CIS_MAX_INDEX; + return PJ_ETOOMANY; +} + +PJ_DEF(pj_status_t) pj_cis_dup(pj_cis_t *new_cis, pj_cis_t *existing) +{ + pj_status_t status; + unsigned i; + + /* Warning: typecasting here! */ + status = pj_cis_init((pj_cis_buf_t *)existing->cis_buf, new_cis); + if (status != PJ_SUCCESS) + return status; + + for (i = 0; i < 256; ++i) { + if (PJ_CIS_ISSET(existing, i)) + PJ_CIS_SET(new_cis, i); + else + PJ_CIS_CLR(new_cis, i); + } + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c new file mode 100755 index 000000000..38630f6d9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * THIS FILE IS INCLUDED BY scanner.c. + * DO NOT COMPILE THIS FILE ALONE! + */ + +PJ_DEF(void) pj_cis_buf_init(pj_cis_buf_t *cis_buf) +{ + /* Do nothing. */ + PJ_UNUSED_ARG(cis_buf); +} + +PJ_DEF(pj_status_t) pj_cis_init(pj_cis_buf_t *cis_buf, pj_cis_t *cis) +{ + PJ_UNUSED_ARG(cis_buf); + pj_bzero(cis->cis_buf, sizeof(cis->cis_buf)); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_cis_dup(pj_cis_t *new_cis, pj_cis_t *existing) +{ + pj_memcpy(new_cis, existing, sizeof(pj_cis_t)); + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c new file mode 100755 index 000000000..e33ad04b2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c @@ -0,0 +1,322 @@ +/* + * Modified 2/07 + * By Benny Prijono + * Still 100% Public Domain + * + * This is the implementation of SHA-1 encryption algorithm based on + * Steve Reid work. Modified to work with PJLIB. + */ + +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +----------------- +Modified 7/98 +By James H. Brown +Still 100% Public Domain + +Corrected a problem which generated improper hash values on 16 bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int +len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned +long len) + +The 'len' parameter was declared an int which works fine on 32 bit machines. +However, on 16 bit machines an int is too small for the shifts being done +against +it. This caused the hash function to generate incorrect values if len was +greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or larger would +be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million +"a"s). + +I also changed the declaration of variables i & j in SHA1Update to +unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit implementations since +an +int and a long are the same size in those environments. + +-- +I also corrected a few compiler warnings generated by Borland C. +1. Added #include for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments containing 'JHB' +----------------- +Modified 8/98 +By Steve Reid +Still 100% public domain + +1- Removed #include and used return() instead of exit() +2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) +3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + +----------------- +Modified 4/01 +By Saul Kravitz +Still 100% PD +Modified to run on Compaq Alpha hardware. + +----------------- +Modified 07/2002 +By Ralph Giles +Still 100% public domain +modified for use with stdint types, autoconf +code cleanup, removed attribution comments +switched SHA1Final() argument order for consistency +use SHA1_ prefix for public api +move public api to sha1.h +*/ + +/* +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define SHA1HANDSOFF */ +/* blp: +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "os_types.h" + +#include "sha1.h" +*/ +#include +#include + +#undef SHA1HANDSOFF + +static void SHA1_Transform(pj_uint32_t state[5], pj_uint8_t buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +/* FIXME: can we do this in an endian-proof way? */ +/* #ifdef WORDS_BIGENDIAN */ +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0 +#define blk0(i) block->l[i] +#else +#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF)) +#endif +#define blk(i) \ + (block->l[i & 15] = \ + rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +static void SHA1_Transform(pj_uint32_t state[5], pj_uint8_t buffer[64]) +{ + pj_uint32_t a, b, c, d, e; + typedef union { + pj_uint8_t c[64]; + pj_uint32_t l[16]; + } CHAR64LONG16; + CHAR64LONG16 *block; + +#ifdef SHA1HANDSOFF + static pj_uint8_t workspace[64]; + block = (CHAR64LONG16 *)workspace; + pj_memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16 *)buffer; +#endif + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + +/* SHA1Init - Initialize new context */ +PJ_DEF(void) pj_sha1_init(pj_sha1_context *context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ +PJ_DEF(void) pj_sha1_update(pj_sha1_context *context, const pj_uint8_t *data, const pj_size_t len) +{ + pj_size_t i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += (pj_uint32_t)len << 3) < (len << 3)) + context->count[1]++; + context->count[1] += ((pj_uint32_t)len >> 29); + if ((j + len) > 63) { + pj_memcpy(&context->buffer[j], data, (i = 64 - j)); + SHA1_Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + pj_uint8_t tmp[64]; + pj_memcpy(tmp, data + i, 64); + SHA1_Transform(context->state, tmp); + } + j = 0; + } else + i = 0; + pj_memcpy(&context->buffer[j], &data[i], len - i); +} + +/* Add padding and return the message digest. */ +PJ_DEF(void) pj_sha1_final(pj_sha1_context *context, pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]) +{ + pj_uint32_t i; + pj_uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = + (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } + pj_sha1_update(context, (pj_uint8_t *)"\200", 1); + while ((context->count[0] & 504) != 448) { + pj_sha1_update(context, (pj_uint8_t *)"\0", 1); + } + pj_sha1_update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ + for (i = 0; i < PJ_SHA1_DIGEST_SIZE; i++) { + digest[i] = (pj_uint8_t)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + + /* Wipe variables */ + i = 0; + pj_memset(context->buffer, 0, 64); + pj_memset(context->state, 0, 20); + pj_memset(context->count, 0, 8); + pj_memset(finalcount, 0, 8); /* SWR */ + +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */ + SHA1_Transform(context->state, context->buffer); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c new file mode 100755 index 000000000..4e0d7fce8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "srv_resolver.c" + +#define ADDR_MAX_COUNT PJ_DNS_MAX_IP_IN_A_REC + +struct common { + pj_dns_type type; /**< Type of this structure.*/ +}; + +#pragma pack(1) +struct srv_target { + struct common common; + struct common common_aaaa; + pj_dns_srv_async_query *parent; + pj_str_t target_name; + pj_dns_async_query *q_a; + pj_dns_async_query *q_aaaa; + char target_buf[PJ_MAX_HOSTNAME]; + pj_str_t cname; + char cname_buf[PJ_MAX_HOSTNAME]; + pj_uint16_t port; + unsigned priority; + unsigned weight; + unsigned sum; + unsigned addr_cnt; + pj_sockaddr addr[ADDR_MAX_COUNT]; /**< Address family and IP.*/ +}; +#pragma pack() + +struct pj_dns_srv_async_query { + struct common common; + char *objname; + + pj_dns_type dns_state; /**< DNS type being resolved. */ + pj_dns_resolver *resolver; /**< Resolver SIP instance. */ + void *token; + pj_dns_async_query *q_srv; + pj_dns_srv_resolver_cb *cb; + pj_status_t last_error; + + /* Original request: */ + unsigned option; + pj_str_t full_name; + pj_str_t domain_part; + pj_uint16_t def_port; + + /* SRV records and their resolved IP addresses: */ + unsigned srv_cnt; + struct srv_target srv[PJ_DNS_SRV_MAX_ADDR]; + + /* Number of hosts in SRV records that the IP address has been resolved */ + unsigned host_resolved; +}; + +/* Async resolver callback, forward decl. */ +static void dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *pkt); + +/* + * The public API to invoke DNS SRV resolution. + */ +PJ_DEF(pj_status_t) +pj_dns_srv_resolve(const pj_str_t *domain_name, const pj_str_t *res_name, unsigned def_port, pj_pool_t *pool, + pj_dns_resolver *resolver, unsigned option, void *token, pj_dns_srv_resolver_cb *cb, + pj_dns_srv_async_query **p_query) +{ + pj_size_t len; + pj_str_t target_name; + pj_dns_srv_async_query *query_job; + pj_status_t status; + + PJ_ASSERT_RETURN(domain_name && domain_name->slen && res_name && res_name->slen && pool && resolver && cb, + PJ_EINVAL); + + /* Build full name */ + len = domain_name->slen + res_name->slen + 2; + target_name.ptr = (char *)pj_pool_alloc(pool, len); + pj_strcpy(&target_name, res_name); + if (res_name->ptr[res_name->slen - 1] != '.') + pj_strcat2(&target_name, "."); + len = target_name.slen; + pj_strcat(&target_name, domain_name); + target_name.ptr[target_name.slen] = '\0'; + + /* Build the query_job state */ + query_job = PJ_POOL_ZALLOC_T(pool, pj_dns_srv_async_query); + query_job->common.type = PJ_DNS_TYPE_SRV; + query_job->objname = target_name.ptr; + query_job->resolver = resolver; + query_job->token = token; + query_job->cb = cb; + query_job->option = option; + query_job->full_name = target_name; + query_job->domain_part.ptr = target_name.ptr + len; + query_job->domain_part.slen = target_name.slen - len; + query_job->def_port = (pj_uint16_t)def_port; + + /* Normalize query job option PJ_DNS_SRV_RESOLVE_AAAA_ONLY */ + if (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) + query_job->option |= PJ_DNS_SRV_RESOLVE_AAAA; + + /* Start the asynchronous query_job */ + + query_job->dns_state = PJ_DNS_TYPE_SRV; + + PJ_LOG(5, (query_job->objname, "Starting async DNS %s query_job: target=%.*s:%d", + pj_dns_get_type_name(query_job->dns_state), (int)target_name.slen, target_name.ptr, def_port)); + + status = pj_dns_resolver_start_query(resolver, &target_name, query_job->dns_state, 0, &dns_callback, query_job, + &query_job->q_srv); + if (status == PJ_SUCCESS && p_query) + *p_query = query_job; + + return status; +} + +/* + * Cancel pending query. + */ +PJ_DEF(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query, pj_bool_t notify) +{ + pj_bool_t has_pending = PJ_FALSE; + unsigned i; + + if (query->q_srv) { + pj_dns_resolver_cancel_query(query->q_srv, PJ_FALSE); + query->q_srv = NULL; + has_pending = PJ_TRUE; + } + + for (i = 0; i < query->srv_cnt; ++i) { + struct srv_target *srv = &query->srv[i]; + if (srv->q_a) { + pj_dns_resolver_cancel_query(srv->q_a, PJ_FALSE); + srv->q_a = NULL; + has_pending = PJ_TRUE; + } + if (srv->q_aaaa) { + /* Check if it is a dummy query. */ + if (srv->q_aaaa != (pj_dns_async_query *)0x1) { + pj_dns_resolver_cancel_query(srv->q_aaaa, PJ_FALSE); + has_pending = PJ_TRUE; + } + srv->q_aaaa = NULL; + } + } + + if (has_pending && notify && query->cb) { + (*query->cb)(query->token, PJ_ECANCELLED, NULL); + } + + return has_pending ? PJ_SUCCESS : PJ_EINVALIDOP; +} + +#define SWAP(type, ptr1, ptr2) \ + if (ptr1 != ptr2) { \ + type tmp; \ + pj_memcpy(&tmp, ptr1, sizeof(type)); \ + pj_memcpy(ptr1, ptr2, sizeof(type)); \ + (ptr1)->target_name.ptr = (ptr1)->target_buf; \ + pj_memcpy(ptr2, &tmp, sizeof(type)); \ + (ptr2)->target_name.ptr = (ptr2)->target_buf; \ + } else { \ + } + +/* Build server entries in the query_job based on received SRV response */ +static void build_server_entries(pj_dns_srv_async_query *query_job, pj_dns_parsed_packet *response) +{ + unsigned i; + + /* Save the Resource Records in DNS answer into SRV targets. */ + query_job->srv_cnt = 0; + for (i = 0; i < response->hdr.anscount && query_job->srv_cnt < PJ_DNS_SRV_MAX_ADDR; ++i) { + pj_dns_parsed_rr *rr = &response->ans[i]; + struct srv_target *srv = &query_job->srv[query_job->srv_cnt]; + + if (rr->type != PJ_DNS_TYPE_SRV) { + PJ_LOG(4, (query_job->objname, "Received non SRV answer for SRV query_job!")); + continue; + } + + if (rr->rdata.srv.target.slen > PJ_MAX_HOSTNAME) { + PJ_LOG(4, (query_job->objname, "Hostname is too long!")); + continue; + } + + if (rr->rdata.srv.target.slen == 0) { + PJ_LOG(4, (query_job->objname, "Hostname is empty!")); + continue; + } + + /* Build the SRV entry for RR */ + pj_bzero(srv, sizeof(*srv)); + srv->target_name.ptr = srv->target_buf; + pj_strncpy(&srv->target_name, &rr->rdata.srv.target, sizeof(srv->target_buf)); + srv->port = rr->rdata.srv.port; + srv->priority = rr->rdata.srv.prio; + srv->weight = rr->rdata.srv.weight; + + ++query_job->srv_cnt; + } + + if (query_job->srv_cnt == 0) { + PJ_LOG(4, (query_job->objname, "Could not find SRV record in DNS answer!")); + return; + } + + /* First pass: + * order the entries based on priority. + */ + for (i = 0; i < query_job->srv_cnt - 1; ++i) { + unsigned min = i, j; + for (j = i + 1; j < query_job->srv_cnt; ++j) { + if (query_job->srv[j].priority < query_job->srv[min].priority) + min = j; + } + SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[min]); + } + + /* Second pass: + * Order the entry in a list. + * + * The algorithm for selecting server among servers with the same + * priority is described in RFC 2782. + */ + for (i = 0; i < query_job->srv_cnt; ++i) { + unsigned j, count = 1, sum; + + /* Calculate running sum for servers with the same priority */ + sum = query_job->srv[i].sum = query_job->srv[i].weight; + for (j = i + 1; j < query_job->srv_cnt && query_job->srv[j].priority == query_job->srv[i].priority; ++j) { + sum += query_job->srv[j].weight; + query_job->srv[j].sum = sum; + ++count; + } + + if (count > 1) { + unsigned r; + + /* Elect one random number between zero and the total sum of + * weight (inclusive). + */ + r = pj_rand() % (sum + 1); + + /* Select the first server which running sum is greater than or + * equal to the random number. + */ + for (j = i; j < i + count; ++j) { + if (query_job->srv[j].sum >= r) + break; + } + + /* Must have selected one! */ + pj_assert(j != i + count); + + /* Put this entry in front (of entries with same priority) */ + SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[j]); + + /* Remove all other entries (of the same priority) */ + /* Don't need to do this. + * See https://github.com/pjsip/pjproject/issues/1719 + while (count > 1) { + pj_array_erase(query_job->srv, sizeof(struct srv_target), + query_job->srv_cnt, i+1); + --count; + --query_job->srv_cnt; + } + */ + } + } + + /* Since we've been moving around SRV entries, update the pointers + * in target_name. + */ + for (i = 0; i < query_job->srv_cnt; ++i) { + query_job->srv[i].target_name.ptr = query_job->srv[i].target_buf; + } + + /* Check for Additional Info section if A/AAAA records are available, and + * fill in the IP address (so that we won't need to resolve the A/AAAA + * record with another DNS query_job). + */ + for (i = 0; i < response->hdr.arcount; ++i) { + pj_dns_parsed_rr *rr = &response->arr[i]; + unsigned j; + + /* Skip non-A/AAAA record */ + if (rr->type != PJ_DNS_TYPE_A && rr->type != PJ_DNS_TYPE_AAAA) + continue; + + /* Also skip if: + * - it is A record and app only want AAAA record, or + * - it is AAAA record and app does not want AAAA record + */ + if ((rr->type == PJ_DNS_TYPE_A && (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) != 0) || + (rr->type == PJ_DNS_TYPE_AAAA && (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) == 0)) { + continue; + } + + /* Yippeaiyee!! There is an "A/AAAA" record! + * Update the IP address of the corresponding SRV record. + */ + for (j = 0; j < query_job->srv_cnt; ++j) { + if (pj_stricmp(&rr->name, &query_job->srv[j].target_name) == 0 && + query_job->srv[j].addr_cnt < ADDR_MAX_COUNT) { + unsigned cnt = query_job->srv[j].addr_cnt; + if (rr->type == PJ_DNS_TYPE_A) { + pj_sockaddr_init(pj_AF_INET(), &query_job->srv[j].addr[cnt], NULL, query_job->srv[j].port); + query_job->srv[j].addr[cnt].ipv4.sin_addr = rr->rdata.a.ip_addr; + } else { + pj_sockaddr_init(pj_AF_INET6(), &query_job->srv[j].addr[cnt], NULL, query_job->srv[j].port); + query_job->srv[j].addr[cnt].ipv6.sin6_addr = rr->rdata.aaaa.ip_addr; + } + + /* Only increment host_resolved once per SRV record */ + if (query_job->srv[j].addr_cnt == 0) + ++query_job->host_resolved; + + ++query_job->srv[j].addr_cnt; + break; + } + } + + /* Not valid message; SRV entry might have been deleted in + * server selection process. + */ + /* + if (j == query_job->srv_cnt) { + PJ_LOG(4,(query_job->objname, + "Received DNS SRV answer with A record, but " + "couldn't find matching name (name=%.*s)", + (int)rr->name.slen, + rr->name.ptr)); + } + */ + } + + /* Rescan again the name specified in the SRV record to see if IP + * address is specified as the target name (unlikely, but well, who + * knows..). + */ + for (i = 0; i < query_job->srv_cnt; ++i) { + pj_in_addr addr; + pj_in6_addr addr6; + unsigned cnt = query_job->srv[i].addr_cnt; + + if (cnt != 0) { + /* IP address already resolved */ + continue; + } + + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) == 0 && + pj_inet_pton(pj_AF_INET(), &query_job->srv[i].target_name, &addr) == PJ_SUCCESS) { + pj_sockaddr_init(pj_AF_INET(), &query_job->srv[i].addr[cnt], NULL, query_job->srv[i].port); + query_job->srv[i].addr[cnt].ipv4.sin_addr = addr; + ++query_job->srv[i].addr_cnt; + ++query_job->host_resolved; + } else if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0 && + pj_inet_pton(pj_AF_INET6(), &query_job->srv[i].target_name, &addr6) == PJ_SUCCESS) { + pj_sockaddr_init(pj_AF_INET6(), &query_job->srv[i].addr[cnt], NULL, query_job->srv[i].port); + query_job->srv[i].addr[cnt].ipv6.sin6_addr = addr6; + ++query_job->srv[i].addr_cnt; + ++query_job->host_resolved; + } + } + + /* Print resolved entries to the log */ + PJ_LOG(5, (query_job->objname, + "SRV query_job for %.*s completed, " + "%d of %d total entries selected%c", + (int)query_job->full_name.slen, query_job->full_name.ptr, query_job->srv_cnt, response->hdr.anscount, + (query_job->srv_cnt ? ':' : ' '))); + + for (i = 0; i < query_job->srv_cnt; ++i) { + char addr[PJ_INET6_ADDRSTRLEN]; + + if (query_job->srv[i].addr_cnt != 0) { + pj_sockaddr_print(&query_job->srv[i].addr[0], addr, sizeof(addr), 2); + } else + pj_ansi_strcpy(addr, "-"); + + PJ_LOG(5, (query_job->objname, " %d: SRV %d %d %d %.*s (%s)", i, query_job->srv[i].priority, + query_job->srv[i].weight, query_job->srv[i].port, (int)query_job->srv[i].target_name.slen, + query_job->srv[i].target_name.ptr, addr)); + } +} + +/* Start DNS A and/or AAAA record queries for all SRV records in + * the query_job structure. + */ +static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job) +{ + unsigned i, err_cnt = 0; + pj_status_t err = PJ_SUCCESS, status; + + query_job->dns_state = PJ_DNS_TYPE_A; + + for (i = 0; i < query_job->srv_cnt; ++i) { + struct srv_target *srv = &query_job->srv[i]; + + if (srv->addr_cnt != 0) { + /* + * This query is already counted as resolved because of the + * additional records in the SRV response or the target name + * is an IP address exception in build_server_entries(). + */ + continue; + } + + PJ_LOG(5, (query_job->objname, "Starting async DNS A query_job for %.*s", (int)srv->target_name.slen, + srv->target_name.ptr)); + + srv->common.type = PJ_DNS_TYPE_A; + srv->common_aaaa.type = PJ_DNS_TYPE_AAAA; + srv->parent = query_job; + srv->q_a = NULL; + srv->q_aaaa = NULL; + + status = PJ_SUCCESS; + + /* Start DNS A record query */ + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) == 0) { + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0) { + /* If there will be DNS AAAA query too, let's setup + * a dummy one here, otherwise app callback may be called + * immediately (before DNS AAAA query is sent) when + * DNS A record is available in the cache. + */ + srv->q_aaaa = (pj_dns_async_query *)0x1; + } + status = pj_dns_resolver_start_query(query_job->resolver, &srv->target_name, PJ_DNS_TYPE_A, 0, + &dns_callback, &srv->common, &srv->q_a); + } + + /* Start DNS AAAA record query */ + if (status == PJ_SUCCESS && (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0) { + status = pj_dns_resolver_start_query(query_job->resolver, &srv->target_name, PJ_DNS_TYPE_AAAA, 0, + &dns_callback, &srv->common_aaaa, &srv->q_aaaa); + } + + /* See also #1809: dns_callback() will be invoked synchronously when response + * is available in the cache, and var 'query_job->host_resolved' will get + * incremented within the dns_callback(), which will cause this function + * returning false error, so don't use that variable for counting errors. + */ + if (status != PJ_SUCCESS) { + query_job->host_resolved++; + err_cnt++; + err = status; + } + } + + return (err_cnt == query_job->srv_cnt) ? err : PJ_SUCCESS; +} + +/* + * This callback is called by PJLIB-UTIL DNS resolver when asynchronous + * query_job has completed (successfully or with error). + */ +static void dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *pkt) +{ + struct common *common = (struct common *)user_data; + pj_dns_srv_async_query *query_job; + struct srv_target *srv = NULL; + unsigned i; + + if (common->type == PJ_DNS_TYPE_SRV) { + query_job = (pj_dns_srv_async_query *)common; + srv = NULL; + } else if (common->type == PJ_DNS_TYPE_A) { + srv = (struct srv_target *)common; + query_job = srv->parent; + } else if (common->type == PJ_DNS_TYPE_AAAA) { + srv = (struct srv_target *)((pj_int8_t *)common - sizeof(struct common)); + query_job = srv->parent; + } else { + pj_assert(!"Unexpected user data!"); + return; + } + + /* Proceed to next stage */ + if (query_job->dns_state == PJ_DNS_TYPE_SRV) { + + /* We are getting SRV response */ + + /* Clear the outstanding job */ + query_job->q_srv = NULL; + + if (status == PJ_SUCCESS) { + if (PJ_DNS_GET_TC(pkt->hdr.flags)) { + /* Got truncated answer, the standard recommends to follow up + * the query using TCP. Since we currently don't support it, + * just return error. + */ + PJ_LOG(4, (query_job->objname, "Discard truncated DNS SRV response for %.*s", + (int)query_job->full_name.slen, query_job->full_name.ptr)); + + status = PJ_EIGNORED; + query_job->last_error = status; + goto on_error; + } else if (pkt->hdr.anscount != 0) { + /* Got SRV response, build server entry. If A records are + * available in additional records section of the DNS response, + * save them too. + */ + build_server_entries(query_job, pkt); + } + + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Update query_job last error */ + query_job->last_error = status; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4, (query_job->objname, "DNS SRV resolution failed for %.*s: %s", (int)query_job->full_name.slen, + query_job->full_name.ptr, errmsg)); + + /* Trigger error when fallback is disabled */ + if ((query_job->option & (PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA)) == 0) { + goto on_error; + } + } + + /* If we can't build SRV record, assume the original target is + * an A record and resolve with DNS A resolution. + */ + if (query_job->srv_cnt == 0 && query_job->domain_part.slen > 0) { + unsigned new_option = 0; + + /* Looks like we aren't getting any SRV responses. + * Resolve the original target as A record by creating a + * single "dummy" srv record and start the hostname resolution. + */ + PJ_LOG(4, (query_job->objname, + "DNS SRV resolution failed for %.*s, trying " + "resolving A/AAAA record for %.*s", + (int)query_job->full_name.slen, query_job->full_name.ptr, (int)query_job->domain_part.slen, + query_job->domain_part.ptr)); + + /* Create a "dummy" srv record using the original target */ + i = query_job->srv_cnt++; + pj_bzero(&query_job->srv[i], sizeof(query_job->srv[i])); + query_job->srv[i].target_name = query_job->domain_part; + query_job->srv[i].priority = 0; + query_job->srv[i].weight = 0; + query_job->srv[i].port = query_job->def_port; + + /* Update query_job resolution option based on fallback option */ + if (query_job->option & PJ_DNS_SRV_FALLBACK_AAAA) + new_option |= (PJ_DNS_SRV_RESOLVE_AAAA | PJ_DNS_SRV_RESOLVE_AAAA_ONLY); + if (query_job->option & PJ_DNS_SRV_FALLBACK_A) + new_option &= (~PJ_DNS_SRV_RESOLVE_AAAA_ONLY); + + query_job->option = new_option; + } + + /* Resolve server hostnames (DNS A/AAAA record) for hosts which + * don't have A/AAAA record yet. + */ + if (query_job->host_resolved != query_job->srv_cnt) { + status = resolve_hostnames(query_job); + if (status != PJ_SUCCESS) + goto on_error; + + /* Must return now. Callback may have been called and query_job + * may have been destroyed. + */ + return; + } + + } else if (query_job->dns_state == PJ_DNS_TYPE_A) { + pj_bool_t is_type_a, srv_completed; + pj_dns_addr_record rec; + + /* Avoid warning: potentially uninitialized local variable 'rec' */ + rec.alias.slen = 0; + rec.addr_count = 0; + + /* Clear outstanding job */ + if (common->type == PJ_DNS_TYPE_A) { + srv_completed = (srv->q_aaaa == NULL); + srv->q_a = NULL; + } else if (common->type == PJ_DNS_TYPE_AAAA) { + srv_completed = (srv->q_a == NULL); + srv->q_aaaa = NULL; + } else { + pj_assert(!"Unexpected job type"); + query_job->last_error = status = PJ_EINVALIDOP; + goto on_error; + } + + is_type_a = (common->type == PJ_DNS_TYPE_A); + + /* Parse response */ + if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) { + status = pj_dns_parse_addr_response(pkt, &rec); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, + (query_job->objname, status, "DNS %s record parse error for '%.*s'.", + (is_type_a ? "A" : "AAAA"), (int)query_job->domain_part.slen, query_job->domain_part.ptr)); + } + } + + /* Check that we really have answer */ + if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) { + char addr[PJ_INET6_ADDRSTRLEN]; + + pj_assert(rec.addr_count != 0); + + /* Update CNAME alias, if present. */ + if (srv->cname.slen == 0 && rec.alias.slen) { + pj_assert(rec.alias.slen <= (int)sizeof(srv->cname_buf)); + srv->cname.ptr = srv->cname_buf; + pj_strcpy(&srv->cname, &rec.alias); + //} else { + // srv->cname.slen = 0; + } + + /* Update IP address of the corresponding hostname or CNAME */ + for (i = 0; i < rec.addr_count && srv->addr_cnt < ADDR_MAX_COUNT; ++i) { + pj_bool_t added = PJ_FALSE; + + if (is_type_a && rec.addr[i].af == pj_AF_INET()) { + pj_sockaddr_init(pj_AF_INET(), &srv->addr[srv->addr_cnt], NULL, srv->port); + srv->addr[srv->addr_cnt].ipv4.sin_addr = rec.addr[i].ip.v4; + added = PJ_TRUE; + } else if (!is_type_a && rec.addr[i].af == pj_AF_INET6()) { + pj_sockaddr_init(pj_AF_INET6(), &srv->addr[srv->addr_cnt], NULL, srv->port); + srv->addr[srv->addr_cnt].ipv6.sin6_addr = rec.addr[i].ip.v6; + added = PJ_TRUE; + } else { + /* Mismatched address family, e.g: getting IPv6 address in + * DNS A query resolution. + */ + PJ_LOG(4, (query_job->objname, "Bad address family in DNS %s query for %.*s", + (is_type_a ? "A" : "AAAA"), (int)srv->target_name.slen, srv->target_name.ptr)); + } + + if (added) { + PJ_LOG(5, (query_job->objname, "DNS %s for %.*s: %s", (is_type_a ? "A" : "AAAA"), + (int)srv->target_name.slen, srv->target_name.ptr, + pj_sockaddr_print(&srv->addr[srv->addr_cnt], addr, sizeof(addr), 2))); + + ++srv->addr_cnt; + } + } + + } else if (status != PJ_SUCCESS) { + /* Update last error */ + query_job->last_error = status; + + /* Log error */ + PJ_PERROR(4, (query_job->objname, status, "DNS %s record resolution failed", (is_type_a ? "A" : "AAAA"))); + } + + /* Increment host resolved count when both DNS A and AAAA record + * queries for this server are completed. + */ + if (srv_completed) + ++query_job->host_resolved; + + } else { + pj_assert(!"Unexpected state!"); + query_job->last_error = status = PJ_EINVALIDOP; + goto on_error; + } + + /* Check if all hosts have been resolved */ + if (query_job->host_resolved == query_job->srv_cnt) { + /* Got all answers, build server addresses */ + pj_dns_srv_record srv_rec; + + srv_rec.count = 0; + for (i = 0; i < query_job->srv_cnt; ++i) { + unsigned j; + struct srv_target *srv2 = &query_job->srv[i]; + pj_dns_addr_record *s = &srv_rec.entry[srv_rec.count].server; + + srv_rec.entry[srv_rec.count].priority = srv2->priority; + srv_rec.entry[srv_rec.count].weight = srv2->weight; + srv_rec.entry[srv_rec.count].port = (pj_uint16_t)srv2->port; + + srv_rec.entry[srv_rec.count].server.name = srv2->target_name; + srv_rec.entry[srv_rec.count].server.alias = srv2->cname; + srv_rec.entry[srv_rec.count].server.addr_count = 0; + + pj_assert(srv2->addr_cnt <= PJ_DNS_MAX_IP_IN_A_REC); + + for (j = 0; j < srv2->addr_cnt; ++j) { + s->addr[j].af = srv2->addr[j].addr.sa_family; + if (s->addr[j].af == pj_AF_INET()) + s->addr[j].ip.v4 = srv2->addr[j].ipv4.sin_addr; + else + s->addr[j].ip.v6 = srv2->addr[j].ipv6.sin6_addr; + ++s->addr_count; + } + + if (srv2->addr_cnt > 0) { + ++srv_rec.count; + if (srv_rec.count == PJ_DNS_SRV_MAX_ADDR) + break; + } + } + + PJ_LOG(5, (query_job->objname, "Server resolution complete, %d server entry(s) found", srv_rec.count)); + + if (srv_rec.count > 0) + status = PJ_SUCCESS; + else { + status = query_job->last_error; + if (status == PJ_SUCCESS) + status = PJLIB_UTIL_EDNSNOANSWERREC; + } + + /* Call the callback */ + (*query_job->cb)(query_job->token, status, &srv_rec); + } + + return; + +on_error: + /* Check for failure */ + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (query_job->objname, status, "DNS %s record resolution error for '%.*s'.", + pj_dns_get_type_name(query_job->dns_state), (int)query_job->domain_part.slen, + query_job->domain_part.ptr)); + + /* Cancel any pending query */ + pj_dns_srv_cancel_query(query_job, PJ_FALSE); + + (*query_job->cb)(query_job->token, status, NULL); + return; + } +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c new file mode 100755 index 000000000..c3d159b0a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +PJ_DEF(pj_str_t) pj_str_unescape(pj_pool_t *pool, const pj_str_t *src_str) +{ + char *src = src_str->ptr; + char *end = src + src_str->slen; + pj_str_t dst_str; + char *dst; + + if (pj_strchr(src_str, '%') == NULL) + return *src_str; + + dst = dst_str.ptr = (char *)pj_pool_alloc(pool, src_str->slen); + + while (src != end) { + if (*src == '%' && src < end - 2 && pj_isxdigit(*(src + 1)) && pj_isxdigit(*(src + 2))) { + *dst = (pj_uint8_t)((pj_hex_digit_to_val(*(src + 1)) << 4) + pj_hex_digit_to_val(*(src + 2))); + ++dst; + src += 3; + } else { + *dst++ = *src++; + } + } + dst_str.slen = dst - dst_str.ptr; + return dst_str; +} + +PJ_DEF(pj_str_t *) pj_strcpy_unescape(pj_str_t *dst_str, const pj_str_t *src_str) +{ + const char *src = src_str->ptr; + const char *end = src + src_str->slen; + char *dst = dst_str->ptr; + + while (src != end) { + if (*src == '%' && src < end - 2) { + *dst = (pj_uint8_t)((pj_hex_digit_to_val(*(src + 1)) << 4) + pj_hex_digit_to_val(*(src + 2))); + ++dst; + src += 3; + } else { + *dst++ = *src++; + } + } + dst_str->slen = dst - dst_str->ptr; + return dst_str; +} + +PJ_DEF(pj_ssize_t) pj_strncpy2_escape(char *dst_str, const pj_str_t *src_str, pj_ssize_t max, const pj_cis_t *unres) +{ + const char *src = src_str->ptr; + const char *src_end = src + src_str->slen; + char *dst = dst_str; + char *dst_end = dst + max; + + if (max < src_str->slen) + return -1; + + while (src != src_end && dst != dst_end) { + if (pj_cis_match(unres, *src)) { + *dst++ = *src++; + } else { + if (dst < dst_end - 2) { + *dst++ = '%'; + pj_val_to_hex_digit(*src, dst); + dst += 2; + ++src; + } else { + break; + } + } + } + + return src == src_end ? dst - dst_str : -1; +} + +PJ_DEF(pj_str_t *) pj_strncpy_escape(pj_str_t *dst_str, const pj_str_t *src_str, pj_ssize_t max, const pj_cis_t *unres) +{ + dst_str->slen = pj_strncpy2_escape(dst_str->ptr, src_str, max, unres); + return dst_str->slen < 0 ? NULL : dst_str; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c new file mode 100755 index 000000000..d8156ff67 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_simple.c" + +PJ_DEF(pj_status_t) +pjstun_create_bind_req(pj_pool_t *pool, void **msg, pj_size_t *len, pj_uint32_t id_hi, pj_uint32_t id_lo) +{ + pjstun_msg_hdr *hdr; + + PJ_CHECK_STACK(); + + hdr = PJ_POOL_ZALLOC_T(pool, pjstun_msg_hdr); + if (!hdr) + return PJ_ENOMEM; + + hdr->type = pj_htons(PJSTUN_BINDING_REQUEST); + hdr->tsx[2] = pj_htonl(id_hi); + hdr->tsx[3] = pj_htonl(id_lo); + *msg = hdr; + *len = sizeof(pjstun_msg_hdr); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjstun_parse_msg(void *buf, pj_size_t buf_len, pjstun_msg *msg) +{ + pj_uint16_t msg_type, msg_len; + char *p_attr; + int attr_max_cnt = PJ_ARRAY_SIZE(msg->attr); + + PJ_CHECK_STACK(); + + msg->hdr = (pjstun_msg_hdr *)buf; + msg_type = pj_ntohs(msg->hdr->type); + + switch (msg_type) { + case PJSTUN_BINDING_REQUEST: + case PJSTUN_BINDING_RESPONSE: + case PJSTUN_BINDING_ERROR_RESPONSE: + case PJSTUN_SHARED_SECRET_REQUEST: + case PJSTUN_SHARED_SECRET_RESPONSE: + case PJSTUN_SHARED_SECRET_ERROR_RESPONSE: + break; + default: + PJ_LOG(4, (THIS_FILE, "Error: unknown msg type %d", msg_type)); + return PJLIB_UTIL_ESTUNINMSGTYPE; + } + + msg_len = pj_ntohs(msg->hdr->length); + if (msg_len != buf_len - sizeof(pjstun_msg_hdr)) { + PJ_LOG(4, (THIS_FILE, "Error: invalid msg_len %d (expecting %d)", msg_len, buf_len - sizeof(pjstun_msg_hdr))); + return PJLIB_UTIL_ESTUNINMSGLEN; + } + + msg->attr_count = 0; + p_attr = (char *)buf + sizeof(pjstun_msg_hdr); + + while (msg_len > 0 && msg->attr_count < attr_max_cnt) { + pjstun_attr_hdr **attr = &msg->attr[msg->attr_count]; + pj_uint32_t len; + pj_uint16_t attr_type; + + *attr = (pjstun_attr_hdr *)p_attr; + len = pj_ntohs((pj_uint16_t)((*attr)->length)) + sizeof(pjstun_attr_hdr); + len = (len + 3) & ~3; + + if (msg_len < len) { + PJ_LOG(4, (THIS_FILE, "Error: length mismatch in attr %d", msg->attr_count)); + return PJLIB_UTIL_ESTUNINATTRLEN; + } + + attr_type = pj_ntohs((*attr)->type); + if (attr_type > PJSTUN_ATTR_REFLECTED_FROM && attr_type != PJSTUN_ATTR_XOR_MAPPED_ADDR) { + PJ_LOG(5, (THIS_FILE, + "Warning: unknown attr type %x in attr %d. " + "Attribute was ignored.", + attr_type, msg->attr_count)); + } + + msg_len = (pj_uint16_t)(msg_len - len); + p_attr += len; + ++msg->attr_count; + } + if (msg->attr_count == attr_max_cnt) { + PJ_LOG(4, (THIS_FILE, "Warning: max number attribute %d reached.", attr_max_cnt)); + } + + return PJ_SUCCESS; +} + +PJ_DEF(void *) pjstun_msg_find_attr(pjstun_msg *msg, pjstun_attr_type t) +{ + int i; + + PJ_CHECK_STACK(); + + for (i = 0; i < msg->attr_count; ++i) { + pjstun_attr_hdr *attr = msg->attr[i]; + if (pj_ntohs(attr->type) == t) + return attr; + } + + return 0; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c new file mode 100755 index 000000000..705d38397 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { MAX_REQUEST = 4 }; +static int stun_timer[] = {500, 500, 500, 500}; +#define STUN_MAGIC 0x2112A442 + +#define THIS_FILE "stun_client.c" + +#define TRACE_(x) PJ_LOG(6, x) + +PJ_DEF(pj_status_t) +pjstun_get_mapped_addr(pj_pool_factory *pf, int sock_cnt, pj_sock_t sock[], const pj_str_t *srv1, int port1, + const pj_str_t *srv2, int port2, pj_sockaddr_in mapped_addr[]) +{ + pjstun_setting opt; + + pj_bzero(&opt, sizeof(opt)); + opt.use_stun2 = PJ_FALSE; + opt.srv1 = *srv1; + opt.port1 = port1; + opt.srv2 = *srv2; + opt.port2 = port2; + + return pjstun_get_mapped_addr2(pf, &opt, sock_cnt, sock, mapped_addr); +} + +PJ_DEF(pj_status_t) +pjstun_get_mapped_addr2(pj_pool_factory *pf, const pjstun_setting *opt, int sock_cnt, pj_sock_t sock[], + pj_sockaddr_in mapped_addr[]) +{ + unsigned srv_cnt; + const pj_str_t *srv1, *srv2; + int port1, port2; + pj_sockaddr srv_addr[2]; + int i, send_cnt = 0, nfds; + pj_pool_t *pool; + struct query_rec { + struct { + pj_uint32_t mapped_addr; + pj_uint32_t mapped_port; + } srv[2]; + } * rec; + void *out_msg; + pj_size_t out_msg_len; + int wait_resp = 0; + pj_status_t status; + + PJ_CHECK_STACK(); + + srv1 = &opt->srv1; + port1 = opt->port1; + srv2 = &opt->srv1; + port2 = opt->port2; + + TRACE_((THIS_FILE, "Entering pjstun_get_mapped_addr()")); + + /* Create pool. */ + pool = pj_pool_create(pf, "stun%p", 400, 400, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Allocate client records */ + rec = (struct query_rec *)pj_pool_calloc(pool, sock_cnt, sizeof(*rec)); + if (!rec) { + status = PJ_ENOMEM; + goto on_error; + } + + TRACE_((THIS_FILE, " Memory allocated.")); + + /* Create the outgoing BIND REQUEST message template */ + status = pjstun_create_bind_req(pool, &out_msg, &out_msg_len, pj_rand(), pj_rand()); + if (status != PJ_SUCCESS) + goto on_error; + + /* Insert magic cookie (specified in RFC 5389) when requested to. */ + if (opt->use_stun2) { + pjstun_msg_hdr *hdr = (pjstun_msg_hdr *)out_msg; + hdr->tsx[0] = pj_htonl(STUN_MAGIC); + } + + TRACE_((THIS_FILE, " Binding request created.")); + + /* Resolve servers. */ + status = pj_sockaddr_init(opt->af, &srv_addr[0], srv1, (pj_uint16_t)port1); + if (status != PJ_SUCCESS) + goto on_error; + + srv_cnt = 1; + + if (srv2 && port2) { + status = pj_sockaddr_init(opt->af, &srv_addr[1], srv2, (pj_uint16_t)port2); + if (status != PJ_SUCCESS) + goto on_error; + + if (pj_sockaddr_cmp(&srv_addr[1], &srv_addr[0]) != 0) { + srv_cnt++; + } + } + + TRACE_((THIS_FILE, " Server initialized, using %d server(s)", srv_cnt)); + + /* Init mapped addresses to zero */ + pj_memset(mapped_addr, 0, sock_cnt * sizeof(pj_sockaddr_in)); + + /* We need these many responses */ + wait_resp = sock_cnt * srv_cnt; + + TRACE_((THIS_FILE, " Done initialization.")); + +#if defined(PJ_SELECT_NEEDS_NFDS) && PJ_SELECT_NEEDS_NFDS != 0 + nfds = -1; + for (i = 0; i < sock_cnt; ++i) { + if (sock[i] > nfds) { + nfds = sock[i]; + } + } +#else + nfds = FD_SETSIZE - 1; +#endif + + /* Main retransmission loop. */ + for (send_cnt = 0; send_cnt < MAX_REQUEST; ++send_cnt) { + pj_time_val next_tx, now; + pj_fd_set_t r; + int select_rc; + + PJ_FD_ZERO(&r); + + /* Send messages to servers that has not given us response. */ + for (i = 0; i < sock_cnt && status == PJ_SUCCESS; ++i) { + unsigned j; + for (j = 0; j < srv_cnt && status == PJ_SUCCESS; ++j) { + pjstun_msg_hdr *msg_hdr = (pjstun_msg_hdr *)out_msg; + pj_ssize_t sent_len; + + if (rec[i].srv[j].mapped_port != 0) + continue; + + /* Modify message so that we can distinguish response. */ + msg_hdr->tsx[2] = pj_htonl(i); + msg_hdr->tsx[3] = pj_htonl(j); + + /* Send! */ + sent_len = out_msg_len; + status = pj_sock_sendto(sock[i], out_msg, &sent_len, 0, (pj_sockaddr_t *)&srv_addr[j], + pj_sockaddr_get_len(&srv_addr[j])); + } + } + + /* All requests sent. + * The loop below will wait for responses until all responses have + * been received (i.e. wait_resp==0) or timeout occurs, which then + * we'll go to the next retransmission iteration. + */ + TRACE_((THIS_FILE, " Request(s) sent, counter=%d", send_cnt)); + + /* Calculate time of next retransmission. */ + pj_gettickcount(&next_tx); + next_tx.sec += (stun_timer[send_cnt] / 1000); + next_tx.msec += (stun_timer[send_cnt] % 1000); + pj_time_val_normalize(&next_tx); + + for (pj_gettickcount(&now), select_rc = 1; + status == PJ_SUCCESS && select_rc >= 1 && wait_resp > 0 && PJ_TIME_VAL_LT(now, next_tx); + pj_gettickcount(&now)) { + pj_time_val timeout; + + timeout = next_tx; + PJ_TIME_VAL_SUB(timeout, now); + + for (i = 0; i < sock_cnt; ++i) { + PJ_FD_SET(sock[i], &r); + } + + select_rc = pj_sock_select(nfds + 1, &r, NULL, NULL, &timeout); + TRACE_((THIS_FILE, " select() rc=%d", select_rc)); + if (select_rc < 1) + continue; + + for (i = 0; i < sock_cnt; ++i) { + int sock_idx, srv_idx; + pj_ssize_t len; + pjstun_msg msg; + pj_sockaddr addr; + int addrlen = sizeof(addr); + pjstun_mapped_addr_attr *attr; + char recv_buf[128]; + + if (!PJ_FD_ISSET(sock[i], &r)) + continue; + + len = sizeof(recv_buf); + status = pj_sock_recvfrom(sock[i], recv_buf, &len, 0, (pj_sockaddr_t *)&addr, &addrlen); + + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (THIS_FILE, status, "recvfrom() error ignored")); + + /* Ignore non-PJ_SUCCESS status. + * It possible that other SIP entity is currently + * sending SIP request to us, and because SIP message + * is larger than STUN, we could get EMSGSIZE when + * we call recvfrom(). + */ + status = PJ_SUCCESS; + continue; + } + + status = pjstun_parse_msg(recv_buf, len, &msg); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (THIS_FILE, status, "STUN parsing error ignored")); + + /* Also ignore non-successful parsing. This may not + * be STUN response at all. See the comment above. + */ + status = PJ_SUCCESS; + continue; + } + + sock_idx = pj_ntohl(msg.hdr->tsx[2]); + srv_idx = pj_ntohl(msg.hdr->tsx[3]); + + if (sock_idx < 0 || sock_idx >= sock_cnt || sock_idx != i || srv_idx < 0 || srv_idx >= 2) { + status = PJLIB_UTIL_ESTUNININDEX; + continue; + } + + if (pj_ntohs(msg.hdr->type) != PJSTUN_BINDING_RESPONSE) { + status = PJLIB_UTIL_ESTUNNOBINDRES; + continue; + } + + if (rec[sock_idx].srv[srv_idx].mapped_port != 0) { + /* Already got response */ + continue; + } + + /* From this part, we consider the packet as a valid STUN + * response for our request. + */ + --wait_resp; + + if (pjstun_msg_find_attr(&msg, PJSTUN_ATTR_ERROR_CODE) != NULL) { + status = PJLIB_UTIL_ESTUNRECVERRATTR; + continue; + } + + attr = (pjstun_mapped_addr_attr *)pjstun_msg_find_attr(&msg, PJSTUN_ATTR_MAPPED_ADDR); + if (!attr) { + attr = (pjstun_mapped_addr_attr *)pjstun_msg_find_attr(&msg, PJSTUN_ATTR_XOR_MAPPED_ADDR); + if (!attr || attr->family != 1) { + status = PJLIB_UTIL_ESTUNNOMAP; + continue; + } + } + + rec[sock_idx].srv[srv_idx].mapped_addr = attr->addr; + rec[sock_idx].srv[srv_idx].mapped_port = attr->port; + if (pj_ntohs(attr->hdr.type) == PJSTUN_ATTR_XOR_MAPPED_ADDR) { + rec[sock_idx].srv[srv_idx].mapped_addr ^= pj_htonl(STUN_MAGIC); + rec[sock_idx].srv[srv_idx].mapped_port ^= pj_htons(STUN_MAGIC >> 16); + } + } + } + + /* The best scenario is if all requests have been replied. + * Then we don't need to go to the next retransmission iteration. + */ + if (wait_resp <= 0) + break; + } + + TRACE_((THIS_FILE, " All responses received, calculating result..")); + + for (i = 0; i < sock_cnt && status == PJ_SUCCESS; ++i) { + if (srv_cnt == 1) { + mapped_addr[i].sin_family = pj_AF_INET(); + mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr; + mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port; + + if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) { + status = PJLIB_UTIL_ESTUNNOTRESPOND; + break; + } + } else if (rec[i].srv[0].mapped_addr == rec[i].srv[1].mapped_addr && + rec[i].srv[0].mapped_port == rec[i].srv[1].mapped_port) { + mapped_addr[i].sin_family = pj_AF_INET(); + mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr; + mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port; + + if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) { + status = PJLIB_UTIL_ESTUNNOTRESPOND; + break; + } + } else { + status = PJLIB_UTIL_ESTUNSYMMETRIC; + break; + } + } + + TRACE_((THIS_FILE, " Pool usage=%d of %d", pj_pool_get_used_size(pool), pj_pool_get_capacity(pool))); + + pj_pool_release(pool); + + TRACE_((THIS_FILE, " Done.")); + return status; + +on_error: + if (pool) + pj_pool_release(pool); + return status; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c new file mode 100755 index 000000000..6395d36f1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +/* + * md5.h + */ +PJ_EXPORT_SYMBOL(md5_init) +PJ_EXPORT_SYMBOL(md5_append) +PJ_EXPORT_SYMBOL(md5_finish) + +/* + * scanner.h + */ +PJ_EXPORT_SYMBOL(pj_cs_init) +PJ_EXPORT_SYMBOL(pj_cs_set) +PJ_EXPORT_SYMBOL(pj_cs_add_range) +PJ_EXPORT_SYMBOL(pj_cs_add_alpha) +PJ_EXPORT_SYMBOL(pj_cs_add_num) +PJ_EXPORT_SYMBOL(pj_cs_add_str) +PJ_EXPORT_SYMBOL(pj_cs_del_range) +PJ_EXPORT_SYMBOL(pj_cs_del_str) +PJ_EXPORT_SYMBOL(pj_cs_invert) +PJ_EXPORT_SYMBOL(pj_scan_init) +PJ_EXPORT_SYMBOL(pj_scan_fini) +PJ_EXPORT_SYMBOL(pj_scan_peek) +PJ_EXPORT_SYMBOL(pj_scan_peek_n) +PJ_EXPORT_SYMBOL(pj_scan_peek_until) +PJ_EXPORT_SYMBOL(pj_scan_get) +PJ_EXPORT_SYMBOL(pj_scan_get_quote) +PJ_EXPORT_SYMBOL(pj_scan_get_n) +PJ_EXPORT_SYMBOL(pj_scan_get_char) +PJ_EXPORT_SYMBOL(pj_scan_get_newline) +PJ_EXPORT_SYMBOL(pj_scan_get_until) +PJ_EXPORT_SYMBOL(pj_scan_get_until_ch) +PJ_EXPORT_SYMBOL(pj_scan_get_until_chr) +PJ_EXPORT_SYMBOL(pj_scan_advance_n) +PJ_EXPORT_SYMBOL(pj_scan_strcmp) +PJ_EXPORT_SYMBOL(pj_scan_stricmp) +PJ_EXPORT_SYMBOL(pj_scan_skip_whitespace) +PJ_EXPORT_SYMBOL(pj_scan_save_state) +PJ_EXPORT_SYMBOL(pj_scan_restore_state) + +/* + * stun.h + */ +PJ_EXPORT_SYMBOL(pj_stun_create_bind_req) +PJ_EXPORT_SYMBOL(pj_stun_parse_msg) +PJ_EXPORT_SYMBOL(pj_stun_msg_find_attr) +PJ_EXPORT_SYMBOL(pj_stun_get_mapped_addr) +PJ_EXPORT_SYMBOL(pj_stun_get_err_msg) + +/* + * xml.h + */ +PJ_EXPORT_SYMBOL(pj_xml_parse) +PJ_EXPORT_SYMBOL(pj_xml_print) +PJ_EXPORT_SYMBOL(pj_xml_add_node) +PJ_EXPORT_SYMBOL(pj_xml_add_attr) +PJ_EXPORT_SYMBOL(pj_xml_find_node) +PJ_EXPORT_SYMBOL(pj_xml_find_next_node) +PJ_EXPORT_SYMBOL(pj_xml_find_attr) +PJ_EXPORT_SYMBOL(pj_xml_find) diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c new file mode 100755 index 000000000..be563893b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define EX_SYNTAX_ERROR 12 +#define THIS_FILE "xml.c" + +static void on_syntax_error(struct pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(EX_SYNTAX_ERROR); +} + +static pj_xml_node *alloc_node(pj_pool_t *pool) +{ + pj_xml_node *node; + + node = PJ_POOL_ZALLOC_T(pool, pj_xml_node); + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + + return node; +} + +static pj_xml_attr *alloc_attr(pj_pool_t *pool) +{ + return PJ_POOL_ZALLOC_T(pool, pj_xml_attr); +} + +/* This is a recursive function! */ +static pj_xml_node *xml_parse_node(pj_pool_t *pool, pj_scanner *scanner) +{ + pj_xml_node *node; + pj_str_t end_name; + + PJ_CHECK_STACK(); + + if (*scanner->curptr != '<') + on_syntax_error(scanner); + + /* Handle Processing Instructino (PI) construct (i.e. "curptr == '<' && *(scanner->curptr + 1) == '?') { + pj_scan_advance_n(scanner, 2, PJ_FALSE); + for (;;) { + pj_str_t dummy; + pj_scan_get_until_ch(scanner, '?', &dummy); + if (*scanner->curptr == '?' && *(scanner->curptr + 1) == '>') { + pj_scan_advance_n(scanner, 2, PJ_TRUE); + break; + } else { + pj_scan_advance_n(scanner, 1, PJ_FALSE); + } + } + return xml_parse_node(pool, scanner); + } + + /* Handle comments construct (i.e. "', &dummy); + if (pj_scan_strcmp(scanner, ">", 1) == 0) { + pj_scan_advance_n(scanner, 1, PJ_TRUE); + break; + } else { + pj_scan_advance_n(scanner, 1, PJ_FALSE); + } + } + return xml_parse_node(pool, scanner); + } + + /* Alloc node. */ + node = alloc_node(pool); + + /* Get '<' */ + pj_scan_get_char(scanner); + + /* Get node name. */ + pj_scan_get_until_chr(scanner, " />\t\r\n", &node->name); + + /* Get attributes. */ + while (*scanner->curptr != '>' && *scanner->curptr != '/') { + pj_xml_attr *attr = alloc_attr(pool); + + pj_scan_get_until_chr(scanner, "=> \t\r\n", &attr->name); + if (*scanner->curptr == '=') { + pj_scan_get_char(scanner); + pj_scan_get_quotes(scanner, "\"'", "\"'", 2, &attr->value); + /* remove quote characters */ + ++attr->value.ptr; + attr->value.slen -= 2; + } + + pj_list_push_back(&node->attr_head, attr); + } + + if (*scanner->curptr == '/') { + pj_scan_get_char(scanner); + if (pj_scan_get_char(scanner) != '>') + on_syntax_error(scanner); + return node; + } + + /* Enclosing bracket. */ + if (pj_scan_get_char(scanner) != '>') + on_syntax_error(scanner); + + /* Sub nodes. */ + while (*scanner->curptr == '<' && *(scanner->curptr + 1) != '/' && *(scanner->curptr + 1) != '!') { + pj_xml_node *sub_node = xml_parse_node(pool, scanner); + pj_list_push_back(&node->node_head, sub_node); + } + + /* Content. */ + if (!pj_scan_is_eof(scanner) && *scanner->curptr != '<') { + pj_scan_get_until_ch(scanner, '<', &node->content); + } + + /* CDATA content. */ + if (*scanner->curptr == '<' && *(scanner->curptr + 1) == '!' && pj_scan_strcmp(scanner, "content); + while (pj_scan_strcmp(scanner, "]]>", 3)) { + pj_str_t dummy; + + pj_scan_advance_n(scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(scanner, ']', &dummy); + } + node->content.slen = scanner->curptr - node->content.ptr; + pj_scan_advance_n(scanner, 3, PJ_TRUE); + } + + /* Enclosing node. */ + if (pj_scan_get_char(scanner) != '<' || pj_scan_get_char(scanner) != '/') + on_syntax_error(scanner); + + pj_scan_get_until_chr(scanner, " \t>", &end_name); + + /* Compare name. */ + if (pj_stricmp(&node->name, &end_name) != 0) + on_syntax_error(scanner); + + /* Enclosing '>' */ + if (pj_scan_get_char(scanner) != '>') + on_syntax_error(scanner); + + return node; +} + +PJ_DEF(pj_xml_node *) pj_xml_parse(pj_pool_t *pool, char *msg, pj_size_t len) +{ + pj_xml_node *node = NULL; + pj_scanner scanner; + PJ_USE_EXCEPTION; + + if (!msg || !len || !pool) + return NULL; + + pj_scan_init(&scanner, msg, len, PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE, &on_syntax_error); + PJ_TRY + { + node = xml_parse_node(pool, &scanner); + } + PJ_CATCH_ANY + { + PJ_LOG(4, + (THIS_FILE, "Syntax error parsing XML in line %d column %d", scanner.line, pj_scan_get_col(&scanner))); + } + PJ_END; + pj_scan_fini(&scanner); + return node; +} + +/* This is a recursive function. */ +static int xml_print_node(const pj_xml_node *node, int indent, char *buf, pj_size_t len) +{ + int i; + char *p = buf; + pj_xml_attr *attr; + pj_xml_node *sub_node; + +#define SIZE_LEFT() ((int)(len - (p - buf))) + + PJ_CHECK_STACK(); + + /* Print name. */ + if (SIZE_LEFT() < node->name.slen + indent + 5) + return -1; + for (i = 0; i < indent; ++i) + *p++ = ' '; + *p++ = '<'; + pj_memcpy(p, node->name.ptr, node->name.slen); + p += node->name.slen; + + /* Print attributes. */ + attr = node->attr_head.next; + while (attr != &node->attr_head) { + + if (SIZE_LEFT() < attr->name.slen + attr->value.slen + 4) + return -1; + + *p++ = ' '; + + /* Attribute name. */ + pj_memcpy(p, attr->name.ptr, attr->name.slen); + p += attr->name.slen; + + /* Attribute value. */ + if (attr->value.slen) { + *p++ = '='; + *p++ = '"'; + pj_memcpy(p, attr->value.ptr, attr->value.slen); + p += attr->value.slen; + *p++ = '"'; + } + + attr = attr->next; + } + + /* Check for empty node. */ + if (node->content.slen == 0 && node->node_head.next == (pj_xml_node *)&node->node_head) { + if (SIZE_LEFT() < 3) + return -1; + *p++ = ' '; + *p++ = '/'; + *p++ = '>'; + return (int)(p - buf); + } + + /* Enclosing '>' */ + if (SIZE_LEFT() < 1) + return -1; + *p++ = '>'; + + /* Print sub nodes. */ + sub_node = node->node_head.next; + while (sub_node != (pj_xml_node *)&node->node_head) { + int printed; + + if (SIZE_LEFT() < indent + 3) + return -1; + //*p++ = '\r'; + *p++ = '\n'; + + printed = xml_print_node(sub_node, indent + 1, p, SIZE_LEFT()); + if (printed < 0) + return -1; + + p += printed; + sub_node = sub_node->next; + } + + /* Content. */ + if (node->content.slen) { + if (SIZE_LEFT() < node->content.slen) + return -1; + pj_memcpy(p, node->content.ptr, node->content.slen); + p += node->content.slen; + } + + /* Enclosing node. */ + if (node->node_head.next != (pj_xml_node *)&node->node_head) { + if (SIZE_LEFT() < node->name.slen + 5 + indent) + return -1; + //*p++ = '\r'; + *p++ = '\n'; + for (i = 0; i < indent; ++i) + *p++ = ' '; + } else { + if (SIZE_LEFT() < node->name.slen + 3) + return -1; + } + *p++ = '<'; + *p++ = '/'; + pj_memcpy(p, node->name.ptr, node->name.slen); + p += node->name.slen; + *p++ = '>'; + +#undef SIZE_LEFT + + return (int)(p - buf); +} + +PJ_DEF(int) pj_xml_print(const pj_xml_node *node, char *buf, pj_size_t len, pj_bool_t include_prolog) +{ + int prolog_len = 0; + int printed; + + if (!node || !buf || !len) + return 0; + + if (include_prolog) { + pj_str_t prolog = {"\n", 39}; + if ((int)len < prolog.slen) + return -1; + pj_memcpy(buf, prolog.ptr, prolog.slen); + prolog_len = (int)prolog.slen; + } + + printed = xml_print_node(node, 0, buf + prolog_len, len - prolog_len) + prolog_len; + if (printed > 0 && len - printed >= 1) { + buf[printed++] = '\n'; + } + return printed; +} + +PJ_DEF(pj_xml_node *) pj_xml_node_new(pj_pool_t *pool, const pj_str_t *name) +{ + pj_xml_node *node = alloc_node(pool); + pj_strdup(pool, &node->name, name); + return node; +} + +PJ_DEF(pj_xml_attr *) pj_xml_attr_new(pj_pool_t *pool, const pj_str_t *name, const pj_str_t *value) +{ + pj_xml_attr *attr = alloc_attr(pool); + pj_strdup(pool, &attr->name, name); + pj_strdup(pool, &attr->value, value); + return attr; +} + +PJ_DEF(void) pj_xml_add_node(pj_xml_node *parent, pj_xml_node *node) +{ + pj_list_push_back(&parent->node_head, node); +} + +PJ_DEF(void) pj_xml_add_attr(pj_xml_node *node, pj_xml_attr *attr) +{ + pj_list_push_back(&node->attr_head, attr); +} + +PJ_DEF(pj_xml_node *) pj_xml_find_node(const pj_xml_node *parent, const pj_str_t *name) +{ + const pj_xml_node *node = parent->node_head.next; + + PJ_CHECK_STACK(); + + while (node != (void *)&parent->node_head) { + if (pj_stricmp(&node->name, name) == 0) + return (pj_xml_node *)node; + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) pj_xml_find_node_rec(const pj_xml_node *parent, const pj_str_t *name) +{ + const pj_xml_node *node = parent->node_head.next; + + PJ_CHECK_STACK(); + + while (node != (void *)&parent->node_head) { + pj_xml_node *found; + if (pj_stricmp(&node->name, name) == 0) + return (pj_xml_node *)node; + found = pj_xml_find_node_rec(node, name); + if (found) + return (pj_xml_node *)found; + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) pj_xml_find_next_node(const pj_xml_node *parent, const pj_xml_node *node, const pj_str_t *name) +{ + PJ_CHECK_STACK(); + + node = node->next; + while (node != (void *)&parent->node_head) { + if (pj_stricmp(&node->name, name) == 0) + return (pj_xml_node *)node; + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_attr *) pj_xml_find_attr(const pj_xml_node *node, const pj_str_t *name, const pj_str_t *value) +{ + const pj_xml_attr *attr = node->attr_head.next; + while (attr != (void *)&node->attr_head) { + if (pj_stricmp(&attr->name, name) == 0) { + if (value) { + if (pj_stricmp(&attr->value, value) == 0) + return (pj_xml_attr *)attr; + } else { + return (pj_xml_attr *)attr; + } + } + attr = attr->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) +pj_xml_find(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)) +{ + const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next; + + if (!name && !match) + return NULL; + + while (node != (const pj_xml_node *)&parent->node_head) { + if (name) { + if (pj_stricmp(&node->name, name) != 0) { + node = node->next; + continue; + } + } + if (match) { + if (match(node, data)) + return (pj_xml_node *)node; + } else { + return (pj_xml_node *)node; + } + + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) +pj_xml_find_rec(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)) +{ + const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next; + + if (!name && !match) + return NULL; + + while (node != (const pj_xml_node *)&parent->node_head) { + pj_xml_node *found; + + if (name) { + if (pj_stricmp(&node->name, name) == 0) { + if (match) { + if (match(node, data)) + return (pj_xml_node *)node; + } else { + return (pj_xml_node *)node; + } + } + + } else if (match) { + if (match(node, data)) + return (pj_xml_node *)node; + } + + found = pj_xml_find_rec(node, name, data, match); + if (found) + return found; + + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) pj_xml_clone(pj_pool_t *pool, const pj_xml_node *rhs) +{ + pj_xml_node *node; + const pj_xml_attr *r_attr; + const pj_xml_node *child; + + node = alloc_node(pool); + + pj_strdup(pool, &node->name, &rhs->name); + pj_strdup(pool, &node->content, &rhs->content); + + /* Clone all attributes */ + r_attr = rhs->attr_head.next; + while (r_attr != &rhs->attr_head) { + + pj_xml_attr *attr; + + attr = alloc_attr(pool); + pj_strdup(pool, &attr->name, &r_attr->name); + pj_strdup(pool, &attr->value, &r_attr->value); + + pj_list_push_back(&node->attr_head, attr); + + r_attr = r_attr->next; + } + + /* Clone all child nodes. */ + child = rhs->node_head.next; + while (child != (pj_xml_node *)&rhs->node_head) { + pj_xml_node *new_child; + + new_child = pj_xml_clone(pool, child); + pj_list_push_back(&node->node_head, new_child); + + child = child->next; + } + + return node; +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h new file mode 100755 index 000000000..4dca3f131 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ASYNCSOCK_H__ +#define __PJ_ASYNCSOCK_H__ + +/** + * @file activesock.h + * @brief Active socket + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_ACTIVESOCK Active socket I/O + * @brief Active socket performs active operations on socket. + * @ingroup PJ_IO + * @{ + * + * Active socket is a higher level abstraction to the ioqueue. It provides + * automation to socket operations which otherwise would have to be done + * manually by the applications. For example with socket recv(), recvfrom(), + * and accept() operations, application only needs to invoke these + * operation once, and it will be notified whenever data or incoming TCP + * connection (in the case of accept()) arrives. + */ + +/** + * This opaque structure describes the active socket. + */ +typedef struct pj_activesock_t pj_activesock_t; + +/** + * This structure contains the callbacks to be called by the active socket. + */ +typedef struct pj_activesock_cb { + /** + * This callback is called when a data arrives as the result of + * pj_activesock_start_read(). + * + * @param asock The active socket. + * @param data The buffer containing the new data, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument may be NULL. + * @param size The length of data in the buffer. + * @param status The status of the read operation. This may contain + * non-PJ_SUCCESS for example when the TCP connection + * has been closed. In this case, the buffer may + * contain left over data from previous callback which + * the application may want to process. + * @param remainder If application wishes to leave some data in the + * buffer (common for TCP applications), it should + * move the remainder data to the front part of the + * buffer and set the remainder length here. The value + * of this parameter will be ignored for datagram + * sockets. + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_read)(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); + /** + * This callback is called when a packet arrives as the result of + * pj_activesock_start_recvfrom(). + * + * @param asock The active socket. + * @param data The buffer containing the packet, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + * @param size The length of packet in the buffer. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to zero. + * @param src_addr Source address of the packet. + * @param addr_len Length of the source address. + * @param status This contains + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_recvfrom)(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + + /** + * This callback is called when data has been sent. + * + * @param asock The active socket. + * @param send_key Key associated with the send operation. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_sent)(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + + /** + * This callback is called when new connection arrives as the result + * of pj_activesock_start_accept(). If the status of accept operation is + * needed use on_accept_complete2 instead of this callback. + * + * @param asock The active socket. + * @param newsock The new incoming socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the active socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete)(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len); + + /** + * This callback is called when new connection arrives as the result + * of pj_activesock_start_accept(). + * + * @param asock The active socket. + * @param newsock The new incoming socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * @param status The status of the accept operation. This may contain + * non-PJ_SUCCESS for example when the TCP listener is in + * bad state for example on iOS platform after the + * application waking up from background. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the active socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete2)(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status); + + /** + * This callback is called when pending connect operation has been + * completed. + * + * @param asock The active socket. + * @param status The connection result. If connection has been + * successfully established, the status will contain + * PJ_SUCCESS. + * + * @return Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_connect_complete)(pj_activesock_t *asock, pj_status_t status); + +} pj_activesock_cb; + +/** + * Settings that can be given during active socket creation. Application + * must initialize this structure with #pj_activesock_cfg_default(). + */ +typedef struct pj_activesock_cfg { + /** + * Optional group lock to be assigned to the ioqueue key. + */ + pj_grp_lock_t *grp_lock; + + /** + * Number of concurrent asynchronous operations that is to be supported + * by the active socket. This value only affects socket receive and + * accept operations -- the active socket will issue one or more + * asynchronous read and accept operations based on the value of this + * field. Setting this field to more than one will allow more than one + * incoming data or incoming connections to be processed simultaneously + * on multiprocessor systems, when the ioqueue is polled by more than + * one threads. + * + * The default value is 1. + */ + unsigned async_cnt; + + /** + * The ioqueue concurrency to be forced on the socket when it is + * registered to the ioqueue. See #pj_ioqueue_set_concurrency() for more + * info about ioqueue concurrency. + * + * When this value is -1, the concurrency setting will not be forced for + * this socket, and the socket will inherit the concurrency setting of + * the ioqueue. When this value is zero, the active socket will disable + * concurrency for the socket. When this value is +1, the active socket + * will enable concurrency for the socket. + * + * The default value is -1. + */ + int concurrency; + + /** + * If this option is specified, the active socket will make sure that + * asynchronous send operation with stream oriented socket will only + * call the callback after all data has been sent. This means that the + * active socket will automatically resend the remaining data until + * all data has been sent. + * + * Please note that when this option is specified, it is possible that + * error is reported after partial data has been sent. Also setting + * this will disable the ioqueue concurrency for the socket. + * + * Default value is 1. + */ + pj_bool_t whole_data; + +} pj_activesock_cfg; + +/** + * Initialize the active socket configuration with the default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_activesock_cfg_default(pj_activesock_cfg *cfg); + +/** + * Create the active socket for the specified socket. This will register + * the socket to the specified ioqueue. + * + * @param pool Pool to allocate memory from. + * @param sock The socket handle. + * @param sock_type Specify socket type, either pj_SOCK_DGRAM() or + * pj_SOCK_STREAM(). The active socket needs this + * information to handle connection closure for + * connection oriented sockets. + * @param ioqueue The ioqueue to use. + * @param opt Optional settings. When this setting is not specifed, + * the default values will be used. + * @param cb Pointer to structure containing application + * callbacks. + * @param user_data Arbitrary user data to be associated with this + * active socket. + * @param p_asock Pointer to receive the active socket instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_create(pj_pool_t *pool, pj_sock_t sock, int sock_type, const pj_activesock_cfg *opt, + pj_ioqueue_t *ioqueue, const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock); + +/** + * Create UDP socket descriptor, bind it to the specified address, and + * create the active socket for the socket descriptor. + * + * @param pool Pool to allocate memory from. + * @param addr Specifies the address family of the socket and the + * address where the socket should be bound to. If + * this argument is NULL, then AF_INET is assumed and + * the socket will be bound to any addresses and port. + * @param ioqueue The ioqueue. + * @param opt Optional settings. When this setting is not specifed, + * the default values will be used. + * @param cb Pointer to structure containing application + * callbacks. + * @param user_data Arbitrary user data to be associated with this + * active socket. + * @param p_asock Pointer to receive the active socket instance. + * @param bound_addr If this argument is specified, it will be filled with + * the bound address on return. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_create_udp(pj_pool_t *pool, const pj_sockaddr *addr, const pj_activesock_cfg *opt, pj_ioqueue_t *ioqueue, + const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock, + pj_sockaddr *bound_addr); + +/** + * Close the active socket. This will unregister the socket from the + * ioqueue and ultimately close the socket. + * + * @param asock The active socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_activesock_close(pj_activesock_t *asock); + +#if (defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0) || defined(DOXYGEN) +/** + * Set iPhone OS background mode setting. Setting to 1 will enable TCP + * active socket to receive incoming data when application is in the + * background. Setting to 0 will disable it. Default value of this + * setting is PJ_ACTIVESOCK_TCP_IPHONE_OS_BG. + * + * This API is only available if PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT + * is set to non-zero. + * + * @param asock The active socket. + * @param val The value of background mode setting. + * + */ +PJ_DECL(void) pj_activesock_set_iphone_os_bg(pj_activesock_t *asock, int val); + +/** + * Enable/disable support for iPhone OS background mode. This setting + * will apply globally and will affect any active sockets created + * afterwards, if you want to change the setting for a particular + * active socket, use #pj_activesock_set_iphone_os_bg() instead. + * By default, this setting is enabled. + * + * This API is only available if PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT + * is set to non-zero. + * + * @param val The value of global background mode setting. + * + */ +PJ_DECL(void) pj_activesock_enable_iphone_os_bg(pj_bool_t val); +#endif + +/** + * Associate arbitrary data with the active socket. Application may + * inspect this data in the callbacks and associate it with higher + * level processing. + * + * @param asock The active socket. + * @param user_data The user data to be associated with the active + * socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_activesock_set_user_data(pj_activesock_t *asock, void *user_data); + +/** + * Retrieve the user data previously associated with this active + * socket. + * + * @param asock The active socket. + * + * @return The user data. + */ +PJ_DECL(void *) pj_activesock_get_user_data(pj_activesock_t *asock); + +/** + * Starts read operation on this active socket. This function will create + * \a async_cnt number of buffers (the \a async_cnt parameter was given + * in \a pj_activesock_create() function) where each buffer is \a buff_size + * long. The buffers are allocated from the specified \a pool. Once the + * buffers are created, it then issues \a async_cnt number of asynchronous + * \a recv() operations to the socket and returns back to caller. Incoming + * data on the socket will be reported back to application via the + * \a on_data_read() callback. + * + * Application only needs to call this function once to initiate read + * operations. Further read operations will be done automatically by the + * active socket when \a on_data_read() callback returns non-zero. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_read(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_activesock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_read2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); + +/** + * Same as pj_activesock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_recvfrom(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_activesock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_recvfrom2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); + +/** + * Send data using the socket. + * + * @param asock The active socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_activesock_send(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags); + +/** + * Send datagram using the socket. + * + * @param asock The active socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * @param addr The destination address. + * @param addr_len The length of the address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_activesock_sendto(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len); + +#if PJ_HAS_TCP +/** + * Starts asynchronous socket accept() operations on this active socket. + * Application must bind the socket before calling this function. This + * function will issue \a async_cnt number of asynchronous \a accept() + * operations to the socket and returns back to caller. Incoming + * connection on the socket will be reported back to application via the + * \a on_accept_complete() callback. + * + * Application only needs to call this function once to initiate accept() + * operations. Further accept() operations will be done automatically by + * the active socket when \a on_accept_complete() callback returns non-zero. + * + * @param asock The active socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_activesock_start_accept(pj_activesock_t *asock, pj_pool_t *pool); + +/** + * Starts asynchronous socket connect() operation for this socket. Once + * the connection is done (either successfully or not), the + * \a on_connect_complete() callback will be called. + * + * @param asock The active socket. + * @param pool The pool to allocate some internal data for the + * operation. + * @param remaddr Remote address. + * @param addr_len Length of the remote address. + * + * @return PJ_SUCCESS if connection can be established immediately, + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_connect(pj_activesock_t *asock, pj_pool_t *pool, const pj_sockaddr_t *remaddr, int addr_len); + +#endif /* PJ_HAS_TCP */ + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_ASYNCSOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h b/src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h new file mode 100755 index 000000000..b0346dc89 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ADDR_RESOLV_H__ +#define __PJ_ADDR_RESOLV_H__ + +/** + * @file addr_resolv.h + * @brief IP address resolution. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_addr_resolve Network Address Resolution + * @ingroup PJ_IO + * @{ + * + * This module provides function to resolve Internet address of the + * specified host name. To resolve a particular host name, application + * can just call #pj_gethostbyname(). + * + * Example: + *
+ *   ...
+ *   pj_hostent he;
+ *   pj_status_t rc;
+ *   pj_str_t host = pj_str("host.example.com");
+ *
+ *   rc = pj_gethostbyname( &host, &he);
+ *   if (rc != PJ_SUCCESS) {
+ *      char errbuf[80];
+ *      pj_strerror( rc, errbuf, sizeof(errbuf));
+ *      PJ_LOG(2,("sample", "Unable to resolve host, error=%s", errbuf));
+ *      return rc;
+ *   }
+ *
+ *   // process address...
+ *   addr.sin_addr.s_addr = *(pj_uint32_t*)he.h_addr;
+ *   ...
+ * 
+ * + * It's pretty simple really... + */ + +/** This structure describes an Internet host address. */ +typedef struct pj_hostent { + char *h_name; /**< The official name of the host. */ + char **h_aliases; /**< Aliases list. */ + int h_addrtype; /**< Host address type. */ + int h_length; /**< Length of address. */ + char **h_addr_list; /**< List of addresses. */ +} pj_hostent; + +/** Shortcut to h_addr_list[0] */ +#define h_addr h_addr_list[0] + +/** + * This structure describes address information pj_getaddrinfo(). + */ +typedef struct pj_addrinfo { + char ai_canonname[PJ_MAX_HOSTNAME]; /**< Canonical name for host*/ + pj_sockaddr ai_addr; /**< Binary address. */ +} pj_addrinfo; + +/** + * This function fills the structure of type pj_hostent for a given host name. + * For host resolution function that also works with IPv6, please see + * #pj_getaddrinfo(). + * + * @param name Host name to resolve. Specifying IPv4 address here + * may fail on some platforms (e.g. Windows) + * @param he The pj_hostent structure to be filled. Note that + * the pointers in this structure points to temporary + * variables which value will be reset upon subsequent + * invocation. + * + * @return PJ_SUCCESS, or the appropriate error codes. + */ +PJ_DECL(pj_status_t) pj_gethostbyname(const pj_str_t *name, pj_hostent *he); + +/** + * Resolve the primary IP address of local host. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET() or pj_AF_INET6(). + * @param addr On successful resolution, the address family and address + * part of this socket address will be filled up with the host + * IP address, in network byte order. Other parts of the socket + * address are untouched. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr); + +/** + * Get the interface IP address to send data to the specified destination. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET() or pj_AF_INET6(). + * @param dst The destination host. + * @param itf_addr On successful resolution, the address family and address + * part of this socket address will be filled up with the host + * IP address, in network byte order. Other parts of the socket + * address should be ignored. + * @param allow_resolve If \a dst may contain hostname (instead of IP + * address), specify whether hostname resolution should + * be performed. If not, default interface address will + * be returned. + * @param p_dst_addr If not NULL, it will be filled with the IP address of + * the destination host. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_getipinterface(int af, const pj_str_t *dst, pj_sockaddr *itf_addr, pj_bool_t allow_resolve, pj_sockaddr *p_dst_addr); + +/** + * Get the IP address of the default interface. Default interface is the + * interface of the default route. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET() or pj_AF_INET6(). + * @param addr On successful resolution, the address family and address + * part of this socket address will be filled up with the host + * IP address, in network byte order. Other parts of the socket + * address are untouched. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_getdefaultipinterface(int af, pj_sockaddr *addr); + +/** + * This function translates the name of a service location (for example, + * a host name) and returns a set of addresses and associated information + * to be used in creating a socket with which to address the specified + * service. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET(), pj_AF_INET6(), or pj_AF_UNSPEC(). + * @param name Descriptive name or an address string, such as host + * name. + * @param count On input, it specifies the number of elements in + * \a ai array. On output, this will be set with the + * number of address informations found for the + * specified name. + * @param ai Array of address info to be filled with the information + * about the host. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_getaddrinfo(int af, const pj_str_t *name, unsigned *count, pj_addrinfo ai[]); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_ADDR_RESOLV_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/array.h b/src/tuya_p2p/pjproject/pjlib/include/pj/array.h new file mode 100755 index 000000000..c8c07ea3c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/array.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ARRAY_H__ +#define __PJ_ARRAY_H__ + +/** + * @file array.h + * @brief PJLIB Array helper. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_ARRAY Array helper. + * @ingroup PJ_DS + * @{ + * + * This module provides helper to manipulate array of elements of any size. + * It provides most used array operations such as insert, erase, and search. + */ + +/** + * Insert value to the array at the given position, and rearrange the + * remaining nodes after the position. + * + * @param array the array. + * @param elem_size the size of the individual element. + * @param count the CURRENT number of elements in the array. + * @param pos the position where the new element is put. + * @param value the value to copy to the new element. + */ +PJ_DECL(void) pj_array_insert(void *array, unsigned elem_size, unsigned count, unsigned pos, const void *value); + +/** + * Erase a value from the array at given position, and rearrange the remaining + * elements post the erased element. + * + * @param array the array. + * @param elem_size the size of the individual element. + * @param count the current number of elements in the array. + * @param pos the index/position to delete. + */ +PJ_DECL(void) pj_array_erase(void *array, unsigned elem_size, unsigned count, unsigned pos); + +/** + * Search the first value in the array according to matching function. + * + * @param array the array. + * @param elem_size the individual size of the element. + * @param count the number of elements. + * @param matching the matching function, which MUST return PJ_SUCCESS if + * the specified element match. + * @param result the pointer to the value found. + * + * @return PJ_SUCCESS if value is found, otherwise the error code. + */ +PJ_DECL(pj_status_t) +pj_array_find(const void *array, unsigned elem_size, unsigned count, pj_status_t (*matching)(const void *value), + void **result); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_ARRAY_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/assert.h b/src/tuya_p2p/pjproject/pjlib/include/pj/assert.h new file mode 100755 index 000000000..339a35a02 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/assert.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ASSERT_H__ +#define __PJ_ASSERT_H__ + +/** + * @file assert.h + * @brief Assertion macro pj_assert(). + */ + +#include +#include + +/** + * @defgroup pj_assert Assertion Macro + * @ingroup PJ_MISC + * @{ + * + * Assertion and other helper macros for sanity checking. + */ + +/** + * @hideinitializer + * Check during debug build that an expression is true. If the expression + * computes to false during run-time, then the program will stop at the + * offending statements. + * For release build, this macro will not do anything. + * + * @param expr The expression to be evaluated. + */ +#ifndef pj_assert +#define pj_assert(expr) assert(expr) +#endif + +/** + * @hideinitializer + * If the expression yields false, assertion will be triggered + * and the current function will return with the specified return value. + */ +// #if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK != 0 +#define PJ_ASSERT_RETURN(expr, retval) \ + do { \ + if (!(expr)) { \ + pj_assert(expr); \ + return retval; \ + } \ + } while (0) +//#else +//# define PJ_ASSERT_RETURN(expr,retval) pj_assert(expr) +//#endif + +/** + * @hideinitializer + * If the expression yields false, assertion will be triggered + * and @a exec_on_fail will be executed. + */ +//#if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK != 0 +#define PJ_ASSERT_ON_FAIL(expr, exec_on_fail) \ + do { \ + pj_assert(expr); \ + if (!(expr)) \ + exec_on_fail; \ + } while (0) +//#else +//# define PJ_ASSERT_ON_FAIL(expr,exec_on_fail) pj_assert(expr) +//#endif + +/** @} */ + +#endif /* __PJ_ASSERT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h new file mode 100755 index 000000000..eca1480c6 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_ASSERT_H__ +#define __PJ_COMPAT_ASSERT_H__ + +/** + * @file assert.h + * @brief Provides assert() macro. + */ + +#if defined(PJ_HAS_ASSERT_H) && PJ_HAS_ASSERT_H != 0 +#include + +#else +#warning "assert() is not implemented" +#define assert(expr) +#endif + +#endif /* __PJ_COMPAT_ASSERT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h new file mode 100755 index 000000000..75c7f7a68 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CC_ARMCC_H__ +#define __PJ_COMPAT_CC_ARMCC_H__ + +/** + * @file cc_armcc.h + * @brief Describes ARMCC compiler specifics. + */ + +#ifndef __ARMCC__ +#error "This file is only for armcc!" +#endif + +#define PJ_CC_NAME "armcc" +#define PJ_CC_VER_1 (__ARMCC_VERSION / 100000) +#define PJ_CC_VER_2 ((__ARMCC_VERSION % 100000) / 10000) +#define PJ_CC_VER_3 (__ARMCC_VERSION % 10000) + +#ifdef __cplusplus +#define PJ_INLINE_SPECIFIER inline +#else +#define PJ_INLINE_SPECIFIER static __inline +#endif + +#define PJ_THREAD_FUNC +#define PJ_NORETURN +#define PJ_ATTR_NORETURN __attribute__((noreturn)) +#define PJ_ATTR_MAY_ALIAS __attribute__((__may_alias__)) + +#define PJ_HAS_INT64 1 + +typedef long long pj_int64_t; +typedef unsigned long long pj_uint64_t; + +#define PJ_INT64_FMT "L" + +#define PJ_UNREACHED(x) + +#endif /* __PJ_COMPAT_CC_ARMCC_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h new file mode 100755 index 000000000..9faabef06 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CC_GCC_H__ +#define __PJ_COMPAT_CC_GCC_H__ + +/** + * @file cc_gcc.h + * @brief Describes GCC compiler specifics. + */ + +#ifndef __GNUC__ +#error "This file is only for gcc!" +#endif + +#define PJ_CC_NAME "gcc" +#define PJ_CC_VER_1 __GNUC__ +#define PJ_CC_VER_2 __GNUC_MINOR__ + +/* __GNUC_PATCHLEVEL__ doesn't exist in gcc-2.9x.x */ +#ifdef __GNUC_PATCHLEVEL__ +#define PJ_CC_VER_3 __GNUC_PATCHLEVEL__ +#else +#define PJ_CC_VER_3 0 +#endif + +#define PJ_THREAD_FUNC +#define PJ_NORETURN + +#define PJ_HAS_INT64 1 + +#ifdef __STRICT_ANSI__ +#include +typedef int64_t pj_int64_t; +typedef uint64_t pj_uint64_t; +#define PJ_INLINE_SPECIFIER static __inline +#define PJ_ATTR_NORETURN +#define PJ_ATTR_MAY_ALIAS +#else +typedef long long pj_int64_t; +typedef unsigned long long pj_uint64_t; +#define PJ_INLINE_SPECIFIER static inline +#define PJ_ATTR_NORETURN __attribute__((noreturn)) +#define PJ_ATTR_MAY_ALIAS __attribute__((__may_alias__)) +#endif + +#define PJ_INT64(val) val##LL +#define PJ_UINT64(val) val##ULL +#define PJ_INT64_FMT "L" + +#ifdef __GLIBC__ +#define PJ_HAS_BZERO 1 +#endif + +#define PJ_UNREACHED(x) + +#define PJ_ALIGN_DATA(declaration, alignment) declaration __attribute__((aligned(alignment))) + +#endif /* __PJ_COMPAT_CC_GCC_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h new file mode 100755 index 000000000..e3f127d19 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CC_GCCE_H__ +#define __PJ_COMPAT_CC_GCCE_H__ + +/** + * @file cc_gcce.h + * @brief Describes GCCE compiler specifics. + */ + +#ifndef __GCCE__ +#error "This file is only for gcce!" +#endif + +#define PJ_CC_NAME "gcce" +#define PJ_CC_VER_1 __GCCE__ +#define PJ_CC_VER_2 __GCCE_MINOR__ +#define PJ_CC_VER_3 __GCCE_PATCHLEVEL__ + +#define PJ_INLINE_SPECIFIER static inline +#define PJ_THREAD_FUNC +#define PJ_NORETURN +#define PJ_ATTR_NORETURN __attribute__((noreturn)) +#define PJ_ATTR_MAY_ALIAS __attribute__((__may_alias__)) + +#define PJ_HAS_INT64 1 + +typedef long long pj_int64_t; +typedef unsigned long long pj_uint64_t; + +#define PJ_INT64(val) val##LL +#define PJ_UINT64(val) val##LLU +#define PJ_INT64_FMT "L" + +#endif /* __PJ_COMPAT_CC_GCCE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h new file mode 100755 index 000000000..23e2edca8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CTYPE_H__ +#define __PJ_COMPAT_CTYPE_H__ + +/** + * @file ctype.h + * @brief Provides ctype function family. + */ + +#if defined(PJ_HAS_CTYPE_H) && PJ_HAS_CTYPE_H != 0 +#include +#else +#define isalnum(c) (isalpha(c) || isdigit(c)) +#define isalpha(c) (islower(c) || isupper(c)) +#define isascii(c) (((unsigned char)(c)) <= 0x7f) +#define isdigit(c) ((c) >= '0' && (c) <= '9') +#define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == '\v') +#define islower(c) ((c) >= 'a' && (c) <= 'z') +#define isupper(c) ((c) >= 'A' && (c) <= 'Z') +#define isxdigit(c) (isdigit(c) || (tolower(c) >= 'a' && tolower(c) <= 'f')) +#define tolower(c) (((c) >= 'A' && (c) <= 'Z') ? (c) + ('a' - 'A') : (c)) +#define toupper(c) (((c) >= 'a' && (c) <= 'z') ? (c) - ('a' - 'A') : (c)) +#endif + +#endif /* __PJ_COMPAT_CTYPE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h new file mode 100755 index 000000000..358c15274 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_ERRNO_H__ +#define __PJ_COMPAT_ERRNO_H__ + +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 || \ + defined(PJ_WIN64) && PJ_WIN64 != 0 + +typedef unsigned long pj_os_err_type; +#define pj_get_native_os_error() GetLastError() +#define pj_get_native_netos_error() WSAGetLastError() + +#elif defined(PJ_HAS_ERRNO_VAR) && PJ_HAS_ERRNO_VAR != 0 + +typedef int pj_os_err_type; +#define pj_get_native_os_error() (errno) +#define pj_get_native_netos_error() (errno) + +#else + +#error "Please define how to get errno for this platform here!" + +#endif + +#endif /* __PJ_COMPAT_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h new file mode 100755 index 000000000..9a3042017 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_HIGH_PRECISION_H__ +#define __PJ_COMPAT_HIGH_PRECISION_H__ + +//#define PJ_HAS_FLOATING_POINT 1 +#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT != 0 +/* + * The first choice for high precision math is to use double. + */ +#include +typedef double pj_highprec_t; + +#define PJ_HIGHPREC_VALUE_IS_ZERO(a) (a == 0) +#define pj_highprec_mod(a, b) (a = fmod(a, b)) + +#elif defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 +/* + * Next choice is to use 64-bit arithmatics. + */ +typedef pj_int64_t pj_highprec_t; + +#else +#warning "High precision math is not available" + +/* + * Last, fallback to 32-bit arithmetics. + */ +typedef pj_int32_t pj_highprec_t; + +#endif + +/** + * @def pj_highprec_mul + * pj_highprec_mul(a1, a2) - High Precision Multiplication + * Multiply a1 and a2, and store the result in a1. + */ +#ifndef pj_highprec_mul +#define pj_highprec_mul(a1, a2) (a1 = a1 * a2) +#endif + +/** + * @def pj_highprec_div + * pj_highprec_div(a1, a2) - High Precision Division + * Divide a2 from a1, and store the result in a1. + */ +#ifndef pj_highprec_div +#define pj_highprec_div(a1, a2) (a1 = a1 / a2) +#endif + +/** + * @def pj_highprec_mod + * pj_highprec_mod(a1, a2) - High Precision Modulus + * Get the modulus a2 from a1, and store the result in a1. + */ +#ifndef pj_highprec_mod +#define pj_highprec_mod(a1, a2) (a1 = a1 % a2) +#endif + +/** + * @def PJ_HIGHPREC_VALUE_IS_ZERO(a) + * Test if the specified high precision value is zero. + */ +#ifndef PJ_HIGHPREC_VALUE_IS_ZERO +#define PJ_HIGHPREC_VALUE_IS_ZERO(a) (a == 0) +#endif + +#endif /* __PJ_COMPAT_HIGH_PRECISION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h new file mode 100755 index 000000000..2bc78ec85 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2017 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_LIMITS_H__ +#define __PJ_COMPAT_LIMITS_H__ + +/** + * @file limits.h + * @brief Provides integer limits normally found in limits.h. + */ + +#include + +#if defined(PJ_HAS_LIMITS_H) && PJ_HAS_LIMITS_H != 0 +#include +#else + +#ifdef _MSC_VER +#pragma message("limits.h is not found or not supported. LONG_MIN and " \ + "LONG_MAX will be defined by the library in " \ + "pj/compats/limits.h and overridable in config_site.h") +#else +// #warning "limits.h is not found or not supported. LONG_MIN and LONG_MAX " \ +// "will be defined by the library in pj/compats/limits.h and "\ +// "overridable in config_site.h" +#endif + +/* Minimum and maximum values a `signed long int' can hold. */ +#ifndef LONG_MAX +#if __WORDSIZE == 64 +#define LONG_MAX 9223372036854775807L +#else +#define LONG_MAX 2147483647L +#endif +#endif + +#ifndef LONG_MIN +#define LONG_MIN (-LONG_MAX - 1L) +#endif + +/* Maximum value an `unsigned long int' can hold. (Minimum is 0.) */ +#ifndef ULONG_MAX +#if __WORDSIZE == 64 +#define ULONG_MAX 18446744073709551615UL +#else +#define ULONG_MAX 4294967295UL +#endif +#endif +#endif + +#endif /* __PJ_COMPAT_LIMITS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in new file mode 100755 index 000000000..d5a9ce903 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_M_AUTO_H__ +#define __PJ_COMPAT_M_AUTO_H__ + +/** + * @file m_auto.h + * @brief Automatically generated process definition file. + */ + +/* Machine name, filled in by autoconf script */ +#undef PJ_M_NAME + +/* Endianness. It's reported on pjsip list on 09/02/13 that autoconf + * endianness detection failed for universal build, so special case + * for it here. Thanks Ruud Klaver for the fix. + */ +#ifdef PJ_DARWINOS +# ifdef __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else + /* Endianness, as detected by autoconf */ +# undef WORDS_BIGENDIAN +#endif + +#ifdef WORDS_BIGENDIAN +# define PJ_IS_LITTLE_ENDIAN 0 +# define PJ_IS_BIG_ENDIAN 1 +#else +# define PJ_IS_LITTLE_ENDIAN 1 +# define PJ_IS_BIG_ENDIAN 0 +#endif + + +/* Specify if floating point is present/desired */ +#undef PJ_HAS_FLOATING_POINT + +/* Deprecated */ +#define PJ_HAS_PENTIUM 0 + +#endif /* __PJ_COMPAT_M_AUTO_H__ */ + diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h new file mode 100755 index 000000000..c0810dc8d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_M_x86_64_H__ +#define __PJ_COMPAT_M_x86_64_H__ + +/** + * @file m_i386.h + * @brief Describes 64bit x86 Intel/AMD family processor specifics. + */ + +#define PJ_M_NAME "x86_64" + +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#endif /* __PJ_COMPAT_M_x86_64_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h new file mode 100755 index 000000000..1eabf0beb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_MALLOC_H__ +#define __PJ_COMPAT_MALLOC_H__ + +/** + * @file malloc.h + * @brief Provides malloc() and free() functions. + */ + +#if defined(PJ_HAS_MALLOC_H) && PJ_HAS_MALLOC_H != 0 +#include +#elif defined(PJ_HAS_STDLIB_H) && PJ_HAS_STDLIB_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_MALLOC_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in new file mode 100755 index 000000000..b37b1aef0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_OS_AUTO_H__ +#define __PJ_COMPAT_OS_AUTO_H__ + +/** + * @file os_auto.h + * @brief Describes operating system specifics (automatically detected by + * autoconf) + */ + +/* Canonical OS name */ +#undef PJ_OS_NAME + +/* Legacy macros */ +#undef PJ_WIN64 +#undef PJ_WIN32 +#undef PJ_WIN32_WINNT +#undef WIN32_LEAN_AND_MEAN +#undef PJ_DARWINOS +#undef PJ_LINUX +#undef PJ_BSD +#undef PJ_RTEMS +#undef PJ_SUNOS +#undef PJ_ANDROID + +#if defined(PJ_WIN32_WINNT) && !defined(_WIN32_WINNT) +# define _WIN32_WINNT PJ_WIN32_WINNT +#endif + +/* Headers availability */ +#undef PJ_HAS_ARPA_INET_H +#undef PJ_HAS_ASSERT_H +#undef PJ_HAS_CTYPE_H +#undef PJ_HAS_ERRNO_H +#undef PJ_HAS_FCNTL_H +#undef PJ_HAS_LIMITS_H +#undef PJ_HAS_LINUX_SOCKET_H +#undef PJ_HAS_MALLOC_H +#undef PJ_HAS_NETDB_H +#undef PJ_HAS_NETINET_IN_SYSTM_H +#undef PJ_HAS_NETINET_IN_H +#undef PJ_HAS_NETINET_IP_H +#undef PJ_HAS_NETINET_TCP_H +#undef PJ_HAS_NET_IF_H +#undef PJ_HAS_IFADDRS_H +#undef PJ_HAS_SEMAPHORE_H +#undef PJ_HAS_SETJMP_H +#undef PJ_HAS_STDARG_H +#undef PJ_HAS_STDDEF_H +#undef PJ_HAS_STDIO_H +#undef PJ_HAS_STDINT_H +#undef PJ_HAS_STDLIB_H +#undef PJ_HAS_STRING_H +#undef PJ_HAS_SYS_IOCTL_H +#undef PJ_HAS_SYS_SELECT_H +#undef PJ_HAS_SYS_SOCKET_H +#undef PJ_HAS_SYS_TIME_H +#undef PJ_HAS_SYS_TIMEB_H +#undef PJ_HAS_SYS_TYPES_H +#undef PJ_HAS_SYS_FILIO_H +#undef PJ_HAS_SYS_SOCKIO_H +#undef PJ_HAS_SYS_UTSNAME_H +#undef PJ_HAS_TIME_H +#undef PJ_HAS_UNISTD_H +#undef PJ_HAS_EXECINFO_H + +#undef PJ_HAS_MSWSOCK_H +#undef PJ_HAS_WINSOCK_H +#undef PJ_HAS_WINSOCK2_H +#undef PJ_HAS_WS2TCPIP_H + +#undef PJ_SOCK_HAS_IPV6_V6ONLY +#undef PJ_SOCK_HAS_INET_ATON +#undef PJ_SOCK_HAS_INET_PTON +#undef PJ_SOCK_HAS_INET_NTOP +#undef PJ_SOCK_HAS_GETADDRINFO +#undef PJ_SOCK_HAS_SOCKETPAIR + +/* On these OSes, semaphore feature depends on semaphore.h */ +#if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H!=0 +# define PJ_HAS_SEMAPHORE 1 +#elif defined(PJ_WIN32) && PJ_WIN32!=0 +# define PJ_HAS_SEMAPHORE 1 +#else +# define PJ_HAS_SEMAPHORE 0 +#endif + +/* Do we have pthread_mutexattr_settype()? */ +#undef PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE + +/* Does pthread_mutexattr_t has "recursive" member? */ +#undef PJ_PTHREAD_MUTEXATTR_T_HAS_RECURSIVE + +/* Set 1 if native sockaddr_in has sin_len member. + * Default: 0 + */ +#undef PJ_SOCKADDR_HAS_LEN + +/* Does the OS have socklen_t? */ +#undef PJ_HAS_SOCKLEN_T + +#if !defined(socklen_t) && (!defined(PJ_HAS_SOCKLEN_T) || PJ_HAS_SOCKLEN_T==0) +# define PJ_HAS_SOCKLEN_T 1 + typedef int socklen_t; +#endif + +/** + * If this macro is set, it tells select I/O Queue that select() needs to + * be given correct value of nfds (i.e. largest fd + 1). This requires + * select ioqueue to re-scan the descriptors on each registration and + * unregistration. + * If this macro is not set, then ioqueue will always give FD_SETSIZE for + * nfds argument when calling select(). + * + * Default: 0 + */ +#undef PJ_SELECT_NEEDS_NFDS + +/* Was Linux epoll support enabled */ +#undef PJ_HAS_LINUX_EPOLL + +/* Is errno a good way to retrieve OS errors? + */ +#undef PJ_HAS_ERRNO_VAR + +/* When this macro is set, getsockopt(SOL_SOCKET, SO_ERROR) will return + * the status of non-blocking connect() operation. + */ +#undef PJ_HAS_SO_ERROR + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket recv() can not return immediate daata. + */ +#undef PJ_BLOCKING_ERROR_VAL + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket connect() can not get connected immediately. + */ +#undef PJ_BLOCKING_CONNECT_ERROR_VAL + +/* Default threading is enabled, unless it's overridden. */ +#ifndef PJ_HAS_THREADS +# define PJ_HAS_THREADS (1) +#endif + +/* Do we need high resolution timer? */ +#undef PJ_HAS_HIGH_RES_TIMER + +/* Is malloc() available? */ +#undef PJ_HAS_MALLOC + +#ifndef PJ_OS_HAS_CHECK_STACK +# define PJ_OS_HAS_CHECK_STACK 0 +#endif + +/* Is localtime_r() available? */ +#undef PJ_HAS_LOCALTIME_R + +/* Unicode? */ +#undef PJ_NATIVE_STRING_IS_UNICODE + +/* Pool alignment in bytes */ +#undef PJ_POOL_ALIGNMENT + +/* The type of atomic variable value: */ +#undef PJ_ATOMIC_VALUE_TYPE + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + /* Disable local host resolution in pj_gethostip() (see ticket #1342) */ +# define PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION 1 + /* Use pj_getaddrinfo() (instead of pj_inet_pton()) in + * pj_sockaddr_set_str_addr() + */ +# define PJ_SOCKADDR_USE_GETADDRINFO 1 + +# include "TargetConditionals.h" +# if TARGET_OS_IPHONE +# include "Availability.h" + /* Use CFHost API for pj_getaddrinfo() (see ticket #1246) */ +# ifndef PJ_GETADDRINFO_USE_CFHOST +# define PJ_GETADDRINFO_USE_CFHOST 0 +# endif +# ifdef __IPHONE_4_0 + /* Is multitasking support available? (see ticket #1107) */ +# define PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT 1 + /* Activesock TCP background mode support (VoIP socket). + * Disabled by default, VoIP socket deprecated since iOS 9 and + * on iOS16 using VoIP socket causes app getting killed. + */ +# define PJ_ACTIVESOCK_TCP_IPHONE_OS_BG 0 +# endif +# endif +#endif + +/* If 1, use Read/Write mutex emulation for platforms that don't support it */ +#undef PJ_EMULATE_RWMUTEX + +/* If 1, pj_thread_create() should enforce the stack size when creating + * threads. + * Default: 0 (let OS decide the thread's stack size). + */ +#undef PJ_THREAD_SET_STACK_SIZE + +/* If 1, pj_thread_create() should allocate stack from the pool supplied. + * Default: 0 (let OS allocate memory for thread's stack). + */ +#undef PJ_THREAD_ALLOCATE_STACK + +/* SSL socket availability. */ +#ifndef PJ_HAS_SSL_SOCK +#undef PJ_HAS_SSL_SOCK +#endif +#ifndef PJ_SSL_SOCK_IMP +#undef PJ_SSL_SOCK_IMP +#endif + +/* Has pthread_np.h ? */ +#undef PJ_HAS_PTHREAD_NP_H +/* Has pthread_setname_np() ? */ +#undef PJ_HAS_PTHREAD_SETNAME_NP +/* Has pthread_set_name_np() ? */ +#undef PJ_HAS_PTHREAD_SET_NAME_NP + + +#endif /* __PJ_COMPAT_OS_AUTO_H__ */ + diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h new file mode 100755 index 000000000..4ece115be --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_OS_LINUX_H__ +#define __PJ_COMPAT_OS_LINUX_H__ + +/** + * @file os_linux.h + * @brief Describes Linux operating system specifics. + */ +#include +#include +#define PJ_OS_NAME "linux" + +#define PJ_HAS_ARPA_INET_H 1 +#define PJ_HAS_ASSERT_H 1 +#define PJ_HAS_CTYPE_H 1 +#define PJ_HAS_ERRNO_H 1 +#define PJ_HAS_LINUX_SOCKET_H 0 +#define PJ_HAS_MALLOC_H 1 +#define PJ_HAS_NETDB_H 1 +#define PJ_HAS_NETINET_IN_H 1 +#define PJ_HAS_SETJMP_H 1 +#define PJ_HAS_STDARG_H 1 +#define PJ_HAS_STDDEF_H 1 +#define PJ_HAS_STDIO_H 1 +#define PJ_HAS_STDLIB_H 1 +#define PJ_HAS_STRING_H 1 +#define PJ_HAS_SYS_IOCTL_H 1 +#define PJ_HAS_SYS_SELECT_H 1 +#define PJ_HAS_SYS_SOCKET_H 1 +#define PJ_HAS_SYS_TIME_H 1 +#define PJ_HAS_SYS_TIMEB_H 1 +#define PJ_HAS_SYS_TYPES_H 1 +#define PJ_HAS_TIME_H 1 +#define PJ_HAS_UNISTD_H 1 +#define PJ_HAS_SEMAPHORE_H 1 + +#define PJ_HAS_MSWSOCK_H 0 +#define PJ_HAS_WINSOCK_H 0 +#define PJ_HAS_WINSOCK2_H 0 + +#define PJ_HAS_LOCALTIME_R 1 + +#define PJ_SOCK_HAS_INET_ATON 1 +#define PJ_SOCK_HAS_INET_NTOP 1 + +/* Set 1 if native sockaddr_in has sin_len member. + * Default: 0 + */ +#define PJ_SOCKADDR_HAS_LEN 0 + +/** + * If this macro is set, it tells select I/O Queue that select() needs to + * be given correct value of nfds (i.e. largest fd + 1). This requires + * select ioqueue to re-scan the descriptors on each registration and + * unregistration. + * If this macro is not set, then ioqueue will always give FD_SETSIZE for + * nfds argument when calling select(). + * + * Default: 0 + */ +#define PJ_SELECT_NEEDS_NFDS 0 + +/* Is errno a good way to retrieve OS errors? + */ +#define PJ_HAS_ERRNO_VAR 1 + +/* When this macro is set, getsockopt(SOL_SOCKET, SO_ERROR) will return + * the status of non-blocking connect() operation. + */ +#define PJ_HAS_SO_ERROR 1 + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket recv() can not return immediate daata. + */ +#define PJ_BLOCKING_ERROR_VAL EAGAIN + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket connect() can not get connected immediately. + */ +#define PJ_BLOCKING_CONNECT_ERROR_VAL EINPROGRESS + +/* Default threading is enabled, unless it's overridden. */ +#ifndef PJ_HAS_THREADS +#define PJ_HAS_THREADS (1) +#endif + +#define PJ_HAS_HIGH_RES_TIMER 1 +#define PJ_HAS_MALLOC 1 +#ifndef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#endif +#define PJ_NATIVE_STRING_IS_UNICODE 0 + +#define PJ_ATOMIC_VALUE_TYPE long + +/* If 1, use Read/Write mutex emulation for platforms that don't support it */ +#define PJ_EMULATE_RWMUTEX 0 + +/* If 1, pj_thread_create() should enforce the stack size when creating + * threads. + * Default: 0 (let OS decide the thread's stack size). + */ +#define PJ_THREAD_SET_STACK_SIZE 0 + +/* If 1, pj_thread_create() should allocate stack from the pool supplied. + * Default: 0 (let OS allocate memory for thread's stack). + */ +#define PJ_THREAD_ALLOCATE_STACK 0 + +/* Linux has socklen_t */ +#define PJ_HAS_SOCKLEN_T 1 + +#endif /* __PJ_COMPAT_OS_LINUX_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h new file mode 100755 index 000000000..0bd9c3515 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_RAND_H__ +#define __PJ_COMPAT_RAND_H__ + +/** + * @file rand.h + * @brief Provides platform_rand() and platform_srand() functions. + */ + +#if defined(PJ_HAS_STDLIB_H) && PJ_HAS_STDLIB_H != 0 +/* + * Use stdlib based rand() and srand(). + */ +#include +#define platform_srand srand +#if defined(RAND_MAX) && RAND_MAX <= 0xFFFF +/* + * When rand() is only 16 bit strong, double the strength + * by calling it twice! + */ +PJ_INLINE(int) platform_rand(void) +{ + return ((rand() & 0xFFFF) << 16) | (rand() & 0xFFFF); +} +#else +#define platform_rand rand +#endif + +#else +#warning "platform_rand() is not implemented" +#define platform_rand() 1 +#define platform_srand(seed) + +#endif + +#endif /* __PJ_COMPAT_RAND_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h new file mode 100755 index 000000000..16dc81ba3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_SETJMP_H__ +#define __PJ_COMPAT_SETJMP_H__ + +/** + * @file setjmp.h + * @brief Provides setjmp.h functionality. + */ + +#if defined(PJ_HAS_SETJMP_H) && PJ_HAS_SETJMP_H != 0 +#include +typedef jmp_buf pj_jmp_buf; +#ifndef pj_setjmp +#define pj_setjmp(buf) setjmp(buf) +#endif +#ifndef pj_longjmp +#define pj_longjmp(buf, d) longjmp(buf, d) +#endif + +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +/* Symbian framework don't use setjmp/longjmp */ + +#else +#warning "setjmp()/longjmp() is not implemented" +typedef int pj_jmp_buf[1]; +#define pj_setjmp(buf) 0 +#define pj_longjmp(buf, d) 0 +#endif + +#endif /* __PJ_COMPAT_SETJMP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h new file mode 100755 index 000000000..ee3a8c3e1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_SIZE_T_H__ +#define __PJ_COMPAT_SIZE_T_H__ + +/** + * @file size_t.h + * @brief Provides size_t type. + */ +#if PJ_HAS_STDDEF_H +#include +#endif + +#endif /* __PJ_COMPAT_SIZE_T_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h new file mode 100755 index 000000000..d419d047a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_SOCKET_H__ +#define __PJ_COMPAT_SOCKET_H__ + +/** + * @file socket.h + * @brief Provides all socket related functions,data types, error codes, etc. + */ + +#if defined(PJ_HAS_WINSOCK2_H) && PJ_HAS_WINSOCK2_H != 0 +#include +#endif + +#if defined(PJ_HAS_WINSOCK_H) && PJ_HAS_WINSOCK_H != 0 +#include +#endif + +#if defined(PJ_HAS_WS2TCPIP_H) && PJ_HAS_WS2TCPIP_H != 0 +#include +#endif + +#if (defined(PJ_WIN32_UWP) && PJ_WIN32_UWP != 0) +#include +#endif + +/* + * IPv6 for Visual Studio's + * + * = Visual Studio 6 = + * + * Visual Studio 6 does not ship with IPv6 support, so you MUST + * download and install IPv6 Tehnology Preview (IPv6Kit) from: + * http://msdn.microsoft.com/downloads/sdks/platform/tpipv6/ReadMe.asp + * Then put IPv6Kit\inc in your Visual Studio include path. + * + * In addition, by default IPv6Kit does not want to install on + * Windows 2000 SP4. Please see: + * http://msdn.microsoft.com/downloads/sdks/platform/tpipv6/faq.asp + * on how to install IPv6Kit on Win2K SP4. + * + * + * = Visual Studio 2003, 2005 (including Express) = + * + * These VS uses Microsoft Platform SDK for Windows Server 2003 SP1, and + * it has built-in IPv6 support. + */ +#if defined(_MSC_VER) && defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 != 0 +#ifndef s_addr +#define s_addr S_un.S_addr +#endif + +#if !defined(IPPROTO_IPV6) && (_WIN32_WINNT == 0x0500) +/* Need to download and install IPv6Kit for this platform. + * Please see the comments above about Visual Studio 6. + */ +#include +#endif + +#define PJ_SOCK_HAS_GETADDRINFO 1 +#endif /* _MSC_VER */ + +#if defined(PJ_HAS_SYS_TYPES_H) && PJ_HAS_SYS_TYPES_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_SOCKET_H) && PJ_HAS_SYS_SOCKET_H != 0 +#include +#endif + +#if defined(PJ_HAS_LINUX_SOCKET_H) && PJ_HAS_LINUX_SOCKET_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_SELECT_H) && PJ_HAS_SYS_SELECT_H != 0 +#include +#endif + +#if defined(PJ_HAS_NETINET_IN_H) && PJ_HAS_NETINET_IN_H != 0 +#include +#endif + +#if defined(PJ_HAS_NETINET_IN_SYSTM_H) && PJ_HAS_NETINET_IN_SYSTM_H != 0 +/* Required to include netinet/ip.h in FreeBSD 7.0 */ +#include +#endif + +#if defined(PJ_HAS_NETINET_IP_H) && PJ_HAS_NETINET_IP_H != 0 +/* To pull in IPTOS_* constants */ +#include +#endif + +#if defined(PJ_HAS_NETINET_TCP_H) && PJ_HAS_NETINET_TCP_H != 0 +/* To pull in TCP_NODELAY constants */ +#include +#endif + +#if defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 +/* For interface enumeration in ip_helper */ +#include +#endif + +#if defined(PJ_HAS_IFADDRS_H) && PJ_HAS_IFADDRS_H != 0 +/* Interface enum with getifaddrs() which works with IPv6 */ +#include +#endif + +#if defined(PJ_HAS_ARPA_INET_H) && PJ_HAS_ARPA_INET_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_IOCTL_H) && PJ_HAS_SYS_IOCTL_H != 0 +#include /* FBIONBIO */ +#endif + +#if defined(PJ_HAS_ERRNO_H) && PJ_HAS_ERRNO_H != 0 +#include +#endif + +#if defined(PJ_HAS_NETDB_H) && PJ_HAS_NETDB_H != 0 +#include +#endif + +#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_FILIO_H) && PJ_HAS_SYS_FILIO_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_SOCKIO_H) && PJ_HAS_SYS_SOCKIO_H != 0 +#include +#endif + +/* + * Define common errors. + */ +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0) || \ + (defined(PJ_WIN64) && PJ_WIN64 != 0) +#define OSERR_EWOULDBLOCK WSAEWOULDBLOCK +#define OSERR_EINPROGRESS WSAEINPROGRESS +#define OSERR_ECONNRESET WSAECONNRESET +#define OSERR_ENOTCONN WSAENOTCONN +#define OSERR_EAFNOSUPPORT WSAEAFNOSUPPORT +#define OSERR_ENOPROTOOPT WSAENOPROTOOPT +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +#define OSERR_EWOULDBLOCK -1 +#define OSERR_EINPROGRESS -1 +#define OSERR_ECONNRESET -1 +#define OSERR_ENOTCONN -1 +#define OSERR_EAFNOSUPPORT -1 +#define OSERR_ENOPROTOOPT -1 +#else +#define OSERR_EWOULDBLOCK EWOULDBLOCK +#define OSERR_EINPROGRESS EINPROGRESS +#define OSERR_ECONNRESET ECONNRESET +#define OSERR_ENOTCONN ENOTCONN +#define OSERR_EAFNOSUPPORT EAFNOSUPPORT +#define OSERR_ENOPROTOOPT ENOPROTOOPT +#endif + +/* + * And undefine these.. + * Note (see issue #2311): unfortunately, this may cause build failure + * to anyone who uses these standard macros. + */ +//#undef s_addr +//#undef s6_addr +//#undef sin_zero + +/* + * This will finally be obsoleted, since it should be declared in + * os_auto.h + */ +#if !defined(PJ_HAS_SOCKLEN_T) || PJ_HAS_SOCKLEN_T == 0 +typedef int socklen_t; +#endif + +/* Regarding sin_len member of sockaddr_in: + * BSD systems (including MacOS X requires that the sin_len member of + * sockaddr_in be set to sizeof(sockaddr_in), while other systems (Windows + * and Linux included) do not. + * + * To maintain compatibility between systems, PJLIB will automatically + * set this field before invoking native OS socket API, and it will + * always reset the field to zero before returning pj_sockaddr_in to + * application (such as in pj_getsockname() and pj_recvfrom()). + * + * Application MUST always set this field to zero. + * + * This way we can avoid hard to find problem such as when the socket + * address is used as hash table key. + */ +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 +#define PJ_SOCKADDR_SET_LEN(addr, len) (((pj_addr_hdr *)(addr))->sa_zero_len = (len)) +#define PJ_SOCKADDR_RESET_LEN(addr) (((pj_addr_hdr *)(addr))->sa_zero_len = 0) +#else +#define PJ_SOCKADDR_SET_LEN(addr, len) +#define PJ_SOCKADDR_RESET_LEN(addr) +#endif + +#endif /* __PJ_COMPAT_SOCKET_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h new file mode 100755 index 000000000..333fe18a3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_STDARG_H__ +#define __PJ_COMPAT_STDARG_H__ + +/** + * @file stdarg.h + * @brief Provides stdarg functionality. + */ + +#if defined(PJ_HAS_STDARG_H) && PJ_HAS_STDARG_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_STDARG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h new file mode 100755 index 000000000..bc8b0cfd3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_STDFILEIO_H__ +#define __PJ_COMPAT_STDFILEIO_H__ + +/** + * @file stdfileio.h + * @brief Compatibility for ANSI file I/O like fputs, fflush, etc. + */ + +#if defined(PJ_HAS_STDIO_H) && PJ_HAS_STDIO_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_STDFILEIO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h new file mode 100755 index 000000000..cb0cf6089 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_STRING_H__ +#define __PJ_COMPAT_STRING_H__ + +/** + * @file string.h + * @brief Provides string manipulation functions found in ANSI string.h. + */ + +#if defined(PJ_HAS_STRING_H) && PJ_HAS_STRING_H != 0 +#include +#else + +PJ_DECL(int) strcasecmp(const char *s1, const char *s2); +PJ_DECL(int) strncasecmp(const char *s1, const char *s2, int len); + +#endif + +/* For sprintf family */ +#include + +/* On WinCE, string stuffs are declared in stdlib.h */ +#if defined(PJ_HAS_STDLIB_H) && PJ_HAS_STDLIB_H != 0 +#include +#endif + +#if defined(_MSC_VER) +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +/* snprintf() and vsnprintf() are available since Visual Studio 2015 */ +#if _MSC_VER < 1900 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +#define snwprintf _snwprintf +#define wcsicmp _wcsicmp +#define wcsnicmp _wcsnicmp +#else +#define stricmp strcasecmp +#define strnicmp strncasecmp + +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +#error "Implement Unicode string functions" +#endif +#endif + +#define pj_ansi_strcmp strcmp +#define pj_ansi_strncmp strncmp +#define pj_ansi_strlen strlen +#define pj_ansi_strcpy strcpy +#define pj_ansi_strncpy strncpy +#define pj_ansi_strcat strcat +#define pj_ansi_strstr strstr +#define pj_ansi_strchr strchr +#define pj_ansi_strcasecmp strcasecmp +#define pj_ansi_stricmp strcasecmp +#define pj_ansi_strncasecmp strncasecmp +#define pj_ansi_strnicmp strncasecmp +#define pj_ansi_sprintf sprintf + +#if defined(PJ_HAS_NO_SNPRINTF) && PJ_HAS_NO_SNPRINTF != 0 +#include +#include +PJ_BEGIN_DECL +PJ_DECL(int) snprintf(char *s1, pj_size_t len, const char *s2, ...); +PJ_DECL(int) vsnprintf(char *s1, pj_size_t len, const char *s2, va_list arg); +PJ_END_DECL +#endif + +#define pj_ansi_snprintf snprintf +#define pj_ansi_vsprintf vsprintf +#define pj_ansi_vsnprintf vsnprintf + +#define pj_unicode_strcmp wcscmp +#define pj_unicode_strncmp wcsncmp +#define pj_unicode_strlen wcslen +#define pj_unicode_strcpy wcscpy +#define pj_unicode_strncpy wcsncpy +#define pj_unicode_strcat wcscat +#define pj_unicode_strstr wcsstr +#define pj_unicode_strchr wcschr +#define pj_unicode_strcasecmp wcsicmp +#define pj_unicode_stricmp wcsicmp +#define pj_unicode_strncasecmp wcsnicmp +#define pj_unicode_strnicmp wcsnicmp +#define pj_unicode_sprintf swprintf +#define pj_unicode_snprintf snwprintf +#define pj_unicode_vsprintf vswprintf +#define pj_unicode_vsnprintf vsnwprintf + +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +#define pj_native_strcmp pj_unicode_strcmp +#define pj_native_strncmp pj_unicode_strncmp +#define pj_native_strlen pj_unicode_strlen +#define pj_native_strcpy pj_unicode_strcpy +#define pj_native_strncpy pj_unicode_strncpy +#define pj_native_strcat pj_unicode_strcat +#define pj_native_strstr pj_unicode_strstr +#define pj_native_strchr pj_unicode_strchr +#define pj_native_strcasecmp pj_unicode_strcasecmp +#define pj_native_stricmp pj_unicode_stricmp +#define pj_native_strncasecmp pj_unicode_strncasecmp +#define pj_native_strnicmp pj_unicode_strnicmp +#define pj_native_sprintf pj_unicode_sprintf +#define pj_native_snprintf pj_unicode_snprintf +#define pj_native_vsprintf pj_unicode_vsprintf +#define pj_native_vsnprintf pj_unicode_vsnprintf +#else +#define pj_native_strcmp pj_ansi_strcmp +#define pj_native_strncmp pj_ansi_strncmp +#define pj_native_strlen pj_ansi_strlen +#define pj_native_strcpy pj_ansi_strcpy +#define pj_native_strncpy pj_ansi_strncpy +#define pj_native_strcat pj_ansi_strcat +#define pj_native_strstr pj_ansi_strstr +#define pj_native_strchr pj_ansi_strchr +#define pj_native_strcasecmp pj_ansi_strcasecmp +#define pj_native_stricmp pj_ansi_stricmp +#define pj_native_strncasecmp pj_ansi_strncasecmp +#define pj_native_strnicmp pj_ansi_strnicmp +#define pj_native_sprintf pj_ansi_sprintf +#define pj_native_snprintf pj_ansi_snprintf +#define pj_native_vsprintf pj_ansi_vsprintf +#define pj_native_vsnprintf pj_ansi_vsnprintf +#endif + +#endif /* __PJ_COMPAT_STRING_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h new file mode 100755 index 000000000..6fcfd166a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_TIME_H__ +#define __PJ_COMPAT_TIME_H__ + +/** + * @file time.h + * @brief Provides ftime() and localtime() etc functions. + */ + +#if defined(PJ_HAS_TIME_H) && PJ_HAS_TIME_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_TIME_H) && PJ_HAS_SYS_TIME_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_TIMEB_H) && PJ_HAS_SYS_TIMEB_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_TIME_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config.h new file mode 100755 index 000000000..4f6151371 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config.h @@ -0,0 +1,1393 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_CONFIG_H__ +#define __PJ_CONFIG_H__ + +/** + * @file config.h + * @brief PJLIB Main configuration settings. + */ + +/******************************************************************** + * Include compiler specific configuration. + */ +#include +// #if defined(_MSC_VER) +// # include +// #elif defined(__GNUC__) +// # include +// #elif defined(__CW32__) +// # include +// #elif defined(__MWERKS__) +// # include +// #elif defined(__GCCE__) +// # include +// #elif defined(__ARMCC__) +// # include +// #else +// # error "Unknown compiler." +// #endif + +/* PJ_ALIGN_DATA is compiler specific directive to align data address */ +#ifndef PJ_ALIGN_DATA +#error "PJ_ALIGN_DATA is not defined!" +#endif + +/******************************************************************** + * Include target OS specific configuration. + */ +#if defined(PJ_AUTOCONF) +/* + * Autoconf + */ +#include + +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +/* + * SymbianOS + */ +#include + +#elif defined(PJ_WIN32_WINCE) || defined(_WIN32_WCE) || defined(UNDER_CE) +/* + * Windows CE + */ +#undef PJ_WIN32_WINCE +#define PJ_WIN32_WINCE 1 +#include + +/* Also define Win32 */ +#define PJ_WIN32 1 + +#elif defined(PJ_WIN32_WINPHONE8) || defined(_WIN32_WINPHONE8) +/* + * Windows Phone 8 + */ +#undef PJ_WIN32_WINPHONE8 +#define PJ_WIN32_WINPHONE8 1 +#include + +/* Also define Win32 */ +#define PJ_WIN32 1 + +#elif defined(PJ_WIN32_UWP) || defined(_WIN32_UWP) +/* + * Windows UWP + */ +#undef PJ_WIN32_UWP +#define PJ_WIN32_UWP 1 +#include + +/* Define Windows phone */ +#define PJ_WIN32_WINPHONE8 1 + +/* Also define Win32 */ +#define PJ_WIN32 1 + +#elif defined(PJ_WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(PJ_WIN64) || \ + defined(_WIN64) || defined(WIN64) || defined(__TOS_WIN__) +#if defined(PJ_WIN64) || defined(_WIN64) || defined(WIN64) +/* + * Win64 + */ +#undef PJ_WIN64 +#define PJ_WIN64 1 +#endif +#undef PJ_WIN32 +#define PJ_WIN32 1 +#include + +#elif defined(PJ_LINUX) || defined(linux) || defined(__linux) +/* + * Linux + */ +#undef PJ_LINUX +#define PJ_LINUX 1 +#include + +#elif defined(PJ_PALMOS) && PJ_PALMOS != 0 +/* + * Palm + */ +#include + +#elif defined(PJ_SUNOS) || defined(sun) || defined(__sun) +/* + * SunOS + */ +#undef PJ_SUNOS +#define PJ_SUNOS 1 +#include + +#elif defined(PJ_DARWINOS) || defined(__MACOSX__) || defined(__APPLE__) || defined(__MACH__) +/* + * MacOS X + */ +#undef PJ_DARWINOS +#define PJ_DARWINOS 1 +#include + +#elif defined(PJ_RTEMS) && PJ_RTEMS != 0 +/* + * RTEMS + */ +#include +#else +#error "Please specify target os." +#endif + +/******************************************************************** + * Target machine specific configuration. + */ +#if defined(PJ_AUTOCONF) +/* + * Autoconf configured + */ +#include + +#elif defined(PJ_M_I386) || defined(_i386_) || defined(i_386_) || defined(_X86_) || defined(x86) || \ + defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(__I86__) +/* + * Generic i386 processor family, little-endian + */ +#undef PJ_M_I386 +#define PJ_M_I386 1 +#define PJ_M_NAME "i386" +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_X86_64) || defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(_M_X64) || defined(_M_AMD64) +/* + * AMD 64bit processor, little endian + */ +#undef PJ_M_X86_64 +#define PJ_M_X86_64 1 +#define PJ_M_NAME "x86_64" +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_IA64) || defined(__ia64__) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) +/* + * Intel IA64 processor, default to little endian + */ +#undef PJ_M_IA64 +#define PJ_M_IA64 1 +#define PJ_M_NAME "ia64" +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_M68K) && PJ_M_M68K != 0 + +/* + * Motorola m68k processor, big endian + */ +#undef PJ_M_M68K +#define PJ_M_M68K 1 +#define PJ_M_NAME "m68k" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 0 +#define PJ_IS_BIG_ENDIAN 1 + +#elif defined(PJ_M_ALPHA) || defined(__alpha__) || defined(__alpha) || defined(_M_ALPHA) +/* + * DEC Alpha processor, little endian + */ +#undef PJ_M_ALPHA +#define PJ_M_ALPHA 1 +#define PJ_M_NAME "alpha" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_MIPS) || defined(__mips__) || defined(__mips) || defined(__MIPS__) || defined(MIPS) || \ + defined(_MIPS_) +/* + * MIPS, bi-endian, so raise error if endianness is not configured + */ +#undef PJ_M_MIPS +#define PJ_M_MIPS 1 +#define PJ_M_NAME "mips" +#define PJ_HAS_PENTIUM 0 +#if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +#error Endianness must be declared for this processor +#endif + +#elif defined(PJ_M_SPARC) || defined(__sparc__) || defined(__sparc) +/* + * Sun Sparc, big endian + */ +#undef PJ_M_SPARC +#define PJ_M_SPARC 1 +#define PJ_M_NAME "sparc" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 0 +#define PJ_IS_BIG_ENDIAN 1 + +#elif defined(ARM) || defined(_ARM_) || defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) || defined(__aarch64__) +#define PJ_HAS_PENTIUM 0 +/* + * ARM, bi-endian, so raise error if endianness is not configured + */ +#if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +#error Endianness must be declared for this processor +#endif +#if defined(PJ_M_ARMV7) || defined(ARMV7) +#undef PJ_M_ARMV7 +#define PJ_M_ARM7 1 +#define PJ_M_NAME "armv7" +#elif defined(PJ_M_ARMV4) || defined(ARMV4) +#undef PJ_M_ARMV4 +#define PJ_M_ARMV4 1 +#define PJ_M_NAME "armv4" +#elif defined(PJ_M_ARM64) || defined(ARM64) || defined(__aarch64__) +#undef PJ_M_ARM64 +#define PJ_M_ARM64 1 +#define PJ_M_NAME "arm64" +#endif + +#elif defined(PJ_M_POWERPC) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) || \ + defined(__ppc__) || defined(_M_PPC) || defined(_ARCH_PPC) +/* + * PowerPC, bi-endian, so raise error if endianness is not configured + */ +#undef PJ_M_POWERPC +#define PJ_M_POWERPC 1 +#define PJ_M_NAME "powerpc" +#define PJ_HAS_PENTIUM 0 +#if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +#error Endianness must be declared for this processor +#endif + +#elif defined(PJ_M_NIOS2) || defined(__nios2) || defined(__nios2__) || defined(__NIOS2__) || defined(__M_NIOS2) || \ + defined(_ARCH_NIOS2) +/* + * Nios2, little endian + */ +#undef PJ_M_NIOS2 +#define PJ_M_NIOS2 1 +#define PJ_M_NAME "nios2" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#else +#error "Please specify target machine." +#endif + +/* Include size_t definition. */ +#include + +/* Include site/user specific configuration to control PJLIB features. + * YOU MUST CREATE THIS FILE YOURSELF!! + */ +#include + +/******************************************************************** + * PJLIB Features. + */ + +/* Overrides for DOXYGEN */ +#ifdef DOXYGEN +#undef PJ_FUNCTIONS_ARE_INLINED +#undef PJ_HAS_FLOATING_POINT +#undef PJ_LOG_MAX_LEVEL +#undef PJ_LOG_MAX_SIZE +#undef PJ_LOG_USE_STACK_BUFFER +#undef PJ_TERM_HAS_COLOR +#undef PJ_POOL_DEBUG +#undef PJ_HAS_TCP +#undef PJ_MAX_HOSTNAME +#undef PJ_IOQUEUE_MAX_HANDLES +#undef FD_SETSIZE +#undef PJ_HAS_SEMAPHORE +#undef PJ_HAS_EVENT_OBJ +#undef PJ_EXCEPTION_USE_WIN32_SEH +#undef PJ_HAS_ERROR_STRING + +#define PJ_HAS_IPV6 1 +#endif + +/** + * @defgroup pj_config Build Configuration + * @{ + * + * This section contains macros that can set during PJLIB build process + * to controll various aspects of the library. + * + * Note: the values in this page does NOT necessarily reflect to the + * macro values during the build process. + */ + +/** + * If this macro is set to 1, it will enable some debugging checking + * in the library. + * + * Default: equal to (NOT NDEBUG). + */ +#ifndef PJ_DEBUG +#ifndef NDEBUG +#define PJ_DEBUG 1 +#else +#define PJ_DEBUG 0 +#endif +#endif + +/** + * Enable this macro to activate logging to mutex/semaphore related events. + * This is useful to troubleshoot concurrency problems such as deadlocks. + * In addition, you should also add PJ_LOG_HAS_THREAD_ID flag to the + * log decoration to assist the troubleshooting. + * + * Default: 0 + */ +#ifndef PJ_DEBUG_MUTEX +#define PJ_DEBUG_MUTEX 0 +#endif + +/** + * Expand functions in *_i.h header files as inline. + * + * Default: 0. + */ +#ifndef PJ_FUNCTIONS_ARE_INLINED +#define PJ_FUNCTIONS_ARE_INLINED 0 +#endif + +/** + * Use floating point computations in the library. + * + * Default: 1. + */ +#ifndef PJ_HAS_FLOATING_POINT +#define PJ_HAS_FLOATING_POINT 1 +#endif + +/** + * Declare maximum logging level/verbosity. Lower number indicates higher + * importance, with the highest importance has level zero. The least + * important level is five in this implementation, but this can be extended + * by supplying the appropriate implementation. + * + * The level conventions: + * - 0: fatal error + * - 1: error + * - 2: warning + * - 3: info + * - 4: debug + * - 5: trace + * - 6: more detailed trace + * + * Default: 4 + */ +#ifndef PJ_LOG_MAX_LEVEL +#define PJ_LOG_MAX_LEVEL 5 +#endif + +/** + * Maximum message size that can be sent to output device for each call + * to PJ_LOG(). If the message size is longer than this value, it will be cut. + * This may affect the stack usage, depending whether PJ_LOG_USE_STACK_BUFFER + * flag is set. + * + * Default: 4000 + */ +#ifndef PJ_LOG_MAX_SIZE +#define PJ_LOG_MAX_SIZE 4000 +#endif + +/** + * Log buffer. + * Does the log get the buffer from the stack? (default is yes). + * If the value is set to NO, then the buffer will be taken from static + * buffer, which in this case will make the log function non-reentrant. + * + * Default: 1 + */ +#ifndef PJ_LOG_USE_STACK_BUFFER +#define PJ_LOG_USE_STACK_BUFFER 1 +#endif + +/** + * Enable log indentation feature. + * + * Default: 1 + */ +#ifndef PJ_LOG_ENABLE_INDENT +#define PJ_LOG_ENABLE_INDENT 1 +#endif + +/** + * Number of PJ_LOG_INDENT_CHAR to put every time pj_log_push_indent() + * is called. + * + * Default: 1 + */ +#ifndef PJ_LOG_INDENT_SIZE +#define PJ_LOG_INDENT_SIZE 1 +#endif + +/** + * Log indentation character. + * + * Default: space + */ +#ifndef PJ_LOG_INDENT_CHAR +#define PJ_LOG_INDENT_CHAR '.' +#endif + +/** + * Log sender width. + * + * Default: 22 (for 64-bit machines), 14 otherwise + */ +#ifndef PJ_LOG_SENDER_WIDTH +#if PJ_HAS_STDINT_H +#include +#if (UINTPTR_MAX == 0xffffffffffffffff) +#define PJ_LOG_SENDER_WIDTH 22 +#else +#define PJ_LOG_SENDER_WIDTH 14 +#endif +#else +#define PJ_LOG_SENDER_WIDTH 14 +#endif +#endif + +/** + * Log thread name width. + * + * Default: 12 + */ +#ifndef PJ_LOG_THREAD_WIDTH +#define PJ_LOG_THREAD_WIDTH 12 +#endif + +/** + * Colorfull terminal (for logging etc). + * + * Default: 1 + */ +#ifndef PJ_TERM_HAS_COLOR +#define PJ_TERM_HAS_COLOR 1 +#endif + +/** + * Set this flag to non-zero to enable various checking for pool + * operations. When this flag is set, assertion must be enabled + * in the application. + * + * This will slow down pool creation and destruction and will add + * few bytes of overhead, so application would normally want to + * disable this feature on release build. + * + * Default: 0 + */ +#ifndef PJ_SAFE_POOL +#define PJ_SAFE_POOL 0 +#endif + +/** + * If pool debugging is used, then each memory allocation from the pool + * will call malloc(), and pool will release all memory chunks when it + * is destroyed. This works better when memory verification programs + * such as Rational Purify is used. + * + * Default: 0 + */ +#ifndef PJ_POOL_DEBUG +#define PJ_POOL_DEBUG 0 +#endif + +/** + * If enabled, when calling pj_pool_release(), the memory pool content + * will be wiped out first before released. + * + * Default: 0 + */ +#ifndef PJ_POOL_RELEASE_WIPE_DATA +#define PJ_POOL_RELEASE_WIPE_DATA 0 +#endif + +/** + * Enable timer debugging facility. When this is enabled, application + * can call pj_timer_heap_dump() to show the contents of the timer + * along with the source location where the timer entries were scheduled. + * See https://github.com/pjsip/pjproject/issues/1527 for more info. + * + * Default: 1 + */ +#ifndef PJ_TIMER_DEBUG +#define PJ_TIMER_DEBUG 1 +#endif + +/** + * If enabled, the timer will keep internal copies of the timer entries. + * This will increase the robustness and stability of the timer (against + * accidental modification or premature deallocation of the timer entries) and + * makes it easier to troubleshoot any timer related issues, with the overhead + * of additional memory space required. + * + * Note that the detection against premature deallocation only works if the + * freed memory content has changed (such as if it's been reallocated and + * overwritten by another data. Alternatively, you can enable + * PJ_POOL_RELEASE_WIPE_DATA which will erase the data first before releasing + * the memory). + * + * Default: 1 (enabled) + */ +#ifndef PJ_TIMER_USE_COPY +#define PJ_TIMER_USE_COPY 1 +#endif + +/** + * If enabled, the timer use sorted linked list instead of binary heap tree + * structure. Note that using sorted linked list is intended for debugging + * purposes and will hamper performance significantly when scheduling large + * number of entries. + * + * Default: 0 (Use binary heap tree) + */ +#ifndef PJ_TIMER_USE_LINKED_LIST +#define PJ_TIMER_USE_LINKED_LIST 0 +#endif + +/** + * Set this to 1 to enable debugging on the group lock. Default: 0 + */ +#ifndef PJ_GRP_LOCK_DEBUG +#define PJ_GRP_LOCK_DEBUG 0 +#endif + +/** + * Specify this as \a stack_size argument in #pj_thread_create() to specify + * that thread should use default stack size for the current platform. + * + * Default: 8192 + */ +#ifndef PJ_THREAD_DEFAULT_STACK_SIZE +#define PJ_THREAD_DEFAULT_STACK_SIZE 8192 +#endif + +/** + * Specify if PJ_CHECK_STACK() macro is enabled to check the sanity of + * the stack. The OS implementation may check that no stack overflow + * occurs, and it also may collect statistic about stack usage. Note + * that this will increase the footprint of the libraries since it + * tracks the filename and line number of each functions. + */ +#ifndef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#endif + +/** + * Do we have alternate pool implementation? + * + * Default: 0 + */ +#ifndef PJ_HAS_POOL_ALT_API +#define PJ_HAS_POOL_ALT_API PJ_POOL_DEBUG +#endif + +/** + * Support TCP in the library. + * Disabling TCP will reduce the footprint slightly (about 6KB). + * + * Default: 1 + */ +#ifndef PJ_HAS_TCP +#define PJ_HAS_TCP 1 +#endif + +/** + * Support IPv6 in the library. If this support is disabled, some IPv6 + * related functions will return PJ_EIPV6NOTSUP. + * + * Default: 0 (disabled, for now) + */ +#ifndef PJ_HAS_IPV6 +#define PJ_HAS_IPV6 0 +#endif + +/** + * Maximum hostname length. + * Libraries sometimes needs to make copy of an address to stack buffer; + * the value here affects the stack usage. + * + * Default: 128 + */ +#ifndef PJ_MAX_HOSTNAME +#define PJ_MAX_HOSTNAME (128) +#endif + +/** + * Maximum consecutive identical error for accept() operation before + * activesock stops calling the next ioqueue accept. + * + * Default: 50 + */ +#ifndef PJ_ACTIVESOCK_MAX_CONSECUTIVE_ACCEPT_ERROR +#define PJ_ACTIVESOCK_MAX_CONSECUTIVE_ACCEPT_ERROR 50 +#endif + +/** + * Constants for declaring the maximum handles that can be supported by + * a single IOQ framework. This constant might not be relevant to the + * underlying I/O queue impelementation, but still, developers should be + * aware of this constant, to make sure that the program will not break when + * the underlying implementation changes. + */ +#ifndef PJ_IOQUEUE_MAX_HANDLES +#define PJ_IOQUEUE_MAX_HANDLES (64) +#endif + +/** + * If PJ_IOQUEUE_HAS_SAFE_UNREG macro is defined, then ioqueue will do more + * things to ensure thread safety of handle unregistration operation by + * employing reference counter to each handle. + * + * In addition, the ioqueue will preallocate memory for the handles, + * according to the maximum number of handles that is specified during + * ioqueue creation. + * + * All applications would normally want this enabled, but you may disable + * this if: + * - there is no dynamic unregistration to all ioqueues. + * - there is no threading, or there is no preemptive multitasking. + * + * Default: 1 + */ +#ifndef PJ_IOQUEUE_HAS_SAFE_UNREG +#define PJ_IOQUEUE_HAS_SAFE_UNREG 1 +#endif + +/** + * Default concurrency setting for sockets/handles registered to ioqueue. + * This controls whether the ioqueue is allowed to call the key's callback + * concurrently/in parallel. The default is yes, which means that if there + * are more than one pending operations complete simultaneously, more + * than one threads may call the key's callback at the same time. This + * generally would promote good scalability for application, at the + * expense of more complexity to manage the concurrent accesses. + * + * Please see the ioqueue documentation for more info. + */ +#ifndef PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY +#define PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY 1 +#endif + +/* Sanity check: + * if ioqueue concurrency is disallowed, PJ_IOQUEUE_HAS_SAFE_UNREG + * must be enabled. + */ +#if (PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY == 0) && (PJ_IOQUEUE_HAS_SAFE_UNREG == 0) +#error PJ_IOQUEUE_HAS_SAFE_UNREG must be enabled if ioqueue concurrency \ + is disabled +#endif + +/** + * When safe unregistration (PJ_IOQUEUE_HAS_SAFE_UNREG) is configured in + * ioqueue, the PJ_IOQUEUE_KEY_FREE_DELAY macro specifies how long the + * ioqueue key is kept in closing state before it can be reused. + * + * The value is in miliseconds. + * + * Default: 500 msec. + */ +#ifndef PJ_IOQUEUE_KEY_FREE_DELAY +#define PJ_IOQUEUE_KEY_FREE_DELAY 500 +#endif + +/** + * Default flags for epoll_flags member of pj_ioqueue_cfg structure. + * The values are combination of pj_ioqueue_epoll_flag constants. + * + * Default: PJ_IOQUEUE_EPOLL_AUTO + */ +#ifndef PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS +#define PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS PJ_IOQUEUE_EPOLL_AUTO +#endif + +/** + * Determine if FD_SETSIZE is changeable/set-able. If so, then we will + * set it to PJ_IOQUEUE_MAX_HANDLES. Currently we detect this by checking + * for Winsock. + */ +#ifndef PJ_FD_SETSIZE_SETABLE +#if (defined(PJ_HAS_WINSOCK_H) && PJ_HAS_WINSOCK_H != 0) || (defined(PJ_HAS_WINSOCK2_H) && PJ_HAS_WINSOCK2_H != 0) +#define PJ_FD_SETSIZE_SETABLE 1 +#else +#define PJ_FD_SETSIZE_SETABLE 0 +#endif +#endif + +/** + * Overrides FD_SETSIZE so it is consistent throughout the library. + * We only do this if we detected that FD_SETSIZE is changeable. If + * FD_SETSIZE is not set-able, then PJ_IOQUEUE_MAX_HANDLES must be + * set to value lower than FD_SETSIZE. + */ +#if PJ_FD_SETSIZE_SETABLE +/* Only override FD_SETSIZE if the value has not been set */ +#ifndef FD_SETSIZE +#define FD_SETSIZE PJ_IOQUEUE_MAX_HANDLES +#endif +#else +/* When FD_SETSIZE is not changeable, check if PJ_IOQUEUE_MAX_HANDLES + * is lower than FD_SETSIZE value. + * + * Update: Not all ioqueue backends require this (such as epoll), so + * this check will be done on the ioqueue implementation itself, such as + * ioqueue select. + */ +/* +# ifdef FD_SETSIZE +# if PJ_IOQUEUE_MAX_HANDLES > FD_SETSIZE +# error "PJ_IOQUEUE_MAX_HANDLES is greater than FD_SETSIZE" +# endif +# endif +*/ +#endif + +/** + * Specify whether #pj_enum_ip_interface() function should exclude + * loopback interfaces. + * + * Default: 1 + */ +#ifndef PJ_IP_HELPER_IGNORE_LOOPBACK_IF +#define PJ_IP_HELPER_IGNORE_LOOPBACK_IF 1 +#endif + +/** + * Has semaphore functionality? + * + * Default: 1 + */ +#ifndef PJ_HAS_SEMAPHORE +#define PJ_HAS_SEMAPHORE 1 +#endif + +/** + * Use dispatch semaphores on Darwin. + * + * Default: 1 on Darwin, 0 otherwise + */ +#ifndef PJ_SEMAPHORE_USE_DISPATCH_SEM +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#define PJ_SEMAPHORE_USE_DISPATCH_SEM 1 +#else +#define PJ_SEMAPHORE_USE_DISPATCH_SEM 0 +#endif +#endif + +/** + * Event object (for synchronization, e.g. in Win32) + * + * Default: 1 + */ +#ifndef PJ_HAS_EVENT_OBJ +#define PJ_HAS_EVENT_OBJ 1 +#endif + +/** + * Maximum file name length. + */ +#ifndef PJ_MAXPATH +#define PJ_MAXPATH 260 +#endif + +/** + * Enable name registration for exceptions with #pj_exception_id_alloc(). + * If this feature is enabled, then the library will keep track of + * names associated with each exception ID requested by application via + * #pj_exception_id_alloc(). + * + * Disabling this macro will reduce the code and .bss size by a tad bit. + * See also #PJ_MAX_EXCEPTION_ID. + * + * Default: 1 + */ +#ifndef PJ_HAS_EXCEPTION_NAMES +#define PJ_HAS_EXCEPTION_NAMES 1 +#endif + +/** + * Maximum number of unique exception IDs that can be requested + * with #pj_exception_id_alloc(). For each entry, a small record will + * be allocated in the .bss segment. + * + * Default: 16 + */ +#ifndef PJ_MAX_EXCEPTION_ID +#define PJ_MAX_EXCEPTION_ID 16 +#endif + +/** + * Should we use Windows Structured Exception Handling (SEH) for the + * PJLIB exceptions. + * + * Default: 0 + */ +#ifndef PJ_EXCEPTION_USE_WIN32_SEH +#define PJ_EXCEPTION_USE_WIN32_SEH 0 +#endif + +/** + * Should we attempt to use Pentium's rdtsc for high resolution + * timestamp. + * + * Default: 0 + */ +#ifndef PJ_TIMESTAMP_USE_RDTSC +#define PJ_TIMESTAMP_USE_RDTSC 0 +#endif + +/** + * Is native platform error positive number? + * Default: 1 (yes) + */ +#ifndef PJ_NATIVE_ERR_POSITIVE +#define PJ_NATIVE_ERR_POSITIVE 1 +#endif + +/** + * Include error message string in the library (pj_strerror()). + * This is very much desirable! + * + * Default: 1 + */ +#ifndef PJ_HAS_ERROR_STRING +#define PJ_HAS_ERROR_STRING 1 +#endif + +/** + * Include pj_stricmp_alnum() and pj_strnicmp_alnum(), i.e. custom + * functions to compare alnum strings. On some systems, they're faster + * then stricmp/strcasecmp, but they can be slower on other systems. + * When disabled, pjlib will fallback to stricmp/strnicmp. + * + * Default: 0 + */ +#ifndef PJ_HAS_STRICMP_ALNUM +#define PJ_HAS_STRICMP_ALNUM 0 +#endif + +/* + * Warn about obsolete macros. + * + * PJ_ENABLE_EXTRA_CHECK has been deprecated in 2.13. + */ +#if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK == 0 +#ifdef _MSC_VER +#pragma message("Warning: PJ_ENABLE_EXTRA_CHECK macro is deprecated" \ + " and has no effect") +#else +#warning "PJ_ENABLE_EXTRA_CHECK macro is deprecated and has no effect" +#endif +#endif + +/* + * Types of QoS backend implementation. + */ + +/** + * Dummy QoS backend implementation, will always return error on all + * the APIs. + */ +#define PJ_QOS_DUMMY 1 + +/** QoS backend based on setsockopt(IP_TOS) */ +#define PJ_QOS_BSD 2 + +/** QoS backend for Windows Mobile 6 */ +#define PJ_QOS_WM 3 + +/** QoS backend for Symbian */ +#define PJ_QOS_SYMBIAN 4 + +/** QoS backend for Darwin */ +#define PJ_QOS_DARWIN 5 + +/** + * Force the use of some QoS backend API for some platforms. + */ +#ifndef PJ_QOS_IMPLEMENTATION +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE && _WIN32_WCE >= 0x502 +/* Windows Mobile 6 or later */ +#define PJ_QOS_IMPLEMENTATION PJ_QOS_WM +#elif defined(PJ_DARWINOS) +/* Darwin OS (e.g: iOS, MacOS, tvOS) */ +#define PJ_QOS_IMPLEMENTATION PJ_QOS_DARWIN +#endif +#endif + +/** + * Enable secure socket. For most platforms, this is implemented using + * OpenSSL or GnuTLS, so this will require one of those libraries to + * be installed. For Symbian platform, this is implemented natively + * using CSecureSocket. + * + * Default: 0 (for now) + */ +#ifndef PJ_HAS_SSL_SOCK +#define PJ_HAS_SSL_SOCK 0 +#endif + +/* + * Secure socket implementation. + * Select one of these implementations in PJ_SSL_SOCK_IMP. + */ +#define PJ_SSL_SOCK_IMP_NONE 0 /**< Disable SSL socket. */ +#define PJ_SSL_SOCK_IMP_OPENSSL 1 /**< Using OpenSSL. */ +#define PJ_SSL_SOCK_IMP_GNUTLS 2 /**< Using GnuTLS. */ +#define PJ_SSL_SOCK_IMP_DARWIN \ + 3 /**< Using Apple's Secure \ + Transport (deprecated in \ + MacOS 10.15 & iOS 13.0)*/ +#define PJ_SSL_SOCK_IMP_APPLE \ + 4 /**< Using Apple's Network \ + framework. */ + +/** + * Select which SSL socket implementation to use. Currently pjlib supports + * PJ_SSL_SOCK_IMP_OPENSSL, which uses OpenSSL, and PJ_SSL_SOCK_IMP_GNUTLS, + * which uses GnuTLS. Setting this to PJ_SSL_SOCK_IMP_NONE will disable + * secure socket. + * + * Default is PJ_SSL_SOCK_IMP_NONE if PJ_HAS_SSL_SOCK is not set, otherwise + * it is PJ_SSL_SOCK_IMP_OPENSSL. + */ +#ifndef PJ_SSL_SOCK_IMP +#if PJ_HAS_SSL_SOCK == 0 +#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_NONE +#else +#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_OPENSSL +#endif +#endif + +/** + * Define the maximum number of ciphers supported by the secure socket. + * + * Default: 256 + */ +#ifndef PJ_SSL_SOCK_MAX_CIPHERS +#define PJ_SSL_SOCK_MAX_CIPHERS 256 +#endif + +/** + * Specify what should be set as the available list of SSL_CIPHERs. For + * example, set this as "DEFAULT" to use the default cipher list (Note: + * PJSIP release 2.4 and before used this "DEFAULT" setting). + * + * Default: "HIGH:-COMPLEMENTOFDEFAULT" + */ +#ifndef PJ_SSL_SOCK_OSSL_CIPHERS +#define PJ_SSL_SOCK_OSSL_CIPHERS "HIGH:-COMPLEMENTOFDEFAULT" +#endif + +/** + * Define the maximum number of curves supported by the secure socket. + * + * Default: 32 + */ +#ifndef PJ_SSL_SOCK_MAX_CURVES +#define PJ_SSL_SOCK_MAX_CURVES 32 +#endif + +/** + * Use OpenSSL thread locking callback. This is only applicable for OpenSSL + * version prior to 1.1.0 + * + * Default: 1 (enabled) + */ +#ifndef PJ_SSL_SOCK_OSSL_USE_THREAD_CB +#define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 1 +#else +#define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 0 +#endif + +/** + * Disable WSAECONNRESET error for UDP sockets on Win32 platforms. See + * https://github.com/pjsip/pjproject/issues/1197. + * + * Default: 1 + */ +#ifndef PJ_SOCK_DISABLE_WSAECONNRESET +#define PJ_SOCK_DISABLE_WSAECONNRESET 1 +#endif + +/** + * Maximum number of socket options in pj_sockopt_params. + * + * Default: 4 + */ +#ifndef PJ_MAX_SOCKOPT_PARAMS +#define PJ_MAX_SOCKOPT_PARAMS 4 +#endif + +/** @} */ + +/******************************************************************** + * General macros. + */ + +/** + * @defgroup pj_dll_target Building Dynamic Link Libraries (DLL/DSO) + * @ingroup pj_config + * @{ + * + * The libraries support generation of dynamic link libraries for + * Symbian ABIv2 target (.dso/Dynamic Shared Object files, in Symbian + * terms). Similar procedures may be applied for Win32 DLL with some + * modification. + * + * Depending on the platforms, these steps may be necessary in order to + * produce the dynamic libraries: + * - Create the (Visual Studio) projects to produce DLL output. PJLIB + * does not provide ready to use project files to produce DLL, so + * you need to create these projects yourself. For Symbian, the MMP + * files have been setup to produce DSO files for targets that + * require them. + * - In the (Visual Studio) projects, some macros need to be declared + * so that appropriate modifiers are added to symbol declarations + * and definitions. Please see the macro section below for information + * regarding these macros. For Symbian, these have been taken care by the + * MMP files. + * - Some build systems require .DEF file to be specified when creating + * the DLL. For Symbian, .DEF files are included in pjlib distribution, + * in pjlib/build.symbian directory. These DEF files are + * created by running ./makedef.sh all from this directory, + * inside Mingw. + * + * Macros related for building DLL/DSO files: + * - For platforms that supports dynamic link libraries generation, + * it must declare PJ_EXPORT_SPECIFIER macro which value contains + * the prefix to be added to symbol definition, to export this + * symbol in the DLL/DSO. For example, on Win32/Visual Studio, the + * value of this macro is \a __declspec(dllexport), and for ARM + * ABIv2/Symbian, the value is \a EXPORT_C. + * - For platforms that supports linking with dynamic link libraries, + * it must declare PJ_IMPORT_SPECIFIER macro which value contains + * the prefix to be added to symbol declaration, to import this + * symbol from a DLL/DSO. For example, on Win32/Visual Studio, the + * value of this macro is \a __declspec(dllimport), and for ARM + * ABIv2/Symbian, the value is \a IMPORT_C. + * - Both PJ_EXPORT_SPECIFIER and PJ_IMPORT_SPECIFIER + * macros above can be declared in your \a config_site.h if they are not + * declared by pjlib. + * - When PJLIB is built as DLL/DSO, both PJ_DLL and + * PJ_EXPORTING macros must be declared, so that + * PJ_EXPORT_SPECIFIER modifier will be added into function + * definition. + * - When application wants to link dynamically with PJLIB, then it + * must declare PJ_DLL macro when using/including PJLIB header, + * so that PJ_IMPORT_SPECIFIER modifier is properly added into + * symbol declarations. + * + * When PJ_DLL macro is not declared, static linking is assumed. + * + * For example, here are some settings to produce DLLs with Visual Studio + * on Windows/Win32: + * - Create Visual Studio projects to produce DLL. Add the appropriate + * project dependencies to avoid link errors. + * - In the projects, declare PJ_DLL and PJ_EXPORTING + * macros. + * - Declare these macros in your config_site.h: + \verbatim + #define PJ_EXPORT_SPECIFIER __declspec(dllexport) + #define PJ_IMPORT_SPECIFIER __declspec(dllimport) + \endverbatim + * - And in the application (that links with the DLL) project, add + * PJ_DLL in the macro declarations. + */ + +/** @} */ + +/** + * @defgroup pj_config Build Configuration + * @{ + */ + +/** + * @def PJ_INLINE(type) + * @param type The return type of the function. + * Expand the function as inline. + */ +#define PJ_INLINE(type) PJ_INLINE_SPECIFIER type + +/** + * This macro declares platform/compiler specific specifier prefix + * to be added to symbol declaration to export the symbol when PJLIB + * is built as dynamic library. + * + * This macro should have been added by platform specific headers, + * if the platform supports building dynamic library target. + */ +#ifndef PJ_EXPORT_DECL_SPECIFIER +#define PJ_EXPORT_DECL_SPECIFIER +#endif + +/** + * This macro declares platform/compiler specific specifier prefix + * to be added to symbol definition to export the symbol when PJLIB + * is built as dynamic library. + * + * This macro should have been added by platform specific headers, + * if the platform supports building dynamic library target. + */ +#ifndef PJ_EXPORT_DEF_SPECIFIER +#define PJ_EXPORT_DEF_SPECIFIER +#endif + +/** + * This macro declares platform/compiler specific specifier prefix + * to be added to symbol declaration to import the symbol. + * + * This macro should have been added by platform specific headers, + * if the platform supports building dynamic library target. + */ +#ifndef PJ_IMPORT_DECL_SPECIFIER +#define PJ_IMPORT_DECL_SPECIFIER +#endif + +/** + * This macro has been deprecated. It will evaluate to nothing. + */ +#ifndef PJ_EXPORT_SYMBOL +#define PJ_EXPORT_SYMBOL(x) +#endif + +/** + * @def PJ_DECL(type) + * @param type The return type of the function. + * Declare a function. + */ +#if defined(PJ_DLL) +#if defined(PJ_EXPORTING) +#define PJ_DECL(type) PJ_EXPORT_DECL_SPECIFIER type +#else +#define PJ_DECL(type) PJ_IMPORT_DECL_SPECIFIER type +#endif +#elif !defined(PJ_DECL) +#if defined(__cplusplus) +#define PJ_DECL(type) type +#else +#define PJ_DECL(type) extern type +#endif +#endif + +/** + * @def PJ_DEF(type) + * @param type The return type of the function. + * Define a function. + */ +#if defined(PJ_DLL) && defined(PJ_EXPORTING) +#define PJ_DEF(type) PJ_EXPORT_DEF_SPECIFIER type +#elif !defined(PJ_DEF) +#define PJ_DEF(type) type +#endif + +/** + * @def PJ_DECL_NO_RETURN(type) + * @param type The return type of the function. + * Declare a function that will not return. + */ +/** + * @def PJ_IDECL_NO_RETURN(type) + * @param type The return type of the function. + * Declare an inline function that will not return. + */ +/** + * @def PJ_BEGIN_DECL + * Mark beginning of declaration section in a header file. + */ +/** + * @def PJ_END_DECL + * Mark end of declaration section in a header file. + */ +#ifdef __cplusplus +#define PJ_DECL_NO_RETURN(type) PJ_DECL(type) PJ_NORETURN +#define PJ_IDECL_NO_RETURN(type) PJ_INLINE(type) PJ_NORETURN +#define PJ_BEGIN_DECL extern "C" { +#define PJ_END_DECL } +#else +#define PJ_DECL_NO_RETURN(type) PJ_NORETURN PJ_DECL(type) +#define PJ_IDECL_NO_RETURN(type) PJ_NORETURN PJ_INLINE(type) +#define PJ_BEGIN_DECL +#define PJ_END_DECL +#endif + +/** + * @def PJ_DECL_DATA(type) + * @param type The data type. + * Declare a global data. + */ +#if defined(PJ_DLL) +#if defined(PJ_EXPORTING) +#define PJ_DECL_DATA(type) PJ_EXPORT_DECL_SPECIFIER extern type +#else +#define PJ_DECL_DATA(type) PJ_IMPORT_DECL_SPECIFIER extern type +#endif +#elif !defined(PJ_DECL_DATA) +#define PJ_DECL_DATA(type) extern type +#endif + +/** + * @def PJ_DEF_DATA(type) + * @param type The data type. + * Define a global data. + */ +#if defined(PJ_DLL) && defined(PJ_EXPORTING) +#define PJ_DEF_DATA(type) PJ_EXPORT_DEF_SPECIFIER type +#elif !defined(PJ_DEF_DATA) +#define PJ_DEF_DATA(type) type +#endif + +/** + * @def PJ_IDECL(type) + * @param type The function's return type. + * Declare a function that may be expanded as inline. + */ +/** + * @def PJ_IDEF(type) + * @param type The function's return type. + * Define a function that may be expanded as inline. + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#define PJ_IDECL(type) PJ_INLINE(type) +#define PJ_IDEF(type) PJ_INLINE(type) +#else +#define PJ_IDECL(type) PJ_DECL(type) +#define PJ_IDEF(type) PJ_DEF(type) +#endif + +/** + * @def PJ_UNUSED_ARG(arg) + * @param arg The argument name. + * PJ_UNUSED_ARG prevents warning about unused argument in a function. + */ +#define PJ_UNUSED_ARG(arg) (void)arg + +/** + * @def PJ_TODO(id) + * @param id Any identifier that will be printed as TODO message. + * PJ_TODO macro will display TODO message as warning during compilation. + * Example: PJ_TODO(CLEAN_UP_ERROR); + */ +#ifndef PJ_TODO +#define PJ_TODO(id) TODO___##id: +#endif + +/** + * Simulate race condition by sleeping the thread in strategic locations. + * Default: no! + */ +#ifndef PJ_RACE_ME +#define PJ_RACE_ME(x) +#endif + +/** + * Function attributes to inform that the function may throw exception. + * + * @param x The exception list, enclosed in parenthesis. + */ +#define __pj_throw__(x) + +/** @} */ + +/******************************************************************** + * Sanity Checks + */ +#ifndef PJ_HAS_HIGH_RES_TIMER +#error "PJ_HAS_HIGH_RES_TIMER is not defined!" +#endif + +#if !defined(PJ_HAS_PENTIUM) +#error "PJ_HAS_PENTIUM is not defined!" +#endif + +#if !defined(PJ_IS_LITTLE_ENDIAN) +#error "PJ_IS_LITTLE_ENDIAN is not defined!" +#endif + +#if !defined(PJ_IS_BIG_ENDIAN) +#error "PJ_IS_BIG_ENDIAN is not defined!" +#endif + +#if !defined(PJ_EMULATE_RWMUTEX) +#error "PJ_EMULATE_RWMUTEX should be defined in compat/os_xx.h" +#endif + +#if !defined(PJ_THREAD_SET_STACK_SIZE) +#error "PJ_THREAD_SET_STACK_SIZE should be defined in compat/os_xx.h" +#endif + +#if !defined(PJ_THREAD_ALLOCATE_STACK) +#error "PJ_THREAD_ALLOCATE_STACK should be defined in compat/os_xx.h" +#endif + +PJ_BEGIN_DECL + +/** PJLIB version major number. */ +#define PJ_VERSION_NUM_MAJOR 2 + +/** PJLIB version minor number. */ +#define PJ_VERSION_NUM_MINOR 13 + +/** PJLIB version revision number. */ +#define PJ_VERSION_NUM_REV 1 + +/** + * Extra suffix for the version (e.g. "-trunk"), or empty for + * web release version. + */ +#define PJ_VERSION_NUM_EXTRA "" + +/** + * PJLIB version number consists of three bytes with the following format: + * 0xMMIIRR00, where MM: major number, II: minor number, RR: revision + * number, 00: always zero for now. + */ +#define PJ_VERSION_NUM ((PJ_VERSION_NUM_MAJOR << 24) | (PJ_VERSION_NUM_MINOR << 16) | (PJ_VERSION_NUM_REV << 8)) + +/** + * PJLIB version string constant. @see pj_get_version() + */ +PJ_DECL_DATA(const char *) PJ_VERSION; + +/** + * Get PJLIB version string. + * + * @return #PJ_VERSION constant. + */ +PJ_DECL(const char *) pj_get_version(void); + +/** + * Dump configuration to log with verbosity equal to info(3). + */ +PJ_DECL(void) pj_dump_config(void); + +PJ_END_DECL + +#endif /* __PJ_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h new file mode 100755 index 000000000..df73ba62e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h @@ -0,0 +1,15 @@ +/* + * Put this file in pjlib/include/pj + */ + +/* sample configure command: + CFLAGS="-g -Wno-unused-label" ./aconfigure --enable-ext-sound --disable-speex-aec --disable-g711-codec + --disable-l16-codec --disable-gsm-codec --disable-g722-codec --disable-g7221-codec --disable-speex-codec + --disable-ilbc-codec --disable-opencore-amrnb --disable-sdl --disable-ffmpeg --disable-v4l2 + */ + +#define THIRD_PARTY_MEDIA 1 + +#if THIRD_PARTY_MEDIA + +#endif /* THIRD_PARTY_MEDIA */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h new file mode 100755 index 000000000..7574e85a2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h @@ -0,0 +1,476 @@ +/* + * This file contains several sample settings especially for Windows + * Mobile and Symbian targets. You can include this file in your + * file. + * + * The Windows Mobile and Symbian settings will be activated + * automatically if you include this file. + * + * In addition, you may specify one of these macros (before including + * this file) to activate additional settings: + * + * #define PJ_CONFIG_NOKIA_APS_DIRECT + * Use this macro to activate the APS-Direct feature. Please see + * http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct for more + * info. + * + * #define PJ_CONFIG_WIN32_WMME_DIRECT + * Configuration to activate "APS-Direct" media mode on Windows or + * Windows Mobile, useful for testing purposes only. + */ + +/* + * Typical configuration for WinCE target. + */ +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + +/* + * PJLIB settings. + */ + +/* Disable floating point support */ +#define PJ_HAS_FLOATING_POINT 0 + +/* + * PJMEDIA settings + */ + +/* Select codecs to disable */ +#define PJMEDIA_HAS_L16_CODEC 0 +#define PJMEDIA_HAS_ILBC_CODEC 0 + +/* We probably need more buffers on WM, so increase the limit */ +#define PJMEDIA_SOUND_BUFFER_COUNT 32 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* For CPU reason, disable speex AEC and use the echo suppressor. */ +#define PJMEDIA_HAS_SPEEX_AEC 0 + +/* Previously, resampling is disabled due to performance reason and + * this condition prevented some 'light' wideband codecs (e.g: G722.1) + * to work along with narrowband codecs. Lately, some tests showed + * that 16kHz <-> 8kHz resampling using libresample small filter was + * affordable on ARM9 260 MHz, so here we decided to enable resampling. + * Note that it is important to make sure that libresample is created + * using small filter. For example PJSUA_DEFAULT_CODEC_QUALITY must + * be set to 3 or 4 so pjsua-lib will apply small filter resampling. + */ +//#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE +#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE + +/* Use the lighter WSOLA implementation */ +#define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE + +/* + * PJSIP settings. + */ + +/* Set maximum number of dialog/transaction/calls to minimum to reduce + * memory usage + */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* + * PJSUA settings + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of objects to minimum to reduce memory usage */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 + +#endif /* PJ_WIN32_WINCE */ + +/* + * Typical configuration for Symbian OS target + */ +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + +/* + * PJLIB settings. + */ + +/* Disable floating point support */ +#define PJ_HAS_FLOATING_POINT 0 + +/* Misc PJLIB setting */ +#define PJ_MAXPATH 80 + +/* This is important for Symbian. Symbian lacks vsnprintf(), so + * if the log buffer is not long enough it's possible that + * large incoming packet will corrupt memory when the log tries + * to log the packet. + */ +#define PJ_LOG_MAX_SIZE (PJSIP_MAX_PKT_LEN + 500) + +/* Since we don't have threads, log buffer can use static buffer + * rather than stack + */ +#define PJ_LOG_USE_STACK_BUFFER 0 + +/* Disable check stack since it increases footprint */ +#define PJ_OS_HAS_CHECK_STACK 0 + +/* + * PJMEDIA settings + */ + +/* Disable non-Symbian audio devices */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 + +/* Select codecs to disable */ +#define PJMEDIA_HAS_L16_CODEC 0 +#define PJMEDIA_HAS_ILBC_CODEC 0 +#define PJMEDIA_HAS_G722_CODEC 0 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* For CPU reason, disable speex AEC and use the echo suppressor. */ +#define PJMEDIA_HAS_SPEEX_AEC 0 + +/* Previously, resampling is disabled due to performance reason and + * this condition prevented some 'light' wideband codecs (e.g: G722.1) + * to work along with narrowband codecs. Lately, some tests showed + * that 16kHz <-> 8kHz resampling using libresample small filter was + * affordable on ARM9 222 MHz, so here we decided to enable resampling. + * Note that it is important to make sure that libresample is created + * using small filter. For example PJSUA_DEFAULT_CODEC_QUALITY must + * be set to 3 or 4 so pjsua-lib will apply small filter resampling. + */ +//#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE +#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE + +/* Use the lighter WSOLA implementation */ +#define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE + +/* We probably need more buffers especially if MDA audio backend + * is used, so increase the limit + */ +#define PJMEDIA_SOUND_BUFFER_COUNT 32 + +/* + * PJSIP settings. + */ + +/* Disable safe module access, since we don't use multithreading */ +#define PJSIP_SAFE_MODULE 0 + +/* Use large enough packet size */ +#define PJSIP_MAX_PKT_LEN 2000 + +/* Symbian has problem with too many large blocks */ +#define PJSIP_POOL_LEN_ENDPT 1000 +#define PJSIP_POOL_INC_ENDPT 1000 +#define PJSIP_POOL_RDATA_LEN 2000 +#define PJSIP_POOL_RDATA_INC 2000 +#define PJSIP_POOL_LEN_TDATA 2000 +#define PJSIP_POOL_INC_TDATA 512 +#define PJSIP_POOL_LEN_UA 2000 +#define PJSIP_POOL_INC_UA 1000 +#define PJSIP_POOL_TSX_LAYER_LEN 256 +#define PJSIP_POOL_TSX_LAYER_INC 256 +#define PJSIP_POOL_TSX_LEN 512 +#define PJSIP_POOL_TSX_INC 128 + +/* + * PJSUA settings. + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of dialog/transaction/calls to minimum */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* Other pjsua settings */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 +#endif + +/* + * Additional configuration to activate APS-Direct feature for + * Nokia S60 target + * + * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct + */ +#ifdef PJ_CONFIG_NOKIA_APS_DIRECT + +/* MUST use switchboard rather than the conference bridge */ +#define PJMEDIA_CONF_USE_SWITCH_BOARD 1 + +/* Enable APS sound device backend and disable MDA & VAS */ +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA 0 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 1 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS 0 + +/* Enable passthrough codec framework */ +#define PJMEDIA_HAS_PASSTHROUGH_CODECS 1 + +/* And selectively enable which codecs are supported by the handset */ +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1 + +#endif + +/* + * Additional configuration to activate VAS-Direct feature for + * Nokia S60 target + * + * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct + */ +#ifdef PJ_CONFIG_NOKIA_VAS_DIRECT + +/* MUST use switchboard rather than the conference bridge */ +#define PJMEDIA_CONF_USE_SWITCH_BOARD 1 + +/* Enable VAS sound device backend and disable MDA & APS */ +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA 0 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 0 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS 1 + +/* Enable passthrough codec framework */ +#define PJMEDIA_HAS_PASSTHROUGH_CODECS 1 + +/* And selectively enable which codecs are supported by the handset */ +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1 + +#endif + +/* + * Configuration to activate "APS-Direct" media mode on Windows, + * useful for testing purposes only. + */ +#ifdef PJ_CONFIG_WIN32_WMME_DIRECT + +/* MUST use switchboard rather than the conference bridge */ +#define PJMEDIA_CONF_USE_SWITCH_BOARD 1 + +/* Only WMME supports the "direct" feature */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 1 + +/* Enable passthrough codec framework */ +#define PJMEDIA_HAS_PASSTHROUGH_CODECS 1 + +/* Only PCMA and PCMU are supported by WMME-direct */ +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 0 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 0 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 0 + +#endif + +/* + * iPhone sample settings. + */ +#if PJ_CONFIG_IPHONE +/* + * PJLIB settings. + */ + +/* Both armv6 and armv7 has FP hardware support. + * See https://github.com/pjsip/pjproject/issues/1589 for more info + */ +#define PJ_HAS_FLOATING_POINT 1 + +/* + * PJMEDIA settings + */ + +/* We have our own native CoreAudio backend */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 +#define PJMEDIA_AUDIO_DEV_HAS_COREAUDIO 1 + +/* The CoreAudio backend has built-in echo canceller! */ +#define PJMEDIA_HAS_SPEEX_AEC 0 + +/* Disable some codecs */ +#define PJMEDIA_HAS_L16_CODEC 0 +//#define PJMEDIA_HAS_G722_CODEC 0 + +/* Use the built-in CoreAudio's iLBC codec (yay!) */ +#define PJMEDIA_HAS_ILBC_CODEC 1 +#define PJMEDIA_ILBC_CODEC_USE_COREAUDIO 1 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* + * PJSIP settings. + */ + +/* Increase allowable packet size, just in case */ +//#define PJSIP_MAX_PKT_LEN 2000 + +/* + * PJSUA settings. + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of dialog/transaction/calls to minimum */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* Other pjsua settings */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 + +#endif + +/* + * Android sample settings. + */ +#if PJ_CONFIG_ANDROID + +/* + * PJLIB settings. + */ + +/* Disable floating point support */ +#undef PJ_HAS_FLOATING_POINT +#define PJ_HAS_FLOATING_POINT 0 + +/* + * PJMEDIA settings + */ + +/* We have our own OpenSL ES backend */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 +#define PJMEDIA_AUDIO_DEV_HAS_OPENSL 0 +#define PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI 1 + +/* Disable some codecs */ +#define PJMEDIA_HAS_L16_CODEC 0 +//#define PJMEDIA_HAS_G722_CODEC 0 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* + * PJSIP settings. + */ + +/* Increase allowable packet size, just in case */ +//#define PJSIP_MAX_PKT_LEN 2000 + +/* + * PJSUA settings. + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of dialog/transaction/calls to minimum */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* Separate worker thread for timer and ioqueue */ +// #define PJSUA_SEPARATE_WORKER_FOR_TIMER 1 + +/* Other pjsua settings */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 +#endif + +/* + * BB10 + */ +#if defined(PJ_CONFIG_BB10) && PJ_CONFIG_BB10 +/* Quality 3 - 4 to use resampling small filter */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 +#define PJMEDIA_HAS_LEGACY_SOUND_API 0 +#undef PJMEDIA_HAS_SPEEX_AEC +#define PJMEDIA_HAS_SPEEX_AEC 0 +#undef PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#undef PJMEDIA_AUDIO_DEV_HAS_ALSA +#define PJMEDIA_AUDIO_DEV_HAS_ALSA 0 +#endif + +/* + * Minimum size + */ +#ifdef PJ_CONFIG_MINIMAL_SIZE + +#undef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#define PJ_LOG_MAX_LEVEL 0 +#define PJ_HAS_ERROR_STRING 0 +#undef PJ_IOQUEUE_MAX_HANDLES +/* Putting max handles to lower than 32 will make pj_fd_set_t size smaller + * than native fdset_t and will trigger assertion on sock_select.c. + */ +#define PJ_IOQUEUE_MAX_HANDLES 32 +#define PJ_CRC32_HAS_TABLES 0 +#define PJSIP_MAX_TSX_COUNT 15 +#define PJSIP_MAX_DIALOG_COUNT 15 +#define PJSIP_UDP_SO_SNDBUF_SIZE 4000 +#define PJSIP_UDP_SO_RCVBUF_SIZE 4000 +#define PJMEDIA_HAS_ALAW_ULAW_TABLE 0 + +#elif defined(PJ_CONFIG_MAXIMUM_SPEED) +#define PJ_SCANNER_USE_BITWISE 0 +#undef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#define PJ_LOG_MAX_LEVEL 3 +#define PJ_IOQUEUE_MAX_HANDLES 5000 +#define PJSIP_MAX_TSX_COUNT ((640 * 1024) - 1) +#define PJSIP_MAX_DIALOG_COUNT ((640 * 1024) - 1) +#define PJSIP_UDP_SO_SNDBUF_SIZE (24 * 1024 * 1024) +#define PJSIP_UDP_SO_RCVBUF_SIZE (24 * 1024 * 1024) +#define PJ_DEBUG 0 +#define PJSIP_SAFE_MODULE 0 +#define PJ_HAS_STRICMP_ALNUM 0 +#define PJSIP_UNESCAPE_IN_PLACE 1 + +#if defined(PJ_WIN32) || defined(PJ_WIN64) +#define PJSIP_MAX_NET_EVENTS 10 +#endif + +#define PJSUA_MAX_CALLS 512 + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h new file mode 100755 index 000000000..d91a1695a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h @@ -0,0 +1,11 @@ +#define PJMEDIA_SRTP_HAS_DTLS 1 +#define PJMEDIA_HAS_WEBRTC_AEC 1 +#define PJMEDIA_CODEC_L16_HAS_8KHZ_MONO 1 +#define PJMEDIA_CODEC_L16_HAS_8KHZ_STEREO 1 +#define PJMEDIA_CODEC_L16_HAS_16KHZ_MONO 1 +#define PJMEDIA_CODEC_L16_HAS_16KHZ_STEREO 1 +#define PJMEDIA_CODEC_L16_HAS_48KHZ_MONO 1 +#define PJMEDIA_CODEC_L16_HAS_48KHZ_STEREO 1 +#define PJMEDIA_HAS_G7221_CODEC 1 +#define PJMEDIA_HAS_G722_CODEC 1 +#define PJ_EXCLUDE_BENCHMARK_TESTS 1 diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h new file mode 100755 index 000000000..a98940442 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_CTYPE_H__ +#define __PJ_CTYPE_H__ + +/** + * @file ctype.h + * @brief C type helper macros. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_ctype ctype - Character Type + * @ingroup PJ_MISC + * @{ + * + * This module contains several inline functions/macros for testing or + * manipulating character types. It is provided in PJLIB because PJLIB + * must not depend to LIBC. + */ + +/** + * Returns a non-zero value if either isalpha or isdigit is true for c. + * @param c The integer character to test. + * @return Non-zero value if either isalpha or isdigit is true for c. + */ +PJ_INLINE(int) pj_isalnum(unsigned char c) +{ + return isalnum(c); +} + +/** + * Returns a non-zero value if c is a particular representation of an + * alphabetic character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of an + * alphabetic character. + */ +PJ_INLINE(int) pj_isalpha(unsigned char c) +{ + return isalpha(c); +} + +/** + * Returns a non-zero value if c is a particular representation of an + * ASCII character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * an ASCII character. + */ +PJ_INLINE(int) pj_isascii(unsigned char c) +{ + return c < 128; +} + +/** + * Returns a non-zero value if c is a particular representation of + * a decimal-digit character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a decimal-digit character. + */ +PJ_INLINE(int) pj_isdigit(unsigned char c) +{ + return isdigit(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * a space character (0x09 - 0x0D or 0x20). + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a space character (0x09 - 0x0D or 0x20). + */ +PJ_INLINE(int) pj_isspace(unsigned char c) +{ + return isspace(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * a lowercase character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a lowercase character. + */ +PJ_INLINE(int) pj_islower(unsigned char c) +{ + return islower(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * a uppercase character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a uppercase character. + */ +PJ_INLINE(int) pj_isupper(unsigned char c) +{ + return isupper(c); +} + +/** + * Returns a non-zero value if c is a either a space (' ') or horizontal + * tab ('\\t') character. + * @param c The integer character to test. + * @return Non-zero value if c is a either a space (' ') or horizontal + * tab ('\\t') character. + */ +PJ_INLINE(int) pj_isblank(unsigned char c) +{ + return (c == ' ' || c == '\t'); +} + +/** + * Converts character to lowercase. + * @param c The integer character to convert. + * @return Lowercase character of c. + */ +PJ_INLINE(int) pj_tolower(unsigned char c) +{ + return tolower(c); +} + +/** + * Converts character to uppercase. + * @param c The integer character to convert. + * @return Uppercase character of c. + */ +PJ_INLINE(int) pj_toupper(unsigned char c) +{ + return toupper(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * an hexadecimal digit character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * an hexadecimal digit character. + */ +PJ_INLINE(int) pj_isxdigit(unsigned char c) +{ + return isxdigit(c); +} + +/** + * Array of hex digits, in lowerspace. + */ +/*extern char pj_hex_digits[];*/ +#define pj_hex_digits "0123456789abcdef" + +/** + * Convert a value to hex representation. + * @param value Integral value to convert. + * @param p Buffer to hold the hex representation, which must be + * at least two bytes length. + */ +PJ_INLINE(void) pj_val_to_hex_digit(unsigned value, char *p) +{ + *p++ = pj_hex_digits[(value & 0xF0) >> 4]; + *p = pj_hex_digits[(value & 0x0F)]; +} + +/** + * Convert hex digit c to integral value. + * @param c The hex digit character. + * @return The integral value between 0 and 15. + */ +PJ_INLINE(unsigned) pj_hex_digit_to_val(unsigned char c) +{ + if (c <= '9') + return (c - '0') & 0x0F; + else if (c <= 'F') + return (c - 'A' + 10) & 0x0F; + else + return (c - 'a' + 10) & 0x0F; +} + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_CTYPE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h b/src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h new file mode 100755 index 000000000..1e8529e57 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h @@ -0,0 +1,986 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_DOXYGEN_H__ +#define __PJ_DOXYGEN_H__ + +/** + * @file doxygen.h + * @brief Doxygen's mainpage. + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + INTRODUCTION PAGE + */ + +/** + * @mainpage Welcome to PJLIB! + * + * @section intro_sec What is PJLIB + * + * PJLIB is an Open Source, small footprint framework library written in C for + * making scalable applications. Because of its small footprint, it can be used + * in embedded applications (we hope so!), but yet the library is also aimed for + * facilitating the creation of high performance protocol stacks. + * + * PJLIB is released under GPL terms. + * + * @section download_sec Download + * + * PJLIB and all documentation can be downloaded from + * http://www.pjsip.org. + * + * + * @section how_to_use_sec About This Documentation + * + * This document is generated directly from PJLIB source file using + * \a doxygen (http://www.doxygen.org). Doxygen is a great (and free!) + * tools for generating such documentation. + * + * + * @subsection find_samples_subsec How to Read This Document + * + * This documentation is laid out more to be a reference guide instead + * of tutorial, therefore first time users may find it difficult to + * grasp PJLIB by reading this document alone. + * + * However, we've tried our best to make this document easy to follow. + * For first time users, we would suggest that you follow these steps + * when reading this documentation: + * + * - continue reading this introduction chapter. At the end of this + * chapter, you'll find section called \ref pjlib_fundamentals_sec + * which should guide you to understand basic things about PJLIB. + * + * - find information about specific features that you want to use + * in PJLIB. Use the Module Index to find out about all + * features in PJLIB (if you're browsing the HTML documentation, + * click on the \a Module link on top of the page, or if you're + * reading the PDF documentation, click on \a Module \a Documentation + * on the navigation pane on the left). + * + * @subsection doc_organize_sec How To's + * + * Please find below links to specific tasks that you probably + * want to do: + * + * - How to Build PJLIB + *\n + * Please refer to \ref pjlib_build_sys_pg page for more information. + * + * - How to Use PJLIB in My Application + *\n + * Please refer to \ref configure_app_sec for more information. + * + * - How to Port PJLIB + *\n + * Please refer to \ref porting_pjlib_pg page. + * + * - Where to Read Samples Documentation + *\n + * Most of the modules provide link to the corresponding sample file. + * Alternatively, to get the list of all examples, you can click on + * Related Pages on the top of HTML document or on + * PJLIB Page Documentation on navigation pane of your PDF reader. + * + * - How to Submit Code to PJLIB Project + *\n + * Please read \ref pjlib_coding_convention_page before submitting + * your code. Send your code as patch against current Subversion tree + * to the appropriate mailing list. + * + * + * @section features_sec Features + * + * @subsection open_source_feat It's Open Source! + * + * PJLIB is currently released on GPL license, but other arrangements + * can be made with the author. + * + * @subsection extreme_portable_feat Extreme Portability + * + * PJLIB is designed to be extremely portable. It can run on any kind + * of processors (16-bit, 32-bit, or 64-bit, big or little endian, single + * or multi-processors) and operating systems. Floating point or no + * floating point. Multi-threading or not. + * It can even run in environment where no ANSI LIBC is available. + * + * Currently PJLIB is known to run on these platforms: + * - Win32/x86 (Win95/98/ME, NT/2000/XP/2003, mingw). + * - arm, WinCE and Windows Mobile. + * - Linux/x86, (user mode and as kernel module(!)). + * - Linux/alpha + * - Solaris/ultra. + * - MacOS X/powerpc + * - RTEMS (x86 and powerpc). + * + * And efforts is under way to port PJLIB on: + * - Symbian OS + * + * + * @subsection small_size_feat Small in Size + * + * One of the primary objectives is to have library that is small in size for + * typical embedded applications. As a rough guidance, we aim to keep the + * library size below 100KB for it to be considered as small. + * As the result, most of the functionalities in the library can be tailored + * to meet the requirements; user can enable/disable specific functionalities + * to get the desired size/performance/functionality balance. + * + * For more info, please see @ref pj_config. + * + * + * @subsection big_perform_feat Big in Performance + * + * Almost everything in PJLIB is designed to achieve the highest possible + * performance out of the target platform. + * + * + * @subsection no_dyn_mem No Dynamic Memory Allocations + * + * The central idea of PJLIB is that for applications to run as fast as it can, + * it should not use \a malloc() at all, but instead should get the memory + * from a preallocated storage pool. There are few things that can be + * optimized with this approach: + * + * - \a alloc() is a O(1) operation. + * - no mutex is used inside alloc(). It is assumed that synchronization + * will be used in higher abstraction by application anyway. + * - no \a free() is required. All chunks will be deleted when the pool is + * destroyed. + * + * The performance gained on some systems can be as high as 30x speed up + * against \a malloc() and \a free() on certain configurations, but of + * course your mileage may vary. + * + * For more information, see \ref PJ_POOL_GROUP + * + * + * @subsection os_abstract_feat Operating System Abstraction + * + * PJLIB has abstractions for features that are normally not portable + * across operating systems: + * - @ref PJ_THREAD + *\n + * Portable thread manipulation. + * - @ref PJ_TLS + *\n + * Storing data in thread's private data. + * - @ref PJ_MUTEX + *\n + * Mutual exclusion protection. + * - @ref PJ_SEM + *\n + * Semaphores. + * - @ref PJ_ATOMIC + *\n + * Atomic variables and their operations. + * - @ref PJ_CRIT_SEC + *\n + * Fast locking of critical sections. + * - @ref PJ_LOCK + *\n + * High level abstraction for lock objects. + * - @ref PJ_EVENT + *\n + * Event object. + * - @ref PJ_TIME + *\n + * Portable time manipulation. + * - @ref PJ_TIMESTAMP + *\n + * High resolution time value. + * - etc. + * + * + * @subsection ll_network_io_sec Low-Level Network I/O + * + * PJLIB has very portable abstraction and fairly complete set of API for + * doing network I/O communications. At the lowest level, PJLIB provides: + * + * - @ref PJ_SOCK + *\n + * A highly portable socket abstraction, runs on all kind of + * network APIs such as standard BSD socket, Windows socket, Linux + * \b kernel socket, PalmOS networking API, etc. + * + * - @ref pj_addr_resolve + *\n + * Portable address resolution, which implements #pj_gethostbyname(). + * + * - @ref PJ_SOCK_SELECT + *\n + * A portable \a select() like API (#pj_sock_select()) which can be + * implemented with various back-end. + * + * + * + * @subsection timer_mgmt_sec Timer Management + * + * A passive framework for managing timer, see @ref PJ_TIMER for more info. + * There is also function to retrieve high resolution timestamp + * from the system (see @ref PJ_TIMESTAMP). + * + * + * @subsection data_struct_sec Various Data Structures + * + * Various data structures are provided in the library: + * + * - @ref PJ_PSTR + * - @ref PJ_ARRAY + * - @ref PJ_HASH + * - @ref PJ_LIST + * - @ref PJ_RBTREE + * + * + * @subsection exception_sec Exception Construct + * + * A convenient TRY/CATCH like construct to propagate errors, which by + * default are used by the @ref PJ_POOL_GROUP "memory pool" and + * the lexical scanner in pjlib-util. The exception + * construct can be used to write programs like below: + * + *
+ *    #define SYNTAX_ERROR  1
+ *
+ *    PJ_TRY {
+ *       msg = NULL;
+ *       msg = parse_msg(buf, len);
+ *    }
+ *    PJ_CATCH ( SYNTAX_ERROR ) {
+ *       .. handle error ..
+ *    }
+ *    PJ_END;
+ * 
+ * + * Please see @ref PJ_EXCEPT for more information. + * + * + * @subsection logging_sec Logging Facility + * + * PJLIB @ref PJ_LOG consists of macros to write logging information to + * some output device. Some of the features of the logging facility: + * + * - the verbosity can be fine-tuned both at compile time (to control + * the library size) or run-time (to control the verbosity of the + * information). + * - output device is configurable (e.g. stdout, printk, file, etc.) + * - log decoration is configurable. + * + * See @ref PJ_LOG for more information. + * + * + * @subsection guid_gen_sec Random and GUID Generation + * + * PJLIB provides facility to create random string + * (#pj_create_random_string()) or globally unique identifier + * (see @ref PJ_GUID). + * + * + * + * @section configure_app_sec Configuring Application to use PJLIB + * + * @subsection pjlib_compil_sec Building PJLIB + * + * Follow the instructions in \ref pjlib_build_sys_pg to build + * PJLIB. + * + * @subsection pjlib_compil_app_sec Building Applications with PJLIB + * + * Use the following settings when building applications with PJLIB. + * + * @subsubsection compil_inc_dir_sec Include Search Path + * + * Add this to your include search path ($PJLIB is PJLIB root directory): + *
+ *   $PJLIB/include
+ * 
+ * + * @subsubsection compil_inc_file_sec Include PJLIB Header + * + * To include all PJLIB headers: + * \verbatim + #include + \endverbatim + * + * Alternatively, you can include individual PJLIB headers like this: + * \verbatim + #include + #include + \endverbatim + * + * + * @subsubsection compil_lib_dir_sec Library Path + * + * Add this to your library search path: + *
+ *   $PJLIB/lib
+ * 
+ * + * Then add the appropriate PJLIB library to your link specification. For + * example, you would add \c libpj-i386-linux-gcc.a when you're building + * applications in Linux. + * + * + * @subsection pjlib_fundamentals_sec Principles in Using PJLIB + * + * Few things that you \b MUST do when using PJLIB, to make sure that + * you create trully portable applications. + * + * @subsubsection call_pjlib_init_sec Call pj_init() + * + * Before you do anything else, call \c pj_init(). This would make sure that + * PJLIB system is properly set up. + * + * @subsubsection no_ansi_subsec Do NOT Use ANSI C + * + * Contrary to popular teaching, ANSI C (and LIBC) is not the most portable + * library in the world, nor it's the most ubiquitous. For example, LIBC + * is not available in Linux kernel. Also normally LIBC will be excluded + * from compilation of RTOSes to reduce size. + * + * So for maximum portability, do NOT use ANSI C. Do not even try to include + * any other header files outside . Stick with the functionalities + * provided by PJLIB. + * + * + * @subsubsection string_rep_subsubsec Use pj_str_t instead of C Strings + * + * PJLIB uses pj_str_t instead of normal C strings. You SHOULD follow this + * convention too. Remember, ANSI string-h is not always available. And + * PJLIB string is faster! + * + * @subsubsection mem_alloc_subsubsec Use Pool for Memory Allocations + * + * You MUST NOT use \a malloc() or any other memory allocation functions. + * Use PJLIB @ref PJ_POOL_GROUP instead! It's faster and most portable. + * + * @subsection logging_subsubsec Use Logging for Text Display + * + * DO NOT use for text output. Use PJLIB @ref PJ_LOG instead. + * + * + * @section porting_pjlib_sec0 Porting PJLIB + * + * Please see \ref porting_pjlib_pg page on more information to port + * PJLIB to new target. + * + * @section enjoy_sec Enjoy Using PJLIB! + * + * We hope that you find PJLIB usefull for your application. If you + * have any questions, suggestions, critics, bug fixes, or anything + * else, we would be happy to hear it. + * + * Enjoy using PJLIB! + * + * Benny Prijono < bennylp at pjsip dot org > + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + CODING CONVENTION + */ + +/** + * @page pjlib_coding_convention_page Coding Convention + * + * Before you submit your code/patches to be included with PJLIB, you must + * make sure that your code is compliant with PJLIB coding convention. + * This is very important! Otherwise we would not accept your code. + * + * @section coding_conv_editor_sec Editor Settings + * + * The single most important thing in the whole coding convention is editor + * settings. It's more important than the correctness of your code (bugs will + * only crash the system, but incorrect tab size is mental!). + * + * Kindly set your editor as follows: + * - tab size to \b 8. + * - indentation to \b 4. + * + * With \c vi, you can do it with: + *
+ *  :se ts=8
+ *  :se sts=4
+ * 
+ * + * You should replace tab with eight spaces. + * + * @section coding_conv_detail_sec Coding Style + * + * Coding style MUST strictly follow K&R style. The rest of coding style + * must follow current style. You SHOULD be able to observe the style + * currently used by PJLIB from PJLIB sources, and apply the style to your + * code. If you're not able to do simple thing like to observe PJLIB + * coding style from the sources, then logic dictates that your ability to + * observe more difficult area in PJLIB such as memory allocation strategy, + * concurrency, etc is questionable. + * + * @section coding_conv_comment_sec Commenting Your Code + * + * Public API (e.g. in header files) MUST have doxygen compliant comments. + * + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + BUILDING AND INSTALLING PJLIB + */ + +/** + * @page pjlib_build_sys_pg Building, and Installing PJLIB + * + * @section build_sys_install_sec Build and Installation + * + * \note + * The most up-to-date information on building and installing PJLIB + * should be found in the website, under "Getting Started" document. + * More over, the new PJLIB build system is now based on autoconf, + * so some of the information here might not be relevant anymore + * (although most still are, since the autoconf script still use + * the old Makefile system as the backend). + * + * @subsection build_sys_install_win32_sec Visual Studio + * + * The PJLIB Visual Studio workspace supports the building of PJLIB + * for Win32 target. Although currently only the Visual Studio 6 Workspace is + * actively maintained, developers with later version of Visual Studio + * can easily imports VS6 workspace into their IDE. + * + * To start building PJLIB projects with Visual Studio 6 or later, open + * the \a workspace file in the corresponding \b \c build directory. You have + * several choices on which \a dsw file to open: + \verbatim + $PJPROJECT/pjlib/build/pjlib.dsw + $PJPROJECT/pjsip/build/pjsip.dsw + ..etc + \endverbatim + * + * The easiest way is to open pjsip_apps.dsw file in \b \c $PJPROJECT/pjsip-apps/build + * directory, and build pjsua project or the samples project. + * However this will not build the complete projects. + * For example, the PJLIB test is not included in this workspace. + * To build the complete projects, you must + * open and build each \a dsw file in \c build directory in each + * subprojects. For example, to open the complete PJLIB workspace, open + * pjlib.dsw in $PJPROJECT/pjlib/build directory. + * + * + * @subsubsection config_site_create_vc_sec Create config_site.h + * + * The file $PJPROJECT/pjlib/include/pj/config_site.h + * is supposed to contain configuration that is specific to your site/target. + * This file is not part of PJLIB, so you must create it yourself. Normally + * you just need to create a blank file. + * + * The reason why it's not included in PJLIB is so that you would not accidently + * overwrite your site configuration. + * + * If you fail to do this, Visual C will complain with error like: + * + * "fatal error C1083: Cannot open include file: 'pj/config_site.h': No such file + * or directory". + * + * @subsubsection build_vc_subsubsec Build the Projects + * + * Just hit the build button! + * + * + * @subsection build_sys_install_unix_sec Make System + * + * For other targets, PJLIB provides a rather comprehensive build system + * that uses GNU \a make (and only GNU \a make will work). + * Currently, the build system supports building * PJLIB for these targets: + * - i386/Win32/mingw + * - i386/Linux + * - i386/Linux (kernel) + * - alpha/linux + * - sparc/SunOS + * - etc.. + * + * + * @subsubsection build_req_sec Requirements + * + * In order to use the \c make based build system, you MUST have: + * + * - GNU make + *\n + * The Makefiles heavily utilize GNU make commands which most likely + * are not available in other \c make system. + * - bash shell is recommended. + *\n + * Specificly, there is a command "echo -n" which may not work + * in other shells. This command is used when generating dependencies + * (make dep) and it's located in + * $PJPROJECT/build/rules.mak. + * - ar, ranlib from GNU binutils + *\n + * In your system has different ar or ranlib (e.g. they + * may have been installed as gar and granlib), then + * either you create the relevant symbolic links, or modify + * $PJPROJECT/build/cc-gcc.mak and rename ar and + * ranlib to the appropriate names. + * - gcc to generate dependency. + *\n + * Currently the build system uses "gcc -MM" to generate build + * dependencies. If gcc is not desired to generate dependency, + * then either you don't run make dep, or edit + * $PJPROJECT/build/rules.mak to calculate dependency using + * your prefered method. (And let me know when you do so so that I can + * update the file. :) ) + * + * @subsubsection build_overview_sec Building the Project + * + * Generally, steps required to build the PJLIB are: + * + \verbatim + $ cd /home/user/pjproject + $ ./configure + $ touch pjlib/include/pj/config_site.h + $ make dep + $ make + \endverbatim + * + * The above process will build all static libraries and all applications. + * + * \note the configure script is not a proper autoconf script, + * but rather a simple shell script to detect current host. This script + * currently does not support cross-compilation. + * + * \note For Linux kernel target, there are additional steps required, which + * will be explained in section \ref linux_kern_target_subsec. + * + * @subsubsection build_mak_sec Cross Compilation + * + * For cross compilation, you will need to edit the \c build.mak file in + * \c $PJPROJECT root directory manually. Please see README-configure file + * in the root directory for more information. + * + * For Linux kernel target, you are also required to declare the following + * variables in this file: + * - \c KERNEL_DIR: full path of kernel source tree. + * - \c KERNEL_ARCH: kernel ARCH options (e.g. "ARCH=um"), or leave blank + * for default. + * - \c PJPROJECT_DIR: full path of PJPROJECT source tree. + * + * Apart from these, there are also additional steps required to build + * Linux kernel target, which will be explained in \ref linux_kern_target_subsec. + * + * @subsubsection build_dir_sec Files in "build" Directory + * + * The *.mak files in \c $PJPROJECT/build directory are used to specify + * the configuration for the specified compiler, target machine target + * operating system, and host options. These files will be executed + * (included) by \a make during building process, depending on the values + * specified in $PJPROJECT/build.mak file. + * + * Normally you don't need to edit these files, except when you're porting + * PJLIB to new target. + * + * Below are the description of some files in this directory: + * + * - rules.mak: contains generic rules always included during make. + * - cc-gcc.mak: rules when gcc is used for compiler. + * - cc-vc.mak: rules when MSVC compiler is used. + * - host-mingw.mak: rules for building in mingw host. + * - host-unix.mak: rules for building in Unix/Posix host. + * - host-win32.mak: rules for building in Win32 command console + * (only valid when VC is used). + * - m-i386.mak: rules when target machine is an i386 processor. + * - m-m68k.mak: rules when target machine is an m68k processor. + * - os-linux.mak: rules when target OS is Linux. + * - os-linux-kernel.mak: rules when PJLIB is to be build as + * part of Linux kernel. + * - os-win32.mak: rules when target OS is Win32. + * + * + * @subsubsection config_site_create_sec Create config_site.h + * + * The file $PJPROJECT/pjlib/include/pj/config_site.h + * is supposed to contain configuration that is specific to your site/target. + * This file is not part of PJLIB, so you must create it yourself. + * + * The reason why it's not included in PJLIB is so that you would not accidently + * overwrite your site configuration. + * + * + * @subsubsection invoking_make_sec Invoking make + * + * Normally, \a make is invoked in \c build directory under each project. + * For example, to build PJLIB, you would invoke \a make in + * \c $PJPROJECT/pjlib/build directory like below: + * + \verbatim + $ cd pjlib/build + $ make + \endverbatim + * + * Alternatively you may invoke make in $PJPROJECT + * directory, to build all projects under that directory (e.g. + * PJLIB, PJSIP, etc.). + * + * + * @subsubsection linux_kern_target_subsec Linux Kernel Target + * + * \note + * BUILDING APPLICATIONS IN LINUX KERNEL MODE IS A VERY DANGEROUS BUSINESS. + * YOU MAY CRASH THE WHOLE OF YOUR SYSTEM, CORRUPT YOUR HARDISK, ETC. PJLIB + * KERNEL MODULES ARE STILL IN EXPERIMENTAL PHASE. DO NOT RUN IT IN PRODUCTION + * SYSTEMS OR OTHER SYSTEMS WHERE RISK OF LOSS OF DATA IS NOT ACCEPTABLE. + * YOU HAVE BEEN WARNED. + * + * \note + * User Mode Linux (UML) provides excellent way to experiment with Linux + * kernel without risking the stability of the host system. See + * http://user-mode-linux.sourceforge.net for details. + * + * \note + * I only use UML to experiment with PJLIB kernel modules. + * I wouldn't be so foolish to use my host Linux machine to experiment + * with this. + * + * \note + * You have been warned. + * + * For building PJLIB for Linux kernel target, there are additional steps required. + * In general, the additional tasks are: + * - Declare some more variables in build.mak file (this + * has been explained in \ref build_mak_sec above). + * - Perform these two small modifications in kernel source tree. + * + * There are two small modification need to be applied to the kernel tree. + * + * 1. Edit Makefile in kernel root source tree. + * + * Add the following lines at the end of the Makefile in your + * $KERNEL_SRC dir: + \verbatim +script: + $(SCRIPT) + \endverbatim + * + * \note Remember to replace spaces with tab in the Makefile. + * + * The modification above is needed to capture kernel's \c $CFLAGS and + * \c $CFLAGS_MODULE which will be used for PJLIB's compilation. + * + * 2. Add Additional Exports. + * + * We need the kernel to export some more symbols for our use. So we declare + * the additional symbols to be exported in extra-exports.c file, and add + * a this file to be compiled into the kernel: + * + * - Copy the file extra-exports.c from pjlib/src/pj + * directory to $KERNEL_SRC/kernel/ directory. + * - Edit Makefile in that directory, and add this line + * somewhere after the declaration of that variable: + \verbatim +obj-y += extra-exports.o + \endverbatim + * + * To illustrate what have been done in your kernel source tree, below + * is screenshot of my kernel source tree _after_ the modification. + * + \verbatim +[root@vpc-linux linux-2.6.7]# pwd +/usr/src/linux-2.6.7 +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# tail Makefile + +endif # skip-makefile + +FORCE: + +.PHONY: script + +script: + $(SCRIPT) + +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# head kernel/extra-exports.c +#include +#include + +EXPORT_SYMBOL(sys_select); + +EXPORT_SYMBOL(sys_epoll_create); +EXPORT_SYMBOL(sys_epoll_ctl); +EXPORT_SYMBOL(sys_epoll_wait); + +EXPORT_SYMBOL(sys_socket); +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# head -15 kernel/Makefile +# +# Makefile for the linux kernel. +# + +obj-y = sched.o fork.o exec_domain.o panic.o printk.o profile.o \ + exit.o itimer.o time.o softirq.o resource.o \ + sysctl.o capability.o ptrace.o timer.o user.o \ + signal.o sys.o kmod.o workqueue.o pid.o \ + rcupdate.o intermodule.o extable.o params.o posix-timers.o \ + kthread.o + +obj-y += extra-exports.o + +obj-$(CONFIG_FUTEX) += futex.o +obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o +[root@vpc-linux linux-2.6.7]# + + \endverbatim + * + * Then you must rebuild the kernel. + * If you fail to do this, you won't be able to insmod pjlib. + * + * \note You will see a lots of warning messages during pjlib-test compilation. + * The warning messages complain about unresolved symbols which are defined + * in pjlib module. You can safely ignore these warnings. However, you can not + * ignore warnings about non-pjlib unresolved symbols. + * + * + * @subsection makefile_explained_sec Makefile Explained + * + * The \a Makefile for each project (e.g. PJLIB, PJSIP, etc) should be + * very similar in the contents. The Makefile is located under \c build + * directory in each project subdir. + * + * @subsubsection pjlib_makefile_subsec PJLIB Makefile. + * + * Below is PJLIB's Makefile: + * + * \include build/Makefile + * + * @subsubsection pjlib_os_makefile_subsec PJLIB os-linux.mak. + * + * Below is file os-linux.mak file in + * $PJPROJECT/pjlib/build directory, + * which is OS specific configuration file for Linux target that is specific + * for PJLIB project. For \b global OS specific configuration, please see + * $PJPROJECT/build/os-*.mak. + * + * \include build/os-linux.mak + * + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + PORTING PJLIB + */ + +/** + * @page porting_pjlib_pg Porting PJLIB + * + * \note + * Since version 0.5.8, PJLIB build system is now based on autoconf, so + * most of the time we shouldn't need to apply the tweakings below to get + * PJLIB working on a new platform. However, since the autoconf build system + * still uses the old Makefile build system, the information below may still + * be useful for reference. + * + * + * @section new_arch_sec Porting to New CPU Architecture + * + * Below is step-by-step guide to add support for new CPU architecture. + * This sample is based on porting to Alpha architecture; however steps for + * porting to other CPU architectures should be pretty similar. + * + * Also note that in this example, the operating system used is Linux. + * Should you wish to add support for new operating system, then follow + * the next section \ref porting_os_sec. + * + * Step-by-step guide to port to new CPU architecture: + * - decide the name for the new architecture. In this case, we choose + * alpha. + * - edit file $PJPROJECT/build.mak, and add new section for + * the new target: + *
+ *      #
+ *      # Linux alpha, gcc
+ *      #
+ *      export MACHINE_NAME := alpha
+ *      export OS_NAME := linux
+ *      export CC_NAME := gcc
+ *      export HOST_NAME := unix
+ *    
+ * + * - create a new file $PJPROJECT/build/m-alpha.mak. + * Alternatively create a copy from other file in this directory. + * The contents of this file will look something like: + *
+ *      export M_CFLAGS := $(CC_DEF)PJ_M_ALPHA=1
+ *      export M_CXXFLAGS :=
+ *      export M_LDFLAGS :=
+ *      export M_SOURCES :=
+ *    
+ * - create a new file $PJPROJECT/pjlib/include/pj/compat/m_alpha.h. + * Alternatively create a copy from other header file in this directory. + * The contents of this file will look something like: + *
+ *      #define PJ_HAS_PENTIUM          0
+ *      #define PJ_IS_LITTLE_ENDIAN     1
+ *      #define PJ_IS_BIG_ENDIAN        0
+ *    
+ * - edit pjlib/include/pj/config.h. Add new processor + * configuration in this header file, like follows: + *
+ *      ...
+ *      #elif defined (PJ_M_ALPHA) && PJ_M_ALPHA != 0
+ *      #   include 
+ *      ...
+ *    
+ * - done. Build PJLIB with: + *
+ *      $ cd $PJPROJECT/pjlib/build
+ *      $ make dep
+ *      $ make clean
+ *      $ make
+ *    
+ * + * @section porting_os_sec Porting to New Operating System Target + * + * This section will try to give you rough guideline on how to + * port PJLIB to a new target. As a sample, we give the target a name tag, + * for example xos (for X OS). + * + * @subsection new_compat_os_h_file_sec Create New Compat Header File + * + * You'll need to create a new header file + * include/pj/compat/os_xos.h. You can copy as a + * template other header file and edit it accordingly. + * + * @subsection modify_config_h_file_sec Modify config.h + * + * Then modify file include/pj/config.h to include + * this file accordingly (e.g. when macro PJ_XOS is + * defined): + * + \verbatim + ... + #elif defined(PJ_XOS) + # include + #else + #... + \endverbatim + * + * @subsection new_target_mak_file_sec Create New Global Make Config File + * + * Then you'll need to create global configuration file that + * is specific for this OS, i.e. os-xos.mak in + * $PJPROJECT/build directory. + * + * At very minimum, the file will normally need to define + * PJ_XOS=1 in the \c CFLAGS section: + * + \verbatim +# +# $PJPROJECT/build/os-xos.mak: +# +export OS_CFLAGS := $(CC_DEF)PJ_XOS=1 +export OS_CXXFLAGS := +export OS_LDFLAGS := +export OS_SOURCES := + \endverbatim + * + * + * @subsection new_target_prj_mak_file_sec Create New Project's Make Config File + * + * Then you'll need to create xos-specific configuration file + * for PJLIB. This file is also named os-xos.mak, + * but its located in pjlib/build directory. + * This file will specify source files that are specific to + * this OS to be included in the build process. + * + * Below is a sample: + \verbatim +# +# pjlib/build/os-xos.mak: +# XOS specific configuration for PJLIB. +# +export PJLIB_OBJS += os_core_xos.o \ + os_error_unix.o \ + os_time_ansi.o +export TEST_OBJS += main.o +export TARGETS = pjlib pjlib-test + \endverbatim + * + * @subsection new_target_src_sec Create and Edit Source Files + * + * You'll normally need to create at least these files: + * - os_core_xos.c: core OS specific + * functionality. + * - os_timestamp_xos.c: how to get timestamp + * in this OS. + * + * Depending on how things are done in your OS, you may need + * to create these files: + * - os_error_*.c: how to manipulate + * OS error codes. Alternatively you may use existing + * os_error_unix.c if the OS has \c errno and + * \c strerror() function. + * - ioqueue_*.c: if the OS has specific method + * to perform asynchronous I/O. Alternatively you may + * use existing ioqueue_select.c if the OS supports + * \c select() function call. + * - sock_*.c: if the OS has specific method + * to perform socket communication. Alternatively you may + * use existing sock_bsd.c if the OS supports + * BSD socket API, and edit include/pj/compat/socket.h + * file accordingly. + * + * You will also need to check various files in + * include/pj/compat/xxx.h, to see if they're + * compatible with your OS. + * + * @subsection new_target_build_file_sec Build The Project + * + * After basic building blocks have been created for the OS, then + * the easiest way to see which parts need to be fixed is by building + * the project and see the error messages. + * + * @subsection new_target_edit_vs_new_file_sec Editing Existing Files vs Creating New File + * + * When you encounter compatibility errors in PJLIB during porting, + * you have three options on how to fix the error: + * - edit the existing *.c file, and give it #ifdef + * switch for the new OS, or + * - edit include/pj/compat/*.h instead, or + * - create a totally new file. + * + * Basicly there is no strict rule on which approach is the best + * to use, however the following guidelines may be used: + * - if the file is expected to be completely different than + * any existing file, then perhaps you should create a completely + * new file. For example, file os_core_xxx.c will + * normally be different for each OS flavour. + * - if the difference can be localized in include/compat + * header file, and existing #ifdef switch is there, + * then preferably you should edit this include/compat + * header file. + * - if the existing *.c file has #ifdef switch, + * then you may add another #elif switch there. This + * normally is used for behaviors that are not totally + * different on each platform. + * - other than that above, use your own judgement on whether + * to edit the file or create new file etc. + */ + +#endif /* __PJ_DOXYGEN_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/errno.h b/src/tuya_p2p/pjproject/pjlib/include/pj/errno.h new file mode 100755 index 000000000..6fe942964 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/errno.h @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ERRNO_H__ +#define __PJ_ERRNO_H__ + +/** + * @file errno.h + * @brief PJLIB Error Subsystem + */ +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_errno Error Subsystem + * @{ + * + * The PJLIB Error Subsystem is a framework to unify all error codes + * produced by all components into a single error space, and provide + * uniform set of APIs to access them. With this framework, any error + * codes are encoded as pj_status_t value. The framework is extensible, + * application may register new error spaces to be recognized by + * the framework. + * + * @section pj_errno_retval Return Values + * + * All functions that returns @a pj_status_t returns @a PJ_SUCCESS if the + * operation was completed successfully, or non-zero value to indicate + * error. If the error came from operating system, then the native error + * code is translated/folded into PJLIB's error namespace by using + * #PJ_STATUS_FROM_OS() macro. The function will do this automatically + * before returning the error to caller. + * + * @section err_services Retrieving and Displaying Error Messages + * + * The framework provides the following APIs to retrieve and/or display + * error messages: + * + * - #pj_strerror(): this is the base API to retrieve error string + * description for the specified pj_status_t error code. + * + * - #PJ_PERROR() macro: use this macro similar to PJ_LOG to format + * an error message and display them to the log + * + * - #pj_perror(): this function is similar to PJ_PERROR() but unlike + * #PJ_PERROR(), this function will always be included in the + * link process. Due to this reason, prefer to use #PJ_PERROR() + * if the application is concerned about the executable size. + * + * Application MUST NOT pass native error codes (such as error code from + * functions like GetLastError() or errno) to PJLIB functions expecting + * @a pj_status_t. + * + * @section err_extending Extending the Error Space + * + * Application may register new error space to be recognized by the + * framework by using #pj_register_strerror(). Use the range started + * from PJ_ERRNO_START_USER to avoid conflict with existing error + * spaces. + * + */ + +/** + * Guidelines on error message length. + */ +#define PJ_ERR_MSG_SIZE 80 + +/** + * Buffer for title string of #PJ_PERROR(). + */ +#ifndef PJ_PERROR_TITLE_BUF_SIZE +#define PJ_PERROR_TITLE_BUF_SIZE 120 +#endif + +/** + * Get the last platform error/status, folded into pj_status_t. + * @return OS dependent error code, folded into pj_status_t. + * @remark This function gets errno, or calls GetLastError() function and + * convert the code into pj_status_t with PJ_STATUS_FROM_OS. Do + * not call this for socket functions! + * @see pj_get_netos_error() + */ +PJ_DECL(pj_status_t) pj_get_os_error(void); + +/** + * Set last error. + * @param code pj_status_t + */ +PJ_DECL(void) pj_set_os_error(pj_status_t code); + +/** + * Get the last error from socket operations. + * @return Last socket error, folded into pj_status_t. + */ +PJ_DECL(pj_status_t) pj_get_netos_error(void); + +/** + * Set error code. + * @param code pj_status_t. + */ +PJ_DECL(void) pj_set_netos_error(pj_status_t code); + +/** + * Get the error message for the specified error code. The message + * string will be NULL terminated. + * + * @param statcode The error code. + * @param buf Buffer to hold the error message string. + * @param bufsize Size of the buffer. + * + * @return The error message as NULL terminated string, + * wrapped with pj_str_t. + */ +PJ_DECL(pj_str_t) pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize); + +/** + * A utility macro to print error message pertaining to the specified error + * code to the log. This macro will construct the error message title + * according to the 'title_fmt' argument, and add the error string pertaining + * to the error code after the title string. A colon (':') will be added + * automatically between the title and the error string. + * + * This function is similar to pj_perror() function, but has the advantage + * that the function call can be omitted from the link process if the + * log level argument is below PJ_LOG_MAX_LEVEL threshold. + * + * Note that the title string constructed from the title_fmt will be built on + * a string buffer which size is PJ_PERROR_TITLE_BUF_SIZE, which normally is + * allocated from the stack. By default this buffer size is small (around + * 120 characters). Application MUST ensure that the constructed title string + * will not exceed this limit, since not all platforms support truncating + * the string. + * + * @see pj_perror() + * + * @param level The logging verbosity level, valid values are 0-6. Lower + * number indicates higher importance, with level zero + * indicates fatal error. Only numeral argument is + * permitted (e.g. not variable). + * @param arg Enclosed 'printf' like arguments, with the following + * arguments: + * - the sender (NULL terminated string), + * - the error code (pj_status_t) + * - the format string (title_fmt), and + * - optional variable number of arguments suitable for the + * format string. + * + * Sample: + * \verbatim + PJ_PERROR(2, (__FILE__, PJ_EBUSY, "Error making %s", "coffee")); + \endverbatim + * @hideinitializer + */ +#define PJ_PERROR(level, arg) \ + do { \ + pj_perror_wrapper_##level(arg); \ + } while (0) + +/** + * A utility function to print error message pertaining to the specified error + * code to the log. This function will construct the error message title + * according to the 'title_fmt' argument, and add the error string pertaining + * to the error code after the title string. A colon (':') will be added + * automatically between the title and the error string. + * + * Unlike the PJ_PERROR() macro, this function takes the \a log_level argument + * as a normal argument, unlike in PJ_PERROR() where a numeral value must be + * given. However this function will always be linked to the executable, + * unlike PJ_PERROR() which can be omitted when the level is below the + * PJ_LOG_MAX_LEVEL. + * + * Note that the title string constructed from the title_fmt will be built on + * a string buffer which size is PJ_PERROR_TITLE_BUF_SIZE, which normally is + * allocated from the stack. By default this buffer size is small (around + * 120 characters). Application MUST ensure that the constructed title string + * will not exceed this limit, since not all platforms support truncating + * the string. + * + * @see PJ_PERROR() + */ +PJ_DECL(void) pj_perror(int log_level, const char *sender, pj_status_t status, const char *title_fmt, ...); + +/** + * Type of callback to be specified in #pj_register_strerror() + * + * @param e The error code to lookup. + * @param msg Buffer to store the error message. + * @param max Length of the buffer. + * + * @return The error string. + */ +typedef pj_str_t (*pj_error_callback)(pj_status_t e, char *msg, pj_size_t max); + +/** + * Register strerror message handler for the specified error space. + * Application can register its own handler to supply the error message + * for the specified error code range. This handler will be called + * by #pj_strerror(). + * + * @param start_code The starting error code where the handler should + * be called to retrieve the error message. + * @param err_space The size of error space. The error code range then + * will fall in start_code to start_code+err_space-1 + * range. + * @param f The handler to be called when #pj_strerror() is + * supplied with error code that falls into this range. + * + * @return PJ_SUCCESS or the specified error code. The + * registration may fail when the error space has been + * occupied by other handler, or when there are too many + * handlers registered to PJLIB. + */ +PJ_DECL(pj_status_t) pj_register_strerror(pj_status_t start_code, pj_status_t err_space, pj_error_callback f); + +/** + * @hideinitializer + * Return platform os error code folded into pj_status_t code. This is + * the macro that is used throughout the library for all PJLIB's functions + * that returns error from operating system. Application may override + * this macro to reduce size (e.g. by defining it to always return + * #PJ_EUNKNOWN). + * + * Note: + * This macro MUST return non-zero value regardless whether zero is + * passed as the argument. The reason is to protect logic error when + * the operating system doesn't report error codes properly. + * + * @param os_code Platform OS error code. This value may be evaluated + * more than once. + * @return The platform os error code folded into pj_status_t. + */ +#ifndef PJ_RETURN_OS_ERROR +#define PJ_RETURN_OS_ERROR(os_code) (os_code ? PJ_STATUS_FROM_OS(os_code) : -1) +#endif + +/** + * @hideinitializer + * Fold a platform specific error into an pj_status_t code. + * + * @param e The platform os error code. + * @return pj_status_t + * @warning Macro implementation; the syserr argument may be evaluated + * multiple times. + */ +#if PJ_NATIVE_ERR_POSITIVE +#define PJ_STATUS_FROM_OS(e) (e == 0 ? PJ_SUCCESS : e + PJ_ERRNO_START_SYS) +#else +#define PJ_STATUS_FROM_OS(e) (e == 0 ? PJ_SUCCESS : PJ_ERRNO_START_SYS - e) +#endif + +/** + * @hideinitializer + * Fold an pj_status_t code back to the native platform defined error. + * + * @param e The pj_status_t folded platform os error code. + * @return pj_os_err_type + * @warning macro implementation; the statcode argument may be evaluated + * multiple times. If the statcode was not created by + * pj_get_os_error or PJ_STATUS_FROM_OS, the results are undefined. + */ +#if PJ_NATIVE_ERR_POSITIVE +#define PJ_STATUS_TO_OS(e) (e == 0 ? PJ_SUCCESS : e - PJ_ERRNO_START_SYS) +#else +#define PJ_STATUS_TO_OS(e) (e == 0 ? PJ_SUCCESS : PJ_ERRNO_START_SYS - e) +#endif + +/** + * @defgroup pj_errnum PJLIB's Own Error Codes + * @ingroup pj_errno + * @{ + */ + +/** + * Use this macro to generate error message text for your error code, + * so that they look uniformly as the rest of the libraries. + * + * @param code The error code + * @param msg The error test. + */ +#ifndef PJ_BUILD_ERR +#define PJ_BUILD_ERR(code, msg) \ + { \ + code, msg " (" #code ")" \ + } +#endif + +/** + * @hideinitializer + * Unknown error has been reported. + */ +#define PJ_EUNKNOWN (PJ_ERRNO_START_STATUS + 1) /* 70001 */ +/** + * @hideinitializer + * The operation is pending and will be completed later. + */ +#define PJ_EPENDING (PJ_ERRNO_START_STATUS + 2) /* 70002 */ +/** + * @hideinitializer + * Too many connecting sockets. + */ +#define PJ_ETOOMANYCONN (PJ_ERRNO_START_STATUS + 3) /* 70003 */ +/** + * @hideinitializer + * Invalid argument. + */ +#define PJ_EINVAL (PJ_ERRNO_START_STATUS + 4) /* 70004 */ +/** + * @hideinitializer + * Name too long (eg. hostname too long). + */ +#define PJ_ENAMETOOLONG (PJ_ERRNO_START_STATUS + 5) /* 70005 */ +/** + * @hideinitializer + * Not found. + */ +#define PJ_ENOTFOUND (PJ_ERRNO_START_STATUS + 6) /* 70006 */ +/** + * @hideinitializer + * Not enough memory. + */ +#define PJ_ENOMEM (PJ_ERRNO_START_STATUS + 7) /* 70007 */ +/** + * @hideinitializer + * Bug detected! + */ +#define PJ_EBUG (PJ_ERRNO_START_STATUS + 8) /* 70008 */ +/** + * @hideinitializer + * Operation timed out. + */ +#define PJ_ETIMEDOUT (PJ_ERRNO_START_STATUS + 9) /* 70009 */ +/** + * @hideinitializer + * Too many objects. + */ +#define PJ_ETOOMANY (PJ_ERRNO_START_STATUS + 10) /* 70010 */ +/** + * @hideinitializer + * Object is busy. + */ +#define PJ_EBUSY (PJ_ERRNO_START_STATUS + 11) /* 70011 */ +/** + * @hideinitializer + * The specified option is not supported. + */ +#define PJ_ENOTSUP (PJ_ERRNO_START_STATUS + 12) /* 70012 */ +/** + * @hideinitializer + * Invalid operation. + */ +#define PJ_EINVALIDOP (PJ_ERRNO_START_STATUS + 13) /* 70013 */ +/** + * @hideinitializer + * Operation is cancelled. + */ +#define PJ_ECANCELLED (PJ_ERRNO_START_STATUS + 14) /* 70014 */ +/** + * @hideinitializer + * Object already exists. + */ +#define PJ_EEXISTS (PJ_ERRNO_START_STATUS + 15) /* 70015 */ +/** + * @hideinitializer + * End of file. + */ +#define PJ_EEOF (PJ_ERRNO_START_STATUS + 16) /* 70016 */ +/** + * @hideinitializer + * Size is too big. + */ +#define PJ_ETOOBIG (PJ_ERRNO_START_STATUS + 17) /* 70017 */ +/** + * @hideinitializer + * Error in gethostbyname(). This is a generic error returned when + * gethostbyname() has returned an error. + */ +#define PJ_ERESOLVE (PJ_ERRNO_START_STATUS + 18) /* 70018 */ +/** + * @hideinitializer + * Size is too small. + */ +#define PJ_ETOOSMALL (PJ_ERRNO_START_STATUS + 19) /* 70019 */ +/** + * @hideinitializer + * Ignored + */ +#define PJ_EIGNORED (PJ_ERRNO_START_STATUS + 20) /* 70020 */ +/** + * @hideinitializer + * IPv6 is not supported + */ +#define PJ_EIPV6NOTSUP (PJ_ERRNO_START_STATUS + 21) /* 70021 */ +/** + * @hideinitializer + * Unsupported address family + */ +#define PJ_EAFNOTSUP (PJ_ERRNO_START_STATUS + 22) /* 70022 */ +/** + * @hideinitializer + * Object no longer exists + */ +#define PJ_EGONE (PJ_ERRNO_START_STATUS + 23) /* 70023 */ +/** + * @hideinitializer + * Socket is stopped + */ +#define PJ_ESOCKETSTOP (PJ_ERRNO_START_STATUS + 24) /* 70024 */ + +/** @} */ /* pj_errnum */ + +/** @} */ /* pj_errno */ + +/** + * PJ_ERRNO_START is where PJLIB specific error values start. + */ +#define PJ_ERRNO_START 20000 + +/** + * PJ_ERRNO_SPACE_SIZE is the maximum number of errors in one of + * the error/status range below. + */ +#define PJ_ERRNO_SPACE_SIZE 50000 + +/** + * PJ_ERRNO_START_STATUS is where PJLIB specific status codes start. + * Effectively the error in this class would be 70000 - 119000. + */ +#define PJ_ERRNO_START_STATUS (PJ_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + +/** + * PJ_ERRNO_START_SYS converts platform specific error codes into + * pj_status_t values. + * Effectively the error in this class would be 120000 - 169000. + */ +#define PJ_ERRNO_START_SYS (PJ_ERRNO_START_STATUS + PJ_ERRNO_SPACE_SIZE) + +/** + * PJ_ERRNO_START_USER are reserved for applications that use error + * codes along with PJLIB codes. + * Effectively the error in this class would be 170000 - 219000. + */ +#define PJ_ERRNO_START_USER (PJ_ERRNO_START_SYS + PJ_ERRNO_SPACE_SIZE) + +/* + * Below are list of error spaces that have been taken so far: + * - PJSIP_ERRNO_START (PJ_ERRNO_START_USER) + * - PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE) + * - PJSIP_SIMPLE_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2) + * - PJLIB_UTIL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*3) + * - PJNATH_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*4) + * - PJMEDIA_AUDIODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5) + * - PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*6) + * - PJMEDIA_VIDEODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*7) + */ + +/** Internal */ +void pj_errno_clear_handlers(void); + +/****** Internal for PJ_PERROR *******/ + +/** + * @def pj_perror_wrapper_1(arg) + * Internal function to write log with verbosity 1. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 1. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 1 +#define pj_perror_wrapper_1(arg) pj_perror_1 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_1(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_1(arg) +#endif + +/** + * @def pj_perror_wrapper_2(arg) + * Internal function to write log with verbosity 2. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 2. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 2 +#define pj_perror_wrapper_2(arg) pj_perror_2 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_2(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_2(arg) +#endif + +/** + * @def pj_perror_wrapper_3(arg) + * Internal function to write log with verbosity 3. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 3. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 3 +#define pj_perror_wrapper_3(arg) pj_perror_3 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_3(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_3(arg) +#endif + +/** + * @def pj_perror_wrapper_4(arg) + * Internal function to write log with verbosity 4. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 4. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 4 +#define pj_perror_wrapper_4(arg) pj_perror_4 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_4(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_4(arg) +#endif + +/** + * @def pj_perror_wrapper_5(arg) + * Internal function to write log with verbosity 5. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 5. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 5 +#define pj_perror_wrapper_5(arg) pj_perror_5 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_5(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_5(arg) +#endif + +/** + * @def pj_perror_wrapper_6(arg) + * Internal function to write log with verbosity 6. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 6. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 6 +#define pj_perror_wrapper_6(arg) pj_perror_6 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_6(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_6(arg) +#endif + +PJ_END_DECL + +#endif /* __PJ_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/except.h b/src/tuya_p2p/pjproject/pjlib/include/pj/except.h new file mode 100755 index 000000000..df38f1136 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/except.h @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_EXCEPTION_H__ +#define __PJ_EXCEPTION_H__ + +/** + * @file except.h + * @brief Exception Handling in C. + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_EXCEPT Exception Handling + * @ingroup PJ_MISC + * @{ + * + * \section pj_except_sample_sec Quick Example + * + * For the impatient, take a look at some examples: + * - @ref page_pjlib_samples_except_c + * - @ref page_pjlib_exception_test + * + * \section pj_except_except Exception Handling + * + * This module provides exception handling syntactically similar to C++ in + * C language. In Win32 systems, it uses Windows Structured Exception + * Handling (SEH) if macro PJ_EXCEPTION_USE_WIN32_SEH is non-zero. + * Otherwise it will use setjmp() and longjmp(). + * + * On some platforms where setjmp/longjmp is not available, setjmp/longjmp + * implementation is provided. See for compatibility. + * + * The exception handling mechanism is completely thread safe, so the exception + * thrown by one thread will not interfere with other thread. + * + * The exception handling constructs are similar to C++. The blocks will be + * constructed similar to the following sample: + * + * \verbatim + #define NO_MEMORY 1 + #define SYNTAX_ERROR 2 + + int sample1() + { + PJ_USE_EXCEPTION; // declare local exception stack. + + PJ_TRY { + ...// do something.. + } + PJ_CATCH(NO_MEMORY) { + ... // handle exception 1 + } + PJ_END; + } + + int sample2() + { + PJ_USE_EXCEPTION; // declare local exception stack. + + PJ_TRY { + ...// do something.. + } + PJ_CATCH_ANY { + if (PJ_GET_EXCEPTION() == NO_MEMORY) + ...; // handle no memory situation + else if (PJ_GET_EXCEPTION() == SYNTAX_ERROR) + ...; // handle syntax error + } + PJ_END; + } + \endverbatim + * + * The above sample uses hard coded exception ID. It is @b strongly + * recommended that applications request a unique exception ID instead + * of hard coded value like above. + * + * \section pj_except_reg Exception ID Allocation + * + * To ensure that exception ID (number) are used consistently and to + * prevent ID collisions in an application, it is strongly suggested that + * applications allocate an exception ID for each possible exception + * type. As a bonus of this process, the application can identify + * the name of the exception when the particular exception is thrown. + * + * Exception ID management are performed with the following APIs: + * - #pj_exception_id_alloc(). + * - #pj_exception_id_free(). + * - #pj_exception_id_name(). + * + * + * PJLIB itself automatically allocates one exception id, i.e. + * #PJ_NO_MEMORY_EXCEPTION which is declared in . This exception + * ID is raised by default pool policy when it fails to allocate memory. + * + * CAVEATS: + * - unlike C++ exception, the scheme here won't call destructors of local + * objects if exception is thrown. Care must be taken when a function + * hold some resorce such as pool or mutex etc. + * - You CAN NOT make nested exception in one single function without using + * a nested PJ_USE_EXCEPTION. Samples: + \verbatim + void wrong_sample() + { + PJ_USE_EXCEPTION; + + PJ_TRY { + // Do stuffs + ... + } + PJ_CATCH_ANY { + // Do other stuffs + .... + .. + + // The following block is WRONG! You MUST declare + // PJ_USE_EXCEPTION once again in this block. + PJ_TRY { + .. + } + PJ_CATCH_ANY { + .. + } + PJ_END; + } + PJ_END; + } + + \endverbatim + + * - You MUST NOT exit the function inside the PJ_TRY block. The correct way + * is to return from the function after PJ_END block is executed. + * For example, the following code will yield crash not in this code, + * but rather in the subsequent execution of PJ_TRY block: + \verbatim + void wrong_sample() + { + PJ_USE_EXCEPTION; + + PJ_TRY { + // do some stuffs + ... + return; <======= DO NOT DO THIS! + } + PJ_CATCH_ANY { + } + PJ_END; + } + \endverbatim + + * - You can not provide more than PJ_CATCH or PJ_CATCH_ANY nor use PJ_CATCH + * and PJ_CATCH_ANY for a single PJ_TRY. + * - Exceptions will always be caught by the first handler (unlike C++ where + * exception is only caught if the type matches. + + * \section PJ_EX_KEYWORDS Keywords + * + * \subsection PJ_THROW PJ_THROW(expression) + * Throw an exception. The expression thrown is an integer as the result of + * the \a expression. This keyword can be specified anywhere within the + * program. + * + * \subsection PJ_USE_EXCEPTION PJ_USE_EXCEPTION + * Specify this in the variable definition section of the function block + * (or any blocks) to specify that the block has \a PJ_TRY/PJ_CATCH exception + * block. + * Actually, this is just a macro to declare local variable which is used to + * push the exception state to the exception stack. + * Note: you must specify PJ_USE_EXCEPTION as the last statement in the + * local variable declarations, since it may evaluate to nothing. + * + * \subsection PJ_TRY PJ_TRY + * The \a PJ_TRY keyword is typically followed by a block. If an exception is + * thrown in this block, then the execution will resume to the \a PJ_CATCH + * handler. + * + * \subsection PJ_CATCH PJ_CATCH(expression) + * The \a PJ_CATCH is normally followed by a block. This block will be executed + * if the exception being thrown is equal to the expression specified in the + * \a PJ_CATCH. + * + * \subsection PJ_CATCH_ANY PJ_CATCH_ANY + * The \a PJ_CATCH is normally followed by a block. This block will be executed + * if any exception was raised in the TRY block. + * + * \subsection PJ_END PJ_END + * Specify this keyword to mark the end of \a PJ_TRY / \a PJ_CATCH blocks. + * + * \subsection PJ_GET_EXCEPTION PJ_GET_EXCEPTION(void) + * Get the last exception thrown. This macro is normally called inside the + * \a PJ_CATCH or \a PJ_CATCH_ANY block, altough it can be used anywhere where + * the \a PJ_USE_EXCEPTION definition is in scope. + * + * + * \section pj_except_examples_sec Examples + * + * For some examples on how to use the exception construct, please see: + * - @ref page_pjlib_samples_except_c + * - @ref page_pjlib_exception_test + */ + +/** + * Allocate a unique exception id. + * Applications don't have to allocate a unique exception ID before using + * the exception construct. However, by doing so it ensures that there is + * no collisions of exception ID. + * + * As a bonus, when exception number is acquired through this function, + * the library can assign name to the exception (only if + * PJ_HAS_EXCEPTION_NAMES is enabled (default is yes)) and find out the + * exception name when it catches an exception. + * + * @param name Name to be associated with the exception ID. + * @param id Pointer to receive the ID. + * + * @return PJ_SUCCESS on success or PJ_ETOOMANY if the library + * is running out out ids. + */ +PJ_DECL(pj_status_t) pj_exception_id_alloc(const char *name, pj_exception_id_t *id); + +/** + * Free an exception id. + * + * @param id The exception ID. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_exception_id_free(pj_exception_id_t id); + +/** + * Retrieve name associated with the exception id. + * + * @param id The exception ID. + * + * @return The name associated with the specified ID. + */ +PJ_DECL(const char *) pj_exception_id_name(pj_exception_id_t id); + +/** @} */ + +#if defined(PJ_EXCEPTION_USE_WIN32_SEH) && PJ_EXCEPTION_USE_WIN32_SEH != 0 +/***************************************************************************** + ** + ** IMPLEMENTATION OF EXCEPTION USING WINDOWS SEH + ** + ****************************************************************************/ +#define WIN32_LEAN_AND_MEAN +#include + +PJ_IDECL_NO_RETURN(void) +pj_throw_exception_(pj_exception_id_t id) PJ_ATTR_NORETURN +{ + RaiseException(id, 1, 0, NULL); +} + +#define PJ_USE_EXCEPTION +#define PJ_TRY __try +#define PJ_CATCH(id) __except (GetExceptionCode() == id ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) +#define PJ_CATCH_ANY __except (EXCEPTION_EXECUTE_HANDLER) +#define PJ_END +#define PJ_THROW(id) pj_throw_exception_(id) +#define PJ_GET_EXCEPTION() GetExceptionCode() + +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +/***************************************************************************** + ** + ** IMPLEMENTATION OF EXCEPTION USING SYMBIAN LEAVE/TRAP FRAMEWORK + ** + ****************************************************************************/ + +/* To include this file, the source file must be compiled as + * C++ code! + */ +#ifdef __cplusplus + +class TPjException +{ +public: + int code_; +}; + +#define PJ_USE_EXCEPTION +#define PJ_TRY try +//#define PJ_CATCH(id) +#define PJ_CATCH_ANY catch (const TPjException &pj_excp_) +#define PJ_END +#define PJ_THROW(x_id) \ + do { \ + TPjException e; \ + e.code_ = x_id; \ + throw e; \ + } while (0) +#define PJ_GET_EXCEPTION() pj_excp_.code_ + +#else + +#define PJ_USE_EXCEPTION +#define PJ_TRY +#define PJ_CATCH_ANY if (0) +#define PJ_END +#define PJ_THROW(x_id) \ + do { \ + PJ_LOG(1, ("PJ_THROW", " error code = %d", x_id)); \ + } while (0) +#define PJ_GET_EXCEPTION() 0 + +#endif /* __cplusplus */ + +#else +/***************************************************************************** + ** + ** IMPLEMENTATION OF EXCEPTION USING GENERIC SETJMP/LONGJMP + ** + ****************************************************************************/ + +/** + * This structure (which should be invisible to user) manages the TRY handler + * stack. + */ +struct pj_exception_state_t { + pj_jmp_buf state; /**< jmp_buf. */ + struct pj_exception_state_t *prev; /**< Previous state in the list. */ +}; + +/** + * Throw exception. + * @param id Exception Id. + */ +PJ_DECL_NO_RETURN(void) +pj_throw_exception_(pj_exception_id_t id) PJ_ATTR_NORETURN; + +/** + * Push exception handler. + */ +PJ_DECL(void) pj_push_exception_handler_(struct pj_exception_state_t *rec); + +/** + * Pop exception handler. + */ +PJ_DECL(void) pj_pop_exception_handler_(struct pj_exception_state_t *rec); + +/** + * Declare that the function will use exception. + * @hideinitializer + */ +#define PJ_USE_EXCEPTION \ + struct pj_exception_state_t pj_x_except__; \ + int pj_x_code__ + +/** + * Start exception specification block. + * @hideinitializer + */ +#define PJ_TRY \ + if (1) { \ + pj_push_exception_handler_(&pj_x_except__); \ + pj_x_code__ = pj_setjmp(pj_x_except__.state); \ + if (pj_x_code__ == 0) +/** + * Catch the specified exception Id. + * @param id The exception number to catch. + * @hideinitializer + */ +#define PJ_CATCH(id) else if (pj_x_code__ == (id)) + +/** + * Catch any exception number. + * @hideinitializer + */ +#define PJ_CATCH_ANY else + +/** + * End of exception specification block. + * @hideinitializer + */ +#define PJ_END \ + pj_pop_exception_handler_(&pj_x_except__); \ + } \ + else \ + { \ + } + +/** + * Throw exception. + * @param exception_id The exception number. + * @hideinitializer + */ +#define PJ_THROW(exception_id) pj_throw_exception_(exception_id) + +/** + * Get current exception. + * @return Current exception code. + * @hideinitializer + */ +#define PJ_GET_EXCEPTION() (pj_x_code__) + +#endif /* PJ_EXCEPTION_USE_WIN32_SEH */ + +PJ_END_DECL + +#endif /* __PJ_EXCEPTION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h b/src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h new file mode 100755 index 000000000..053df8dd3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_FIFOBUF_H__ +#define __PJ_FIFOBUF_H__ + +#include + +PJ_BEGIN_DECL + +typedef struct pj_fifobuf_t pj_fifobuf_t; +struct pj_fifobuf_t { + char *first, *last; + char *ubegin, *uend; + int full; +}; + +PJ_DECL(void) pj_fifobuf_init(pj_fifobuf_t *fb, void *buffer, unsigned size); +PJ_DECL(unsigned) pj_fifobuf_max_size(pj_fifobuf_t *fb); +PJ_DECL(void *) pj_fifobuf_alloc(pj_fifobuf_t *fb, unsigned size); +PJ_DECL(pj_status_t) pj_fifobuf_unalloc(pj_fifobuf_t *fb, void *buf); +PJ_DECL(pj_status_t) pj_fifobuf_free(pj_fifobuf_t *fb, void *buf); + +PJ_END_DECL + +#endif /* __PJ_FIFOBUF_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h b/src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h new file mode 100755 index 000000000..2aec74604 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_FILE_ACCESS_H__ +#define __PJ_FILE_ACCESS_H__ + +/** + * @file file_access.h + * @brief File manipulation and access. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_FILE_ACCESS File Access + * @ingroup PJ_IO + * @{ + * + */ + +/** + * This structure describes file information, to be obtained by + * calling #pj_file_getstat(). The time information in this structure + * is in local time. + */ +typedef struct pj_file_stat { + pj_off_t size; /**< Total file size. */ + pj_time_val atime; /**< Time of last access. */ + pj_time_val mtime; /**< Time of last modification. */ + pj_time_val ctime; /**< Time of last creation. */ +} pj_file_stat; + +/** + * Returns non-zero if the specified file exists. + * + * @param filename The file name. + * + * @return Non-zero if the file exists. + */ +PJ_DECL(pj_bool_t) pj_file_exists(const char *filename); + +/** + * Returns the size of the file. + * + * @param filename The file name. + * + * @return The file size in bytes or -1 on error. + */ +PJ_DECL(pj_off_t) pj_file_size(const char *filename); + +/** + * Delete a file. + * + * @param filename The filename. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_file_delete(const char *filename); + +/** + * Move a \c oldname to \c newname. If \c newname already exists, + * it will be overwritten. + * + * @param oldname The file to rename. + * @param newname New filename to assign. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_file_move(const char *oldname, const char *newname); + +/** + * Return information about the specified file. The time information in + * the \c stat structure will be in local time. + * + * @param filename The filename. + * @param stat Pointer to variable to receive file information. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_file_getstat(const char *filename, pj_file_stat *stat); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_FILE_ACCESS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h b/src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h new file mode 100755 index 000000000..c3e198492 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_FILE_IO_H__ +#define __PJ_FILE_IO_H__ + +/** + * @file file_io.h + * @brief Simple file I/O abstraction. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_FILE_IO File I/O + * @ingroup PJ_IO + * @{ + * + * This file contains functionalities to perform file I/O. The file + * I/O can be implemented with various back-end, either using native + * file API or ANSI stream. + * + * @section pj_file_size_limit_sec Size Limits + * + * There may be limitation on the size that can be handled by the + * #pj_file_setpos() or #pj_file_getpos() functions. The API itself + * uses 64-bit integer for the file offset/position (where available); + * however some backends (such as ANSI) may only support signed 32-bit + * offset resolution. + * + * Reading and writing operation uses signed 32-bit integer to indicate + * the size. + * + * + */ + +/** + * These enumerations are used when opening file. Values PJ_O_RDONLY, + * PJ_O_WRONLY, and PJ_O_RDWR are mutually exclusive. Value PJ_O_APPEND + * can only be used when the file is opened for writing. + */ +enum pj_file_access { + PJ_O_RDONLY = 0x1101, /**< Open file for reading. */ + PJ_O_WRONLY = 0x1102, /**< Open file for writing. */ + PJ_O_RDWR = 0x1103, /**< Open file for reading and writing. + File will be truncated. */ + PJ_O_APPEND = 0x1108 /**< Append to existing file. */ +}; + +/** + * The seek directive when setting the file position with #pj_file_setpos. + */ +enum pj_file_seek_type { + PJ_SEEK_SET = 0x1201, /**< Offset from beginning of the file. */ + PJ_SEEK_CUR = 0x1202, /**< Offset from current position. */ + PJ_SEEK_END = 0x1203 /**< Size of the file plus offset. */ +}; + +/** + * Open the file as specified in \c pathname with the specified + * mode, and return the handle in \c fd. All files will be opened + * as binary. + * + * @param pool Pool to allocate memory for the new file descriptor. + * @param pathname The file name to open. + * @param flags Open flags, which is bitmask combination of + * #pj_file_access enum. The flag must be either + * PJ_O_RDONLY, PJ_O_WRONLY, or PJ_O_RDWR. When file + * writing is specified, existing file will be + * truncated unless PJ_O_APPEND is specified. + * @param fd The returned descriptor. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_open(pj_pool_t *pool, const char *pathname, unsigned flags, pj_oshandle_t *fd); + +/** + * Close an opened file descriptor. + * + * @param fd The file descriptor. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_close(pj_oshandle_t fd); + +/** + * Write data with the specified size to an opened file. + * + * @param fd The file descriptor. + * @param data Data to be written to the file. + * @param size On input, specifies the size of data to be written. + * On return, it contains the number of data actually + * written to the file. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_write(pj_oshandle_t fd, const void *data, pj_ssize_t *size); + +/** + * Read data from the specified file. When end-of-file condition is set, + * this function will return PJ_SUCCESS but the size will contain zero. + * + * @param fd The file descriptor. + * @param data Pointer to buffer to receive the data. + * @param size On input, specifies the maximum number of data to + * read from the file. On output, it contains the size + * of data actually read from the file. It will contain + * zero when EOF occurs. + * + * @return PJ_SUCCESS or the appropriate error code on error. + * When EOF occurs, the return is PJ_SUCCESS but size + * will report zero. + */ +PJ_DECL(pj_status_t) pj_file_read(pj_oshandle_t fd, void *data, pj_ssize_t *size); + +/** + * Set file position to new offset according to directive \c whence. + * + * @param fd The file descriptor. + * @param offset The new file position to set. + * @param whence The directive. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_setpos(pj_oshandle_t fd, pj_off_t offset, enum pj_file_seek_type whence); + +/** + * Get current file position. + * + * @param fd The file descriptor. + * @param pos On return contains the file position as measured + * from the beginning of the file. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_getpos(pj_oshandle_t fd, pj_off_t *pos); + +/** + * Flush file buffers. + * + * @param fd The file descriptor. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_flush(pj_oshandle_t fd); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_FILE_IO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/guid.h b/src/tuya_p2p/pjproject/pjlib/include/pj/guid.h new file mode 100755 index 000000000..8391ce866 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/guid.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_GUID_H__ +#define __PJ_GUID_H__ + +/** + * @file guid.h + * @brief GUID Globally Unique Identifier. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DS Data Structure. + */ +/** + * @defgroup PJ_GUID Globally Unique Identifier + * @ingroup PJ_DS + * @{ + * + * This module provides API to create string that is globally unique. + * If application doesn't require that strong requirement, it can just + * use #pj_create_random_string() instead. + */ + +/** + * PJ_GUID_STRING_LENGTH specifies length of GUID string. The value is + * dependent on the algorithm used internally to generate the GUID string. + * If real GUID generator is used, then the length will be between 32 and + * 36 bytes. Application should not assume which algorithm will + * be used by GUID generator. + * + * Regardless of the actual length of the GUID, it will not exceed + * PJ_GUID_MAX_LENGTH characters. + * + * @see pj_GUID_STRING_LENGTH() + * @see PJ_GUID_MAX_LENGTH + */ +PJ_DECL_DATA(const unsigned) PJ_GUID_STRING_LENGTH; + +/** + * Get #PJ_GUID_STRING_LENGTH constant. + */ +PJ_DECL(unsigned) pj_GUID_STRING_LENGTH(void); + +/** + * PJ_GUID_MAX_LENGTH specifies the maximum length of GUID string, + * regardless of which algorithm to use. + */ +#define PJ_GUID_MAX_LENGTH 36 + +/** + * Create a globally unique string, which length is PJ_GUID_STRING_LENGTH + * characters. Caller is responsible for preallocating the storage used + * in the string. + * + * @param str The string to store the result. + * + * @return The string. + */ +PJ_DECL(pj_str_t *) pj_generate_unique_string(pj_str_t *str); + +/** + * Create a globally unique string in lowercase, which length is + * PJ_GUID_STRING_LENGTH characters. Caller is responsible for preallocating + * the storage used in the string. + * + * @param str The string to store the result. + * + * @return The string. + */ +PJ_DECL(pj_str_t *) pj_generate_unique_string_lower(pj_str_t *str); + +/** + * Generate a unique string. + * + * @param pool Pool to allocate memory from. + * @param str The string. + */ +PJ_DECL(void) pj_create_unique_string(pj_pool_t *pool, pj_str_t *str); + +/** + * Generate a unique string in lowercase. + * + * @param pool Pool to allocate memory from. + * @param str The string. + */ +PJ_DECL(void) pj_create_unique_string_lower(pj_pool_t *pool, pj_str_t *str); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_GUID_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/hash.h b/src/tuya_p2p/pjproject/pjlib/include/pj/hash.h new file mode 100755 index 000000000..463cb185d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/hash.h @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_HASH_H__ +#define __PJ_HASH_H__ + +/** + * @file hash.h + * @brief Hash Table. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_HASH Hash Table + * @ingroup PJ_DS + * @{ + * A hash table is a dictionary in which keys are mapped to array positions by + * hash functions. Having the keys of more than one item map to the same + * position is called a collision. In this library, we will chain the nodes + * that have the same key in a list. + */ + +/** + * If this constant is used as keylen, then the key is interpreted as + * NULL terminated string. + */ +#define PJ_HASH_KEY_STRING ((unsigned)-1) + +/** + * This indicates the size of of each hash entry. + */ +#define PJ_HASH_ENTRY_BUF_SIZE (3 * sizeof(void *) + 2 * sizeof(pj_uint32_t)) + +/** + * Type declaration for entry buffer, used by #pj_hash_set_np() + */ +typedef void *pj_hash_entry_buf[(PJ_HASH_ENTRY_BUF_SIZE + sizeof(void *) - 1) / (sizeof(void *))]; + +/** + * This is the function that is used by the hash table to calculate hash value + * of the specified key. + * + * @param hval the initial hash value, or zero. + * @param key the key to calculate. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to treat + * the key as null terminated string. + * + * @return the hash value. + */ +PJ_DECL(pj_uint32_t) pj_hash_calc(pj_uint32_t hval, const void *key, unsigned keylen); + +/** + * Convert the key to lowercase and calculate the hash value. The resulting + * string is stored in \c result. + * + * @param hval The initial hash value, normally zero. + * @param result Optional. Buffer to store the result, which must be enough + * to hold the string. + * @param key The input key to be converted and calculated. + * + * @return The hash value. + */ +PJ_DECL(pj_uint32_t) pj_hash_calc_tolower(pj_uint32_t hval, char *result, const pj_str_t *key); + +/** + * Create a hash table with the specified 'bucket' size. + * + * @param pool the pool from which the hash table will be allocated from. + * @param size the bucket size, which will be round-up to the nearest 2^n-1 + * + * @return the hash table. + */ +PJ_DECL(pj_hash_table_t *) pj_hash_create(pj_pool_t *pool, unsigned size); + +/** + * Get the value associated with the specified key. + * + * @param ht the hash table. + * @param key the key to look for. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the + * string length of the key. + * @param hval if this argument is not NULL and the value is not zero, + * the value will be used as the computed hash value. If + * the argument is not NULL and the value is zero, it will + * be filled with the computed hash upon return. + * + * @return the value associated with the key, or NULL if the key is not found. + */ +PJ_DECL(void *) pj_hash_get(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval); + +/** + * Variant of #pj_hash_get() with the key being converted to lowercase when + * calculating the hash value. + * + * @see pj_hash_get() + */ +PJ_DECL(void *) pj_hash_get_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval); + +/** + * Associate/disassociate a value with the specified key. If value is not + * NULL and entry already exists, the entry's value will be overwritten. + * If value is not NULL and entry does not exist, a new one will be created + * with the specified pool. Otherwise if value is NULL, entry will be + * deleted if it exists. + * + * @param pool the pool to allocate the new entry if a new entry has to be + * created. + * @param ht the hash table. + * @param key the key. If pool is not specified, the key MUST point to + * buffer that remains valid for the duration of the entry. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the + * string length of the key. + * @param hval if the value is not zero, then the hash table will use + * this value to search the entry's index, otherwise it will + * compute the key. This value can be obtained when calling + * #pj_hash_get(). + * @param value value to be associated, or NULL to delete the entry with + * the specified key. + */ +PJ_DECL(void) +pj_hash_set(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, void *value); + +/** + * Variant of #pj_hash_set() with the key being converted to lowercase when + * calculating the hash value. + * + * @see pj_hash_set() + */ +PJ_DECL(void) +pj_hash_set_lower(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + void *value); + +/** + * Associate/disassociate a value with the specified key. This function works + * like #pj_hash_set(), except that it doesn't use pool (hence the np -- no + * pool suffix). If new entry needs to be allocated, it will use the entry_buf. + * + * @param ht the hash table. + * @param key the key. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the + * string length of the key. + * @param hval if the value is not zero, then the hash table will use + * this value to search the entry's index, otherwise it will + * compute the key. This value can be obtained when calling + * #pj_hash_get(). + * @param entry_buf Buffer which will be used for the new entry, when one needs + * to be created. + * @param value value to be associated, or NULL to delete the entry with + * the specified key. + */ +PJ_DECL(void) +pj_hash_set_np(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, pj_hash_entry_buf entry_buf, + void *value); + +/** + * Variant of #pj_hash_set_np() with the key being converted to lowercase + * when calculating the hash value. + * + * @see pj_hash_set_np() + */ +PJ_DECL(void) +pj_hash_set_np_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + pj_hash_entry_buf entry_buf, void *value); + +/** + * Get the total number of entries in the hash table. + * + * @param ht the hash table. + * + * @return the number of entries in the hash table. + */ +PJ_DECL(unsigned) pj_hash_count(pj_hash_table_t *ht); + +/** + * Get the iterator to the first element in the hash table. + * + * @param ht the hash table. + * @param it the iterator for iterating hash elements. + * + * @return the iterator to the hash element, or NULL if no element presents. + */ +PJ_DECL(pj_hash_iterator_t *) pj_hash_first(pj_hash_table_t *ht, pj_hash_iterator_t *it); + +/** + * Get the next element from the iterator. + * + * @param ht the hash table. + * @param it the hash iterator. + * + * @return the next iterator, or NULL if there's no more element. + */ +PJ_DECL(pj_hash_iterator_t *) pj_hash_next(pj_hash_table_t *ht, pj_hash_iterator_t *it); + +/** + * Get the value associated with a hash iterator. + * + * @param ht the hash table. + * @param it the hash iterator. + * + * @return the value associated with the current element in iterator. + */ +PJ_DECL(void *) pj_hash_this(pj_hash_table_t *ht, pj_hash_iterator_t *it); + +/** + * @} + */ + +PJ_END_DECL + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h new file mode 100755 index 000000000..cfaa75b59 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h @@ -0,0 +1,869 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_IOQUEUE_H__ +#define __PJ_IOQUEUE_H__ + +/** + * @file ioqueue.h + * @brief I/O Dispatching Mechanism + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_IO Input/Output + * @brief Input/Output + * @ingroup PJ_OS + * + * This section contains API building blocks to perform network I/O and + * communications. If provides: + * - @ref PJ_SOCK + *\n + * A highly portable socket abstraction, runs on all kind of + * network APIs such as standard BSD socket, Windows socket, Linux + * \b kernel socket, PalmOS networking API, etc. + * + * - @ref pj_addr_resolve + *\n + * Portable address resolution, which implements #pj_gethostbyname(). + * + * - @ref PJ_SOCK_SELECT + *\n + * A portable \a select() like API (#pj_sock_select()) which can be + * implemented with various back-ends. + * + * - @ref PJ_IOQUEUE + *\n + * Framework for dispatching network events. + * + * For more information see the modules below. + */ + +/** + * @defgroup PJ_IOQUEUE IOQueue: I/O Event Dispatching with Proactor Pattern + * @ingroup PJ_IO + * @{ + * + * I/O Queue provides API for performing asynchronous I/O operations. It + * conforms to proactor pattern, which allows application to submit an + * asynchronous operation and to be notified later when the operation has + * completed. + * + * The I/O Queue can work on both socket and file descriptors. For + * asynchronous file operations however, one must make sure that the correct + * file I/O back-end is used, because not all file I/O back-end can be + * used with the ioqueue. Please see \ref PJ_FILE_IO for more details. + * + * The framework works natively in platforms where asynchronous operation API + * exists, such as in Windows NT with IoCompletionPort/IOCP. In other + * platforms, the I/O queue abstracts the operating system's event poll API + * to provide semantics similar to IoCompletionPort with minimal penalties + * (i.e. per ioqueue and per handle mutex protection). + * + * The I/O queue provides more than just unified abstraction. It also: + * - makes sure that the operation uses the most effective way to utilize + * the underlying mechanism, to achieve the maximum theoritical + * throughput possible on a given platform. + * - choose the most efficient mechanism for event polling on a given + * platform. + * + * Currently, the I/O Queue is implemented using: + * - select(), as the common denominator, but the least + * efficient. Also the number of descriptor is limited to + * \c PJ_IOQUEUE_MAX_HANDLES (which by default is 64). + * - /dev/epoll on Linux (user mode and kernel mode), + * a much faster replacement for select() on Linux (and more importantly + * doesn't have limitation on number of descriptors). + * - I/O Completion ports on Windows NT/2000/XP, which is the most + * efficient way to dispatch events in Windows NT based OSes, and most + * importantly, it doesn't have the limit on how many handles to monitor. + * And it works with files (not only sockets) as well. + * + * + * \section pj_ioqueue_concurrency_sec Concurrency Rules + * + * The ioqueue has been fine tuned to allow multiple threads to poll the + * handles simultaneously, to maximize scalability when the application is + * running on multiprocessor systems. When more than one threads are polling + * the ioqueue and there are more than one handles are signaled, more than + * one threads will execute the callback simultaneously to serve the events. + * These parallel executions are completely safe when the events happen for + * two different handles. + * + * However, with multithreading, care must be taken when multiple events + * happen on the same handle, or when event is happening on a handle (and + * the callback is being executed) and application is performing + * unregistration to the handle at the same time. + * + * The treatments of above scenario differ according to the concurrency + * setting that are applied to the handle. + * + * \subsection pj_ioq_concur_set Concurrency Settings for Handles + * + * Concurrency can be set on per handle (key) basis, by using + * #pj_ioqueue_set_concurrency() function. The default key concurrency value + * for the handle is inherited from the key concurrency setting of the ioqueue, + * and the key concurrency setting for the ioqueue can be changed by using + * #pj_ioqueue_set_default_concurrency(). The default key concurrency setting + * for ioqueue itself is controlled by compile time setting + * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. + * + * Note that this key concurrency setting only controls whether multiple + * threads are allowed to operate on the same key at the same time. + * The ioqueue itself always allows multiple threads to enter the ioqeuue at + * the same time, and also simultaneous callback calls to differrent + * keys is always allowed regardless to the key concurrency setting. + * + * \subsection pj_ioq_parallel Parallel Callback Executions for the Same Handle + * + * Note that when key concurrency is enabled (i.e. parallel callback calls on + * the same key is allowed; this is the default setting), the ioqueue will only + * perform simultaneous callback executions on the same key when the key has + * invoked multiple pending operations. This could be done for example by + * calling #pj_ioqueue_recvfrom() more than once on the same key, each with + * the same key but different operation key (pj_ioqueue_op_key_t). With this + * scenario, when multiple packets arrive on the key at the same time, more + * than one threads may execute the callback simultaneously, each with the + * same key but different operation key. + * + * When there is only one pending operation on the key (e.g. there is only one + * #pj_ioqueue_recvfrom() invoked on the key), then events occuring to the + * same key will be queued by the ioqueue, thus no simultaneous callback calls + * will be performed. + * + * \subsection pj_ioq_allow_concur Concurrency is Enabled (Default Value) + * + * The default setting for the ioqueue is to allow multiple threads to + * execute callbacks for the same handle/key. This setting is selected to + * promote good performance and scalability for application. + * + * However this setting has a major drawback with regard to synchronization, + * and application MUST carefully follow the following guidelines to ensure + * that parallel access to the key does not cause problems: + * + * - Always note that callback may be called simultaneously for the same + * key. + * - Care must be taken when unregistering a key from the + * ioqueue. Application must take care that when one thread is issuing + * an unregistration, other thread is not simultaneously invoking the + * callback to the same key. + *\n + * This happens because the ioqueue functions are working with a pointer + * to the key, and there is a possible race condition where the pointer + * has been rendered invalid by other threads before the ioqueue has a + * chance to acquire mutex on it. + * + * \subsection pj_ioq_disallow_concur Concurrency is Disabled + * + * Alternatively, application may disable key concurrency to make + * synchronization easier. As noted above, there are three ways to control + * key concurrency setting: + * - by controlling on per handle/key basis, with #pj_ioqueue_set_concurrency(). + * - by changing default key concurrency setting on the ioqueue, with + * #pj_ioqueue_set_default_concurrency(). + * - by changing the default concurrency on compile time, by declaring + * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY macro to zero in your config_site.h + * + * \section pj_ioqeuue_examples_sec Examples + * + * For some examples on how to use the I/O Queue, please see: + * + * - \ref page_pjlib_ioqueue_tcp_test + * - \ref page_pjlib_ioqueue_udp_test + * - \ref page_pjlib_ioqueue_perf_test + */ + +/** + * This structure describes operation specific key to be submitted to + * I/O Queue when performing the asynchronous operation. This key will + * be returned to the application when completion callback is called. + * + * Application normally wants to attach it's specific data in the + * \c user_data field so that it can keep track of which operation has + * completed when the callback is called. Alternatively, application can + * also extend this struct to include its data, because the pointer that + * is returned in the completion callback will be exactly the same as + * the pointer supplied when the asynchronous function is called. + */ +typedef struct pj_ioqueue_op_key_t { + void *internal__[32]; /**< Internal I/O Queue data. */ + void *activesock_data; /**< Active socket data. */ + void *user_data; /**< Application data. */ +} pj_ioqueue_op_key_t; + +/** + * This structure describes the callbacks to be called when I/O operation + * completes. + */ +typedef struct pj_ioqueue_callback { + /** + * This callback is called when #pj_ioqueue_recv or #pj_ioqueue_recvfrom + * completes. + * + * @param key The key. + * @param op_key Operation key. + * @param bytes_read >= 0 to indicate the amount of data read, + * otherwise negative value containing the error + * code. To obtain the pj_status_t error code, use + * (pj_status_t code = -bytes_read). + */ + void (*on_read_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); + + /** + * This callback is called when #pj_ioqueue_send or #pj_ioqueue_sendto + * completes. + * + * @param key The key. + * @param op_key Operation key. + * @param bytes_sent >= 0 to indicate the amount of data written, + * otherwise negative value containing the error + * code. To obtain the pj_status_t error code, use + * (pj_status_t code = -bytes_sent). + */ + void (*on_write_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent); + + /** + * This callback is called when #pj_ioqueue_accept completes. + * + * @param key The key. + * @param op_key Operation key. + * @param sock Newly connected socket. + * @param status Zero if the operation completes successfully. + */ + void (*on_accept_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t sock, pj_status_t status); + + /** + * This callback is called when #pj_ioqueue_connect completes. + * + * @param key The key. + * @param status PJ_SUCCESS if the operation completes successfully. + */ + void (*on_connect_complete)(pj_ioqueue_key_t *key, pj_status_t status); +} pj_ioqueue_callback; + +/** + * Types of pending I/O Queue operation. This enumeration is only used + * internally within the ioqueue. + */ +typedef enum pj_ioqueue_operation_e { + PJ_IOQUEUE_OP_NONE = 0, /**< No operation. */ + PJ_IOQUEUE_OP_READ = 1, /**< read() operation. */ + PJ_IOQUEUE_OP_RECV = 2, /**< recv() operation. */ + PJ_IOQUEUE_OP_RECV_FROM = 4, /**< recvfrom() operation. */ + PJ_IOQUEUE_OP_WRITE = 8, /**< write() operation. */ + PJ_IOQUEUE_OP_SEND = 16, /**< send() operation. */ + PJ_IOQUEUE_OP_SEND_TO = 32, /**< sendto() operation. */ +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + PJ_IOQUEUE_OP_ACCEPT = 64, /**< accept() operation. */ + PJ_IOQUEUE_OP_CONNECT = 128 /**< connect() operation. */ +#endif /* PJ_HAS_TCP */ +} pj_ioqueue_operation_e; + +/** + * This macro specifies the maximum number of events that can be + * processed by the ioqueue on a single poll cycle, on implementation + * that supports it. The value is only meaningfull when specified + * during PJLIB build. + */ +#ifndef PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL +#define PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL (16) +#endif + +/** + * This macro specifies the maximum event candidates collected by each + * polling thread to be able to reach maximum number of processed events + * (i.e: PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL) in each poll cycle. + * An event candidate will be dispatched to application as event unless + * it is already being dispatched by other polling thread. So in order to + * anticipate such race condition, each poll operation should collects its + * event candidates more than PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL, the + * recommended value is (PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL * + * number of polling threads). + * + * The value is only meaningfull when specified during PJLIB build and + * is only effective on multiple polling threads environment. + */ +#if !defined(PJ_IOQUEUE_MAX_CAND_EVENTS) || PJ_IOQUEUE_MAX_CAND_EVENTS < PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL +#undef PJ_IOQUEUE_MAX_CAND_EVENTS +#define PJ_IOQUEUE_MAX_CAND_EVENTS PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL +#endif + +/** + * When this flag is specified in ioqueue's recv() or send() operations, + * the ioqueue will always mark the operation as asynchronous. + */ +#define PJ_IOQUEUE_ALWAYS_ASYNC ((pj_uint32_t)1 << (pj_uint32_t)31) + +/** + * Epoll flags. + */ +typedef enum pj_ioqueue_epoll_flag { + /** Use of EPOLLEXCLUSIVE. + */ + PJ_IOQUEUE_EPOLL_EXCLUSIVE = 1, + + /** Use of EPOLLONESHOT. + */ + PJ_IOQUEUE_EPOLL_ONESHOT = 2, + + /** + * Default flag to specify which epoll type to use, which mean to use + * EPOLLEXCLUSIVE if available, otherwise EPOLLONESHOT, otherwise "bare" + * epoll when neither are available. + */ + PJ_IOQUEUE_EPOLL_AUTO = PJ_IOQUEUE_EPOLL_EXCLUSIVE | PJ_IOQUEUE_EPOLL_ONESHOT, + +} pj_ioqueue_epoll_flag; + +/** + * Additional settings that can be given during ioqueue creation. Application + * MUST initialize this structure with #pj_ioqueue_cfg_default(). + */ +typedef struct pj_ioqueue_cfg { + /** + * Specify flags to control e.g. how events are handled when epoll backend + * is used on Linux. The values are combination of pj_ioqueue_epoll_flag. + * The default value is PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS, which by default + * is set to PJ_IOQUEUE_EPOLL_AUTO. This setting will be ignored for other + * ioqueue backends. + */ + unsigned epoll_flags; + + /** + * Default concurrency for the handles registered to this ioqueue. Setting + * this to non-zero enables a handle to process more than one operations + * at the same time using different threads. Default is + * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. This setting is equivalent to + * calling pj_ioqueue_set_default_concurrency() after creating the ioqueue. + */ + pj_bool_t default_concurrency; + +} pj_ioqueue_cfg; + +/** + * Initialize the ioqueue configuration with the default values. + * + * @param cfg The configuration to be initialized. + */ +void pj_ioqueue_cfg_default(pj_ioqueue_cfg *cfg); + +/** + * Return the name of the ioqueue implementation. + * + * @return Implementation name. + */ +PJ_DECL(const char *) pj_ioqueue_name(void); + +/** + * Create a new I/O Queue framework. + * + * @param pool The pool to allocate the I/O queue structure. + * @param max_fd The maximum number of handles to be supported, which + * should not exceed PJ_IOQUEUE_MAX_HANDLES. + * @param ioqueue Pointer to hold the newly created I/O Queue. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **ioqueue); + +/** + * Create a new I/O Queue framework. + * + * @param pool The pool to allocate the I/O queue structure. + * @param max_fd The maximum number of handles to be supported, which + * should not exceed PJ_IOQUEUE_MAX_HANDLES. + * @param cfg Optional ioqueue configuration. Application must + * initialize this structure with pj_ioqueue_cfg_default() + * first. If this is not specified, default config values + * as set pj_ioqueue_cfg_default() by will be used. + * @param ioqueue Pointer to hold the newly created I/O Queue. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_create2(pj_pool_t *pool, pj_size_t max_fd, const pj_ioqueue_cfg *cfg, pj_ioqueue_t **ioqueue); + +/** + * Destroy the I/O queue. + * + * @param ioque The I/O Queue to be destroyed. + * + * @return PJ_SUCCESS if success. + */ +PJ_DECL(pj_status_t) pj_ioqueue_destroy(pj_ioqueue_t *ioque); + +/** + * Set the lock object to be used by the I/O Queue. This function can only + * be called right after the I/O queue is created, before any handle is + * registered to the I/O queue. + * + * Initially the I/O queue is created with non-recursive mutex protection. + * Applications can supply alternative lock to be used by calling this + * function. + * + * @param ioque The ioqueue instance. + * @param lock The lock to be used by the ioqueue. + * @param auto_delete In non-zero, the lock will be deleted by the ioqueue. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_lock(pj_ioqueue_t *ioque, pj_lock_t *lock, pj_bool_t auto_delete); + +/** + * Set default concurrency policy for this ioqueue. If this function is not + * called, the default concurrency policy for the ioqueue is controlled by + * compile time setting PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. + * + * Note that changing the concurrency setting to the ioqueue will only affect + * subsequent key registrations. To modify the concurrency setting for + * individual key, use #pj_ioqueue_set_concurrency(). + * + * @param ioqueue The ioqueue instance. + * @param allow Non-zero to allow concurrent callback calls, or + * PJ_FALSE to disallow it. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_default_concurrency(pj_ioqueue_t *ioqueue, pj_bool_t allow); + +/** + * Register a socket to the I/O queue framework. + * When a socket is registered to the IOQueue, it may be modified to use + * non-blocking IO. If it is modified, there is no guarantee that this + * modification will be restored after the socket is unregistered. + * + * @param pool To allocate the resource for the specified handle, + * which must be valid until the handle/key is unregistered + * from I/O Queue. + * @param ioque The I/O Queue. + * @param sock The socket. + * @param user_data User data to be associated with the key, which can be + * retrieved later. + * @param cb Callback to be called when I/O operation completes. + * @param key Pointer to receive the key to be associated with this + * socket. Subsequent I/O queue operation will need this + * key. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_register_sock(pj_pool_t *pool, pj_ioqueue_t *ioque, pj_sock_t sock, void *user_data, + const pj_ioqueue_callback *cb, pj_ioqueue_key_t **key); + +/** + * Variant of pj_ioqueue_register_sock() with additional group lock parameter. + * If group lock is set for the key, the key will add the reference counter + * when the socket is registered and decrease it when it is destroyed. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_register_sock2(pj_pool_t *pool, pj_ioqueue_t *ioque, pj_sock_t sock, pj_grp_lock_t *grp_lock, + void *user_data, const pj_ioqueue_callback *cb, pj_ioqueue_key_t **key); + +/** + * Unregister from the I/O Queue framework. Caller must make sure that + * the key doesn't have any pending operations before calling this function, + * by calling #pj_ioqueue_is_pending() for all previously submitted + * operations except asynchronous connect, and if necessary call + * #pj_ioqueue_post_completion() to cancel the pending operations. + * + * Note that asynchronous connect operation will automatically be + * cancelled during the unregistration. + * + * Also note that when I/O Completion Port backend is used, application + * MUST close the handle immediately after unregistering the key. This is + * because there is no unregistering API for IOCP. The only way to + * unregister the handle from IOCP is to close the handle. + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the error code. + * + * @see pj_ioqueue_is_pending + */ +PJ_DECL(pj_status_t) pj_ioqueue_unregister(pj_ioqueue_key_t *key); + +/** + * Get user data associated with an ioqueue key. + * + * @param key The key that was previously obtained from registration. + * + * @return The user data associated with the descriptor, or NULL + * on error or if no data is associated with the key during + * registration. + */ +PJ_DECL(void *) pj_ioqueue_get_user_data(pj_ioqueue_key_t *key); + +/** + * Set or change the user data to be associated with the file descriptor or + * handle or socket descriptor. + * + * @param key The key that was previously obtained from registration. + * @param user_data User data to be associated with the descriptor. + * @param old_data Optional parameter to retrieve the old user data. + * + * @return PJ_SUCCESS on success or the error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_user_data(pj_ioqueue_key_t *key, void *user_data, void **old_data); + +/** + * Configure whether the ioqueue is allowed to call the key's callback + * concurrently/in parallel. The default concurrency setting for the key + * is controlled by ioqueue's default concurrency value, which can be + * changed by calling #pj_ioqueue_set_default_concurrency(). + * + * If concurrency is allowed for the key, it means that if there are more + * than one pending operations complete simultaneously, more than one + * threads may call the key's callback at the same time. This generally + * would promote good scalability for application, at the expense of more + * complexity to manage the concurrent accesses in application's code. + * + * Alternatively application may disable the concurrent access by + * setting the \a allow flag to false. With concurrency disabled, only + * one thread can call the key's callback at one time. + * + * @param key The key that was previously obtained from registration. + * @param allow Set this to non-zero to allow concurrent callback calls + * and zero (PJ_FALSE) to disallow it. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_concurrency(pj_ioqueue_key_t *key, pj_bool_t allow); + +/** + * Acquire the key's mutex. When the key's concurrency is disabled, + * application may call this function to synchronize its operation + * with the key's callback (i.e. this function will block until the + * key's callback returns). + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_lock_key(pj_ioqueue_key_t *key); + +/** + * Try to acquire the key's mutex. When the key's concurrency is disabled, + * application may call this function to synchronize its operation + * with the key's callback. + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_trylock_key(pj_ioqueue_key_t *key); + +/** + * Release the lock previously acquired with pj_ioqueue_lock_key(). + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_unlock_key(pj_ioqueue_key_t *key); + +/** + * Initialize operation key. + * + * @param op_key The operation key to be initialied. + * @param size The size of the operation key. + */ +PJ_DECL(void) pj_ioqueue_op_key_init(pj_ioqueue_op_key_t *op_key, pj_size_t size); + +/** + * Check if operation is pending on the specified operation key. + * The \c op_key must have been initialized with #pj_ioqueue_op_key_init() + * or submitted as pending operation before, or otherwise the result + * is undefined. + * + * @param key The key. + * @param op_key The operation key, previously submitted to any of + * the I/O functions and has returned PJ_EPENDING. + * + * @return Non-zero if operation is still pending. + */ +PJ_DECL(pj_bool_t) pj_ioqueue_is_pending(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key); + +/** + * Post completion status to the specified operation key and call the + * appropriate callback. When the callback is called, the number of bytes + * received in read/write callback or the status in accept/connect callback + * will be set from the \c bytes_status parameter. + * + * @param key The key. + * @param op_key Pending operation key. + * @param bytes_status Number of bytes or status to be set. A good value + * to put here is -PJ_ECANCELLED. + * + * @return PJ_SUCCESS if completion status has been successfully + * sent. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_post_completion(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_status); + +/** + * Clear ioqueue key states. This function will cancel any outstanding + * operations on that key, without invoking any completion callback. + * After calling this function, application should reinit its all operation + * keys, i.e: using pj_ioqueue_op_key_init(), before reusing them. + * + * @param key The key. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_clear_key(pj_ioqueue_key_t *key); + +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 +/** + * Instruct I/O Queue to accept incoming connection on the specified + * listening socket. This function will return immediately (i.e. non-blocking) + * regardless whether a connection is immediately available. If the function + * can't complete immediately, the caller will be notified about the incoming + * connection when it calls pj_ioqueue_poll(). If a new connection is + * immediately available, the function returns PJ_SUCCESS with the new + * connection; in this case, the callback WILL NOT be called. + * + * @param key The key which registered to the server socket. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param new_sock Argument which contain pointer to receive the new socket + * for the incoming connection. + * @param local Optional argument which contain pointer to variable to + * receive local address. + * @param remote Optional argument which contain pointer to variable to + * receive the remote address. + * @param addrlen On input, contains the length of the buffer for the + * address, and on output, contains the actual length of the + * address. This argument is optional. + * @return + * - PJ_SUCCESS When connection is available immediately, and the + * parameters will be updated to contain information about + * the new connection. In this case, a completion callback + * WILL NOT be called. + * - PJ_EPENDING If no connection is available immediately. When a new + * connection arrives, the callback will be called. + * - non-zero which indicates the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_accept(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t *new_sock, pj_sockaddr_t *local, + pj_sockaddr_t *remote, int *addrlen); + +/** + * Initiate non-blocking socket connect. If the socket can NOT be connected + * immediately, asynchronous connect() will be scheduled and caller will be + * notified via completion callback when it calls pj_ioqueue_poll(). If + * socket is connected immediately, the function returns PJ_SUCCESS and + * completion callback WILL NOT be called. + * + * @param key The key associated with TCP socket + * @param addr The remote address. + * @param addrlen The remote address length. + * + * @return + * - PJ_SUCCESS If socket is connected immediately. In this case, the + * completion callback WILL NOT be called. + * - PJ_EPENDING If operation is queued, or + * - non-zero Indicates the error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_connect(pj_ioqueue_key_t *key, const pj_sockaddr_t *addr, int addrlen); + +#endif /* PJ_HAS_TCP */ + +/** + * Poll the I/O Queue for completed events. + * + * Note: polling the ioqueue is not necessary in Symbian. Please see + * @ref PJ_SYMBIAN_OS for more info. + * + * @param ioque the I/O Queue. + * @param timeout polling timeout, or NULL if the thread wishes to wait + * indefinetely for the event. + * + * @return + * - zero if timed out (no event). + * - (<0) if error occured during polling. Callback will NOT be called. + * - (>1) to indicate numbers of events. Callbacks have been called. + */ +PJ_DECL(int) pj_ioqueue_poll(pj_ioqueue_t *ioque, const pj_time_val *timeout); + +/** + * Instruct the I/O Queue to read from the specified handle. This function + * returns immediately (i.e. non-blocking) regardless whether some data has + * been transferred. If the operation can't complete immediately, caller will + * be notified about the completion when it calls pj_ioqueue_poll(). If data + * is immediately available, the function will return PJ_SUCCESS and the + * callback WILL NOT be called. + * + * @param key The key that uniquely identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. Caller must make sure that this key remains + * valid until the function completes. + * @param buffer The buffer to hold the read data. The caller MUST make sure + * that this buffer remain valid until the framework completes + * reading the handle. + * @param length On input, it specifies the size of the buffer. If data is + * available to be read immediately, the function returns + * PJ_SUCCESS and this argument will be filled with the + * amount of data read. If the function is pending, caller + * will be notified about the amount of data read in the + * callback. This parameter can point to local variable in + * caller's stack and doesn't have to remain valid for the + * duration of pending operation. + * @param flags Recv flag. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * + * @return + * - PJ_SUCCESS If immediate data has been received in the buffer. In this + * case, the callback WILL NOT be called. + * - PJ_EPENDING If the operation has been queued, and the callback will be + * called when data has been received. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_recv(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, + pj_uint32_t flags); + +/** + * This function behaves similarly as #pj_ioqueue_recv(), except that it is + * normally called for socket, and the remote address will also be returned + * along with the data. Caller MUST make sure that both buffer and addr + * remain valid until the framework completes reading the data. + * + * @param key The key that uniquely identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param buffer The buffer to hold the read data. The caller MUST make sure + * that this buffer remain valid until the framework completes + * reading the handle. + * @param length On input, it specifies the size of the buffer. If data is + * available to be read immediately, the function returns + * PJ_SUCCESS and this argument will be filled with the + * amount of data read. If the function is pending, caller + * will be notified about the amount of data read in the + * callback. This parameter can point to local variable in + * caller's stack and doesn't have to remain valid for the + * duration of pending operation. + * @param flags Recv flag. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * @param addr Optional Pointer to buffer to receive the address. + * @param addrlen On input, specifies the length of the address buffer. + * On output, it will be filled with the actual length of + * the address. This argument can be NULL if \c addr is not + * specified. + * + * @return + * - PJ_SUCCESS If immediate data has been received. In this case, the + * callback must have been called before this function + * returns, and no pending operation is scheduled. + * - PJ_EPENDING If the operation has been queued. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_recvfrom(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, + pj_uint32_t flags, pj_sockaddr_t *addr, int *addrlen); + +/** + * Instruct the I/O Queue to write to the handle. This function will return + * immediately (i.e. non-blocking) regardless whether some data has been + * transferred. If the function can't complete immediately, the caller will + * be notified about the completion when it calls pj_ioqueue_poll(). If + * operation completes immediately and data has been transferred, the function + * returns PJ_SUCCESS and the callback will NOT be called. + * + * @param key The key that identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param data The data to send. Caller MUST make sure that this buffer + * remains valid until the write operation completes. + * @param length On input, it specifies the length of data to send. When + * data was sent immediately, this function returns PJ_SUCCESS + * and this parameter contains the length of data sent. If + * data can not be sent immediately, an asynchronous operation + * is scheduled and caller will be notified via callback the + * number of bytes sent. This parameter can point to local + * variable on caller's stack and doesn't have to remain + * valid until the operation has completed. + * @param flags Send flags. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * + * @return + * - PJ_SUCCESS If data was immediately transferred. In this case, no + * pending operation has been scheduled and the callback + * WILL NOT be called. + * - PJ_EPENDING If the operation has been queued. Once data base been + * transferred, the callback will be called. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_send(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + pj_uint32_t flags); + +/** + * Instruct the I/O Queue to write to the handle. This function will return + * immediately (i.e. non-blocking) regardless whether some data has been + * transferred. If the function can't complete immediately, the caller will + * be notified about the completion when it calls pj_ioqueue_poll(). If + * operation completes immediately and data has been transferred, the function + * returns PJ_SUCCESS and the callback will NOT be called. + * + * @param key the key that identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param data the data to send. Caller MUST make sure that this buffer + * remains valid until the write operation completes. + * @param length On input, it specifies the length of data to send. When + * data was sent immediately, this function returns PJ_SUCCESS + * and this parameter contains the length of data sent. If + * data can not be sent immediately, an asynchronous operation + * is scheduled and caller will be notified via callback the + * number of bytes sent. This parameter can point to local + * variable on caller's stack and doesn't have to remain + * valid until the operation has completed. + * @param flags send flags. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * @param addr Optional remote address. + * @param addrlen Remote address length, \c addr is specified. + * + * @return + * - PJ_SUCCESS If data was immediately written. + * - PJ_EPENDING If the operation has been queued. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_sendto(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + pj_uint32_t flags, const pj_sockaddr_t *addr, int addrlen); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_IOQUEUE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h new file mode 100755 index 000000000..b440e7eb4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_IP_ROUTE_H__ +#define __PJ_IP_ROUTE_H__ + +/** + * @file ip_helper.h + * @brief IP helper API + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_ip_helper IP Interface and Routing Helper + * @ingroup PJ_IO + * @{ + * + * This module provides functions to query local host's IP interface and + * routing table. + */ + +/** + * This structure describes IP routing entry. + */ +typedef union pj_ip_route_entry { + /** IP routing entry for IP version 4 routing */ + struct { + pj_in_addr if_addr; /**< Local interface IP address. */ + pj_in_addr dst_addr; /**< Destination IP address. */ + pj_in_addr mask; /**< Destination mask. */ + } ipv4; +} pj_ip_route_entry; + +/** + * This structure describes options for pj_enum_ip_interface2(). + */ +typedef struct pj_enum_ip_option { + /** + * Family of the address to be retrieved. Application may specify + * pj_AF_UNSPEC() to retrieve all addresses, or pj_AF_INET() or + * pj_AF_INET6() to retrieve interfaces with specific address family. + * + * Default: pj_AF_UNSPEC(). + */ + int af; + + /** + * IPv6 addresses can have a DEPRECATED flag, if this flag is set, any + * DEPRECATED IPv6 address will be omitted. Currently this is only + * available for Linux, on other platforms, if this flag is set, + * pj_enum_ip_interface2() will return PJ_ENOTSUP. + * + * Default: PJ_FALSE. + */ + pj_bool_t omit_deprecated_ipv6; + +} pj_enum_ip_option; + +/** + * Get default values of IP enumeration option. + * + * @param opt The IP enumeration option. + */ +PJ_INLINE(void) pj_enum_ip_option_default(pj_enum_ip_option *opt) +{ + pj_bzero(opt, sizeof(*opt)); +} + +/** + * Enumerate the local IP interfaces currently active in the host. + * + * @param af Family of the address to be retrieved. Application + * may specify pj_AF_UNSPEC() to retrieve all addresses, + * or pj_AF_INET() or pj_AF_INET6() to retrieve interfaces + * with specific address family. + * @param count On input, specify the number of entries. On output, + * it will be filled with the actual number of entries. + * @param ifs Array of socket addresses, which address part will + * be filled with the interface address. The address + * family part will be initialized with the address + * family of the IP address. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_enum_ip_interface(int af, unsigned *count, pj_sockaddr ifs[]); + +/** + * Enumerate the local IP interfaces currently active in the host with + * capability to filter DEPRECATED IPv6 addresses (currently only for Linux). + * + * @param opt The option, default option will be used if NULL. + * @param count On input, specify the number of entries. On output, + * it will be filled with the actual number of entries. + * @param ifs Array of socket (with flags) addresses, which address part + * will be filled with the interface address. The address + * family part will be initialized with the address + * family of the IP address. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_enum_ip_interface2(const pj_enum_ip_option *opt, unsigned *count, pj_sockaddr ifs[]); + +/** + * Enumerate the IP routing table for this host. + * + * @param count On input, specify the number of routes entries. On output, + * it will be filled with the actual number of route entries. + * @param routes Array of IP routing entries. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_enum_ip_route(unsigned *count, pj_ip_route_entry routes[]); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_IP_ROUTE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/limits.h b/src/tuya_p2p/pjproject/pjlib/include/pj/limits.h new file mode 100755 index 000000000..5de375eb7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/limits.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2017 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LIMITS_H__ +#define __PJ_LIMITS_H__ + +/** + * @file limits.h + * @brief Common min and max values + */ + +#include + +/** Maximum value for signed 32-bit integer. */ +#define PJ_MAXINT32 0x7fffffff + +/** Minimum value for signed 32-bit integer. */ +#define PJ_MININT32 0x80000000 + +/** Maximum value for unsigned 16-bit integer. */ +#define PJ_MAXUINT16 0xffff + +/** Maximum value for unsigned char. */ +#define PJ_MAXUINT8 0xff + +/** Maximum value for long. */ +#define PJ_MAXLONG LONG_MAX + +/** Minimum value for long. */ +#define PJ_MINLONG LONG_MIN + +/** Minimum value for unsigned long. */ +#define PJ_MAXULONG ULONG_MAX + +#endif /* __PJ_LIMITS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/list.h b/src/tuya_p2p/pjproject/pjlib/include/pj/list.h new file mode 100755 index 000000000..b4b6531ca --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/list.h @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LIST_H__ +#define __PJ_LIST_H__ + +/** + * @file list.h + * @brief Linked List data structure. + */ + +#include + +PJ_BEGIN_DECL + +/* + * @defgroup PJ_DS Data Structure. + */ + +/** + * @defgroup PJ_LIST Linked List + * @ingroup PJ_DS + * @{ + * + * List in PJLIB is implemented as doubly-linked list, and it won't require + * dynamic memory allocation (just as all PJLIB data structures). The list here + * should be viewed more like a low level C list instead of high level C++ list + * (which normally are easier to use but require dynamic memory allocations), + * therefore all caveats with C list apply here too (such as you can NOT put + * a node in more than one lists). + * + * \section pj_list_example_sec Examples + * + * See below for examples on how to manipulate linked list: + * - @ref page_pjlib_samples_list_c + * - @ref page_pjlib_list_test + */ + +/** + * Use this macro in the start of the structure declaration to declare that + * the structure can be used in the linked list operation. This macro simply + * declares additional member @a prev and @a next to the structure. + * @hideinitializer + */ +#define PJ_DECL_LIST_MEMBER(type) \ + /** List @a prev. */ \ + type *prev; \ + /** List @a next. */ \ + type *next + +/** + * This structure describes generic list node and list. The owner of this list + * must initialize the 'value' member to an appropriate value (typically the + * owner itself). + */ +struct pj_list { + PJ_DECL_LIST_MEMBER(void); +} PJ_ATTR_MAY_ALIAS; /* may_alias avoids warning with gcc-4.4 -Wall -O2 */ + +/** + * Initialize the list. + * Initially, the list will have no member, and function pj_list_empty() will + * always return nonzero (which indicates TRUE) for the newly initialized + * list. + * + * @param node The list head. + */ +PJ_INLINE(void) pj_list_init(pj_list_type *node) +{ + ((pj_list *)node)->next = ((pj_list *)node)->prev = node; +} + +/** + * Check that the list is empty. + * + * @param node The list head. + * + * @return Non-zero if the list is empty, or zero if it is not empty. + * + */ +PJ_INLINE(int) pj_list_empty(const pj_list_type *node) +{ + return ((pj_list *)node)->next == node; +} + +/** + * Insert the node to the list before the specified element position. + * + * @param pos The element to which the node will be inserted before. + * @param node The element to be inserted. + * + */ +PJ_IDECL(void) pj_list_insert_before(pj_list_type *pos, pj_list_type *node); + +/** + * Insert the node to the back of the list. This is just an alias for + * #pj_list_insert_before(). + * + * @param list The list. + * @param node The element to be inserted. + */ +PJ_INLINE(void) pj_list_push_back(pj_list_type *list, pj_list_type *node) +{ + pj_list_insert_before(list, node); +} + +/** + * Inserts all nodes in \a nodes to the target list. + * + * @param lst The target list. + * @param nodes Nodes list. + */ +PJ_IDECL(void) pj_list_insert_nodes_before(pj_list_type *lst, pj_list_type *nodes); + +/** + * Insert a node to the list after the specified element position. + * + * @param pos The element in the list which will precede the inserted + * element. + * @param node The element to be inserted after the position element. + * + */ +PJ_IDECL(void) pj_list_insert_after(pj_list_type *pos, pj_list_type *node); + +/** + * Insert the node to the front of the list. This is just an alias for + * #pj_list_insert_after(). + * + * @param list The list. + * @param node The element to be inserted. + */ +PJ_INLINE(void) pj_list_push_front(pj_list_type *list, pj_list_type *node) +{ + pj_list_insert_after(list, node); +} + +/** + * Insert all nodes in \a nodes to the target list. + * + * @param lst The target list. + * @param nodes Nodes list. + */ +PJ_IDECL(void) pj_list_insert_nodes_after(pj_list_type *lst, pj_list_type *nodes); + +/** + * Remove elements from the source list, and insert them to the destination + * list. The elements of the source list will occupy the + * front elements of the target list. Note that the node pointed by \a list2 + * itself is not considered as a node, but rather as the list descriptor, so + * it will not be inserted to the \a list1. The elements to be inserted starts + * at \a list2->next. If \a list2 is to be included in the operation, use + * \a pj_list_insert_nodes_before. + * + * @param list1 The destination list. + * @param list2 The source list. + * + */ +PJ_IDECL(void) pj_list_merge_first(pj_list_type *list1, pj_list_type *list2); + +/** + * Remove elements from the second list argument, and insert them to the list + * in the first argument. The elements from the second list will be appended + * to the first list. Note that the node pointed by \a list2 + * itself is not considered as a node, but rather as the list descriptor, so + * it will not be inserted to the \a list1. The elements to be inserted starts + * at \a list2->next. If \a list2 is to be included in the operation, use + * \a pj_list_insert_nodes_before. + * + * @param list1 The element in the list which will precede the inserted + * element. + * @param list2 The element in the list to be inserted. + * + */ +PJ_IDECL(void) pj_list_merge_last(pj_list_type *list1, pj_list_type *list2); + +/** + * Erase the node from the list it currently belongs. + * + * @param node The element to be erased. + */ +PJ_IDECL(void) pj_list_erase(pj_list_type *node); + +/** + * Find node in the list. + * + * @param list The list head. + * @param node The node element to be searched. + * + * @return The node itself if it is found in the list, or NULL if it is not + * found in the list. + */ +PJ_IDECL(pj_list_type *) pj_list_find_node(pj_list_type *list, pj_list_type *node); + +/** + * Search the list for the specified value, using the specified comparison + * function. This function iterates on nodes in the list, started with the + * first node, and call the user supplied comparison function until the + * comparison function returns ZERO. + * + * @param list The list head. + * @param value The user defined value to be passed in the comparison + * function + * @param comp The comparison function, which should return ZERO to + * indicate that the searched value is found. + * + * @return The first node that matched, or NULL if it is not found. + */ +PJ_IDECL(pj_list_type *) +pj_list_search(pj_list_type *list, void *value, int (*comp)(void *value, const pj_list_type *node)); + +/** + * Traverse the list to get the number of elements in the list. + * + * @param list The list head. + * + * @return Number of elements. + */ +PJ_IDECL(pj_size_t) pj_list_size(const pj_list_type *list); + +/** + * @} + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#include "list_i.h" +#endif + +PJ_END_DECL + +#endif /* __PJ_LIST_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h b/src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h new file mode 100755 index 000000000..71ed614ca --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Internal */ +PJ_INLINE(void) pj_link_node(pj_list_type *prev, pj_list_type *next) +{ + ((pj_list *)prev)->next = next; + ((pj_list *)next)->prev = prev; +} + +PJ_IDEF(void) pj_list_insert_after(pj_list_type *pos, pj_list_type *node) +{ + ((pj_list *)node)->prev = pos; + ((pj_list *)node)->next = ((pj_list *)pos)->next; + ((pj_list *)((pj_list *)pos)->next)->prev = node; + ((pj_list *)pos)->next = node; +} + +PJ_IDEF(void) pj_list_insert_before(pj_list_type *pos, pj_list_type *node) +{ + pj_list_insert_after(((pj_list *)pos)->prev, node); +} + +PJ_IDEF(void) pj_list_insert_nodes_after(pj_list_type *pos, pj_list_type *lst) +{ + pj_list *lst_last = (pj_list *)((pj_list *)lst)->prev; + pj_list *pos_next = (pj_list *)((pj_list *)pos)->next; + + pj_link_node(pos, lst); + pj_link_node(lst_last, pos_next); +} + +PJ_IDEF(void) pj_list_insert_nodes_before(pj_list_type *pos, pj_list_type *lst) +{ + pj_list_insert_nodes_after(((pj_list *)pos)->prev, lst); +} + +PJ_IDEF(void) pj_list_merge_last(pj_list_type *lst1, pj_list_type *lst2) +{ + if (!pj_list_empty(lst2)) { + pj_link_node(((pj_list *)lst1)->prev, ((pj_list *)lst2)->next); + pj_link_node(((pj_list *)lst2)->prev, lst1); + pj_list_init(lst2); + } +} + +PJ_IDEF(void) pj_list_merge_first(pj_list_type *lst1, pj_list_type *lst2) +{ + if (!pj_list_empty(lst2)) { + pj_link_node(((pj_list *)lst2)->prev, ((pj_list *)lst1)->next); + pj_link_node(((pj_list *)lst1), ((pj_list *)lst2)->next); + pj_list_init(lst2); + } +} + +PJ_IDEF(void) pj_list_erase(pj_list_type *node) +{ + pj_link_node(((pj_list *)node)->prev, ((pj_list *)node)->next); + + /* It'll be safer to init the next/prev fields to itself, to + * prevent multiple erase() from corrupting the list. See + * ticket #520 for one sample bug. + */ + pj_list_init(node); +} + +PJ_IDEF(pj_list_type *) pj_list_find_node(pj_list_type *list, pj_list_type *node) +{ + pj_list *p = (pj_list *)((pj_list *)list)->next; + while (p != list && p != node) + p = (pj_list *)p->next; + + return p == node ? p : NULL; +} + +PJ_IDEF(pj_list_type *) +pj_list_search(pj_list_type *list, void *value, int (*comp)(void *value, const pj_list_type *node)) +{ + pj_list *p = (pj_list *)((pj_list *)list)->next; + while (p != list && (*comp)(value, p) != 0) + p = (pj_list *)p->next; + + return p == list ? NULL : p; +} + +PJ_IDEF(pj_size_t) pj_list_size(const pj_list_type *list) +{ + const pj_list *node = (const pj_list *)((const pj_list *)list)->next; + pj_size_t count = 0; + + while (node != list) { + ++count; + node = (pj_list *)node->next; + } + + return count; +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/lock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/lock.h new file mode 100755 index 000000000..23bb2a0f5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/lock.h @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LOCK_H__ +#define __PJ_LOCK_H__ + +/** + * @file lock.h + * @brief Higher abstraction for locking objects. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_LOCK Lock Objects + * @ingroup PJ_OS + * @{ + * + * Lock Objects are higher abstraction for different lock mechanisms. + * It offers the same API for manipulating different lock types (e.g. + * @ref PJ_MUTEX "mutex", @ref PJ_SEM "semaphores", or null locks). + * Because Lock Objects have the same API for different types of lock + * implementation, it can be passed around in function arguments. As the + * result, it can be used to control locking policy for a particular + * feature. + */ + +/** + * Create simple, non recursive mutex lock object. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_create_simple_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock); + +/** + * Create recursive mutex lock object. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_create_recursive_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock); + +/** + * Create NULL mutex. A NULL mutex doesn't actually have any synchronization + * object attached to it. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_create_null_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock); + +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +/** + * Create semaphore lock object. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param initial Initial value of the semaphore. + * @param max Maximum value of the semaphore. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_lock_create_semaphore(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_lock_t **lock); + +#endif /* PJ_HAS_SEMAPHORE */ + +/** + * Acquire lock on the specified lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_acquire(pj_lock_t *lock); + +/** + * Try to acquire lock on the specified lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_tryacquire(pj_lock_t *lock); + +/** + * Release lock on the specified lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_release(pj_lock_t *lock); + +/** + * Destroy the lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_destroy(pj_lock_t *lock); + +/** @} */ + +/** + * @defgroup PJ_GRP_LOCK Group Lock + * @ingroup PJ_LOCK + * @{ + * + * Group lock is a synchronization object to manage concurrency among members + * within the same logical group. Example of such groups are: + * + * - dialog, which has members such as the dialog itself, an invite session, + * and several transactions + * - ICE, which has members such as ICE stream transport, ICE session, STUN + * socket, TURN socket, and down to ioqueue key + * + * Group lock has three functions: + * + * - mutual exclusion: to protect resources from being accessed by more than + * one threads at the same time + * - session management: to make sure that the resource is not destroyed + * while others are still using or about to use it. + * - lock coordinator: to provide uniform lock ordering among more than one + * lock objects, which is necessary to avoid deadlock. + * + * The requirements of the group lock are: + * + * - must satisfy all the functions above + * - must allow members to join or leave the group (for example, + * transaction may be added or removed from a dialog) + * - must be able to synchronize with external lock (for example, a dialog + * lock must be able to sync itself with PJSUA lock) + * + * Please see https://trac.pjsip.org/repos/wiki/Group_Lock for more info. + */ + +/** + * Settings for creating the group lock. + */ +typedef struct pj_grp_lock_config { + /** + * Creation flags, currently must be zero. + */ + unsigned flags; + +} pj_grp_lock_config; + +/** + * The group lock destroy handler, a destructor function called when + * a group lock is about to be destroyed. + * + * @param member A pointer to be passed to the handler. + */ +typedef void (*pj_grp_lock_handler)(void *member); + +/** + * Initialize the config with the default values. + * + * @param cfg The config to be initialized. + */ +PJ_DECL(void) pj_grp_lock_config_default(pj_grp_lock_config *cfg); + +/** + * Create a group lock object. Initially the group lock will have reference + * counter of zero. + * + * @param pool The group lock only uses the pool parameter to get + * the pool factory, from which it will create its own + * pool. + * @param cfg Optional configuration. + * @param p_grp_lock Pointer to receive the newly created group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_create(pj_pool_t *pool, const pj_grp_lock_config *cfg, pj_grp_lock_t **p_grp_lock); + +/** + * Create a group lock object, with the specified destructor handler, to be + * called by the group lock when it is about to be destroyed. Initially the + * group lock will have reference counter of zero. + * + * @param pool The group lock only uses the pool parameter to get + * the pool factory, from which it will create its own + * pool. + * @param cfg Optional configuration. + * @param member A pointer to be passed to the handler. + * @param handler The destroy handler. + * @param p_grp_lock Pointer to receive the newly created group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_grp_lock_create_w_handler(pj_pool_t *pool, const pj_grp_lock_config *cfg, void *member, pj_grp_lock_handler handler, + pj_grp_lock_t **p_grp_lock); + +/** + * Forcibly destroy the group lock, ignoring the reference counter value. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_destroy(pj_grp_lock_t *grp_lock); + +/** + * Move the contents of the old lock to the new lock and destroy the + * old lock. + * + * @param old_lock The old group lock to be destroyed. + * @param new_lock The new group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_replace(pj_grp_lock_t *old_lock, pj_grp_lock_t *new_lock); + +/** + * Acquire lock on the specified group lock. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_acquire(pj_grp_lock_t *grp_lock); + +/** + * Acquire lock on the specified group lock if it is available, otherwise + * return immediately wihout waiting. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_tryacquire(pj_grp_lock_t *grp_lock); + +/** + * Release the previously held lock. This may cause the group lock + * to be destroyed if it is the last one to hold the reference counter. + * In that case, the function will return PJ_EGONE. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_release(pj_grp_lock_t *grp_lock); + +/** + * Add a destructor handler, to be called by the group lock when it is + * about to be destroyed. + * + * @param grp_lock The group lock. + * @param pool Pool to allocate memory for the handler. + * @param member A pointer to be passed to the handler. + * @param handler The destroy handler. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_grp_lock_add_handler(pj_grp_lock_t *grp_lock, pj_pool_t *pool, void *member, pj_grp_lock_handler handler); + +/** + * Remove previously registered handler. All parameters must be the same + * as when the handler was added. + * + * @param grp_lock The group lock. + * @param member A pointer to be passed to the handler. + * @param handler The destroy handler. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_del_handler(pj_grp_lock_t *grp_lock, void *member, pj_grp_lock_handler handler); + +/** + * Increment reference counter to prevent the group lock grom being destroyed. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#if !PJ_GRP_LOCK_DEBUG +PJ_DECL(pj_status_t) pj_grp_lock_add_ref(pj_grp_lock_t *grp_lock); + +/** + * Debug version of pj_grp_lock_add_ref(), allowing to specify file and lineno. + * + * @param grp_lock The group lock. + * @param x Filename + * @param y Line number + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#define pj_grp_lock_add_ref_dbg(grp_lock, x, y) pj_grp_lock_add_ref(grp_lock) + +#else + +#define pj_grp_lock_add_ref(g) pj_grp_lock_add_ref_dbg(g, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) pj_grp_lock_add_ref_dbg(pj_grp_lock_t *grp_lock, const char *file, int line); +#endif + +/** + * Decrement the reference counter. When the counter value reaches zero, the + * group lock will be destroyed and all destructor handlers will be called. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#if !PJ_GRP_LOCK_DEBUG +PJ_DECL(pj_status_t) pj_grp_lock_dec_ref(pj_grp_lock_t *grp_lock); + +/** + * Debug version of pj_grp_lock_dec_ref(), allowing to specify file and lineno. + * + * @param grp_lock The group lock. + * @param x Filename + * @param y Line number + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#define pj_grp_lock_dec_ref_dbg(grp_lock, x, y) pj_grp_lock_dec_ref(grp_lock) +#else + +#define pj_grp_lock_dec_ref(g) pj_grp_lock_dec_ref_dbg(g, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) pj_grp_lock_dec_ref_dbg(pj_grp_lock_t *grp_lock, const char *file, int line); + +#endif + +/** + * Get current reference count value. This normally is only used for + * debugging purpose. + * + * @param grp_lock The group lock. + * + * @return The reference count value. + */ +PJ_DECL(int) pj_grp_lock_get_ref(pj_grp_lock_t *grp_lock); + +/** + * Dump group lock info for debugging purpose. If group lock debugging is + * enabled (via PJ_GRP_LOCK_DEBUG) macro, this will print the group lock + * reference counter value along with the source file and line. If + * debugging is disabled, this will only print the reference counter. + * + * @param grp_lock The group lock. + */ +PJ_DECL(void) pj_grp_lock_dump(pj_grp_lock_t *grp_lock); + +/** + * Synchronize an external lock with the group lock, by adding it to the + * list of locks to be acquired by the group lock when the group lock is + * acquired. + * + * The ''pos'' argument specifies the lock order and also the relative + * position with regard to lock ordering against the group lock. Locks with + * lower ''pos'' value will be locked first, and those with negative value + * will be locked before the group lock (the group lock's ''pos'' value is + * zero). + * + * @param grp_lock The group lock. + * @param ext_lock The external lock + * @param pos The position. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_chain_lock(pj_grp_lock_t *grp_lock, pj_lock_t *ext_lock, int pos); + +/** + * Remove an external lock from group lock's list of synchronized locks. + * + * @param grp_lock The group lock. + * @param ext_lock The external lock + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_unchain_lock(pj_grp_lock_t *grp_lock, pj_lock_t *ext_lock); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_LOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/log.h b/src/tuya_p2p/pjproject/pjlib/include/pj/log.h new file mode 100755 index 000000000..7d20f29cd --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/log.h @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LOG_H__ +#define __PJ_LOG_H__ + +/** + * @file log.h + * @brief Logging Utility. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_MISC Miscelaneous + */ + +/** + * @defgroup PJ_LOG Logging Facility + * @ingroup PJ_MISC + * @{ + * + * The PJLIB logging facility is a configurable, flexible, and convenient + * way to write logging or trace information. + * + * To write to the log, one uses construct like below: + * + *
+ *   ...
+ *   PJ_LOG(3, ("main.c", "Starting hello..."));
+ *   ...
+ *   PJ_LOG(3, ("main.c", "Hello world from process %d", pj_getpid()));
+ *   ...
+ * 
+ * + * In the above example, the number @b 3 controls the verbosity level of + * the information (which means "information", by convention). The string + * "main.c" specifies the source or sender of the message. + * + * + * \section pj_log_quick_sample_sec Examples + * + * For examples, see: + * - @ref page_pjlib_samples_log_c. + * + */ + +/** + * Log decoration flag, to be specified with #pj_log_set_decor(). + */ +enum pj_log_decoration { + PJ_LOG_HAS_DAY_NAME = 1, /**< Include day name [default: no] */ + PJ_LOG_HAS_YEAR = 2, /**< Include year digit [no] */ + PJ_LOG_HAS_MONTH = 4, /**< Include month [no] */ + PJ_LOG_HAS_DAY_OF_MON = 8, /**< Include day of month [no] */ + PJ_LOG_HAS_TIME = 16, /**< Include time [yes] */ + PJ_LOG_HAS_MICRO_SEC = 32, /**< Include microseconds [yes] */ + PJ_LOG_HAS_SENDER = 64, /**< Include sender in the log [yes] */ + PJ_LOG_HAS_NEWLINE = 128, /**< Terminate each call with newline [yes] */ + PJ_LOG_HAS_CR = 256, /**< Include carriage return [no] */ + PJ_LOG_HAS_SPACE = 512, /**< Include two spaces before log [yes] */ + PJ_LOG_HAS_COLOR = 1024, /**< Colorize logs [yes on win32] */ + PJ_LOG_HAS_LEVEL_TEXT = 2048, /**< Include level text string [no] */ + PJ_LOG_HAS_THREAD_ID = 4096, /**< Include thread identification [no] */ + PJ_LOG_HAS_THREAD_SWC = 8192, /**< Add mark when thread has switched [yes]*/ + PJ_LOG_HAS_INDENT = 16384 /**< Indentation. Say yes! [yes] */ +}; + +/** + * Write log message. + * This is the main macro used to write text to the logging backend. + * + * @param level The logging verbosity level. Lower number indicates higher + * importance, with level zero indicates fatal error. Only + * numeral argument is permitted (e.g. not variable). + * @param arg Enclosed 'printf' like arguments, with the first + * argument is the sender, the second argument is format + * string and the following arguments are variable number of + * arguments suitable for the format string. + * + * Sample: + * \verbatim + PJ_LOG(2, (__FILE__, "current value is %d", value)); + \endverbatim + * @hideinitializer + */ +#define PJ_LOG(level, arg) \ + do { \ + if (level <= pj_log_get_level()) { \ + pj_log_wrapper_##level(arg); \ + } \ + } while (0) + +/** + * Signature for function to be registered to the logging subsystem to + * write the actual log message to some output device. + * + * @param level Log level. + * @param data Log message, which will be NULL terminated. + * @param len Message length. + */ +typedef void pj_log_func(int level, const char *data, int len); + +/** + * Default logging writer function used by front end logger function. + * This function will print the log message to stdout only. + * Application normally should NOT need to call this function, but + * rather use the PJ_LOG macro. + * + * @param level Log level. + * @param buffer Log message. + * @param len Message length. + */ +PJ_DECL(void) pj_log_write(int level, const char *buffer, int len); + +#if PJ_LOG_MAX_LEVEL >= 1 + +/** + * Write to log. + * + * @param sender Source of the message. + * @param level Verbosity level. + * @param format Format. + * @param marker Marker. + */ +PJ_DECL(void) pj_log(const char *sender, int level, const char *format, va_list marker); + +/** + * Change log output function. The front-end logging functions will call + * this function to write the actual message to the desired device. + * By default, the front-end functions use pj_log_write() to write + * the messages, unless it's changed by calling this function. + * + * @param func The function that will be called to write the log + * messages to the desired device. + */ +PJ_DECL(void) pj_log_set_log_func(pj_log_func *func); + +/** + * Get the current log output function that is used to write log messages. + * + * @return Current log output function. + */ +PJ_DECL(pj_log_func *) pj_log_get_log_func(void); + +/** + * Set maximum log level. Application can call this function to set + * the desired level of verbosity of the logging messages. The bigger the + * value, the more verbose the logging messages will be printed. However, + * the maximum level of verbosity can not exceed compile time value of + * PJ_LOG_MAX_LEVEL. + * + * @param level The maximum level of verbosity of the logging + * messages (6=very detailed..1=error only, 0=disabled) + */ +PJ_DECL(void) pj_log_set_level(int level); + +/** + * Get current maximum log verbositylevel. + * + * @return Current log maximum level. + */ +#if 1 +PJ_DECL(int) pj_log_get_level(void); +#else +PJ_DECL_DATA(int) pj_log_max_level; +#define pj_log_get_level() pj_log_max_level +#endif + +/** + * Set log decoration. The log decoration flag controls what are printed + * to output device alongside the actual message. For example, application + * can specify that date/time information should be displayed with each + * log message. + * + * @param decor Bitmask combination of #pj_log_decoration to control + * the layout of the log message. + */ +PJ_DECL(void) pj_log_set_decor(unsigned decor); + +/** + * Get current log decoration flag. + * + * @return Log decoration flag. + */ +PJ_DECL(unsigned) pj_log_get_decor(void); + +/** + * Add indentation to log message. Indentation will add PJ_LOG_INDENT_CHAR + * before the message, and is useful to show the depth of function calls. + * + * @param indent The indentation to add or substract. Positive value + * adds current indent, negative value subtracts current + * indent. + */ +PJ_DECL(void) pj_log_add_indent(int indent); + +/** + * Set indentation to specific value. + * + * @param indent The indentation value. + */ +PJ_DECL(void) pj_log_set_indent(int indent); + +/** + * Get current indentation value. + * + * @return Current indentation value. + */ +PJ_DECL(int) pj_log_get_indent(void); + +/** + * Push indentation to the right by default value (PJ_LOG_INDENT_SIZE). + */ +PJ_DECL(void) pj_log_push_indent(void); + +/** + * Pop indentation (to the left) by default value (PJ_LOG_INDENT_SIZE). + */ +PJ_DECL(void) pj_log_pop_indent(void); + +/** + * Set color of log messages. + * + * @param level Log level which color will be changed. + * @param color Desired color. + */ +PJ_DECL(void) pj_log_set_color(int level, pj_color_t color); + +/** + * Get color of log messages. + * + * @param level Log level which color will be returned. + * @return Log color. + */ +PJ_DECL(pj_color_t) pj_log_get_color(int level); + +/** + * Internal function to be called by pj_init() + */ +pj_status_t pj_log_init(void); + +#else /* #if PJ_LOG_MAX_LEVEL >= 1 */ + +/** + * Change log output function. The front-end logging functions will call + * this function to write the actual message to the desired device. + * By default, the front-end functions use pj_log_write() to write + * the messages, unless it's changed by calling this function. + * + * @param func The function that will be called to write the log + * messages to the desired device. + */ +#define pj_log_set_log_func(func) + +/** + * Write to log. + * + * @param sender Source of the message. + * @param level Verbosity level. + * @param format Format. + * @param marker Marker. + */ +#define pj_log(sender, level, format, marker) + +/** + * Set maximum log level. Application can call this function to set + * the desired level of verbosity of the logging messages. The bigger the + * value, the more verbose the logging messages will be printed. However, + * the maximum level of verbosity can not exceed compile time value of + * PJ_LOG_MAX_LEVEL. + * + * @param level The maximum level of verbosity of the logging + * messages (6=very detailed..1=error only, 0=disabled) + */ +#define pj_log_set_level(level) + +/** + * Set log decoration. The log decoration flag controls what are printed + * to output device alongside the actual message. For example, application + * can specify that date/time information should be displayed with each + * log message. + * + * @param decor Bitmask combination of #pj_log_decoration to control + * the layout of the log message. + */ +#define pj_log_set_decor(decor) + +/** + * Add indentation to log message. Indentation will add PJ_LOG_INDENT_CHAR + * before the message, and is useful to show the depth of function calls. + * + * @param indent The indentation to add or substract. Positive value + * adds current indent, negative value subtracts current + * indent. + */ +#define pj_log_add_indent(indent) + +/** + * Set indentation to specific value. + * + * @param indent The indentation value. + */ +#define pj_log_set_indent(indent) + +/** + * Get current indentation value. + * + * @return Current indentation value. + */ +#define pj_log_get_indent() 0 + +/** + * Push indentation to the right by default value (PJ_LOG_INDENT_SIZE). + */ +#define pj_log_push_indent() + +/** + * Pop indentation (to the left) by default value (PJ_LOG_INDENT_SIZE). + */ +#define pj_log_pop_indent() + +/** + * Set color of log messages. + * + * @param level Log level which color will be changed. + * @param color Desired color. + */ +#define pj_log_set_color(level, color) + +/** + * Get current maximum log verbositylevel. + * + * @return Current log maximum level. + */ +#define pj_log_get_level() 0 + +/** + * Get current log decoration flag. + * + * @return Log decoration flag. + */ +#define pj_log_get_decor() 0 + +/** + * Get color of log messages. + * + * @param level Log level which color will be returned. + * @return Log color. + */ +#define pj_log_get_color(level) 0 + +/** + * Internal. + */ +#define pj_log_init() PJ_SUCCESS + +#endif /* #if PJ_LOG_MAX_LEVEL >= 1 */ + +/** + * @} + */ + +/* **************************************************************************/ +/* + * Log functions implementation prototypes. + * These functions are called by PJ_LOG macros according to verbosity + * level specified when calling the macro. Applications should not normally + * need to call these functions directly. + */ + +/** + * @def pj_log_wrapper_1(arg) + * Internal function to write log with verbosity 1. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 1. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 1 +#define pj_log_wrapper_1(arg) pj_log_1 arg +/** Internal function. */ +PJ_DECL(void) pj_log_1(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_1(arg) +#endif + +/** + * @def pj_log_wrapper_2(arg) + * Internal function to write log with verbosity 2. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 2. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 2 +#define pj_log_wrapper_2(arg) pj_log_2 arg +/** Internal function. */ +PJ_DECL(void) pj_log_2(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_2(arg) +#endif + +/** + * @def pj_log_wrapper_3(arg) + * Internal function to write log with verbosity 3. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 3. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 3 +#define pj_log_wrapper_3(arg) pj_log_3 arg +/** Internal function. */ +PJ_DECL(void) pj_log_3(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_3(arg) +#endif + +/** + * @def pj_log_wrapper_4(arg) + * Internal function to write log with verbosity 4. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 4. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 4 +#define pj_log_wrapper_4(arg) pj_log_4 arg +/** Internal function. */ +PJ_DECL(void) pj_log_4(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_4(arg) +#endif + +/** + * @def pj_log_wrapper_5(arg) + * Internal function to write log with verbosity 5. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 5. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 5 +#define pj_log_wrapper_5(arg) pj_log_5 arg +/** Internal function. */ +PJ_DECL(void) pj_log_5(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_5(arg) +#endif + +/** + * @def pj_log_wrapper_6(arg) + * Internal function to write log with verbosity 6. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 6. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 6 +#define pj_log_wrapper_6(arg) pj_log_6 arg +/** Internal function. */ +PJ_DECL(void) pj_log_6(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_6(arg) +#endif + +PJ_END_DECL + +#endif /* __PJ_LOG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/math.h b/src/tuya_p2p/pjproject/pjlib/include/pj/math.h new file mode 100755 index 000000000..4eaba4216 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/math.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJ_MATH_H__ +#define __PJ_MATH_H__ + +/** + * @file math.h + * @brief Mathematics and Statistics. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_math Mathematics and Statistics + * @ingroup PJ_MISC + * @{ + * + * Provides common mathematics constants and operations, and also standard + * statistics calculation (min, max, mean, standard deviation). Statistics + * calculation is done in realtime (statistics state is updated on time each + * new sample comes). + */ + +/** + * Mathematical constants + */ +/** pi */ +#define PJ_PI 3.14159265358979323846 /* pi */ +/** 1/pi */ +#define PJ_1_PI 0.318309886183790671538 /* 1/pi */ + +/** + * Mathematical macros + */ +/** Get the absolute value */ +#define PJ_ABS(x) ((x) > 0 ? (x) : -(x)) + +/** Get the maximum of two values */ +#define PJ_MAX(x, y) ((x) > (y) ? (x) : (y)) + +/** Get the minimum of two values */ +#define PJ_MIN(x, y) ((x) < (y) ? (x) : (y)) + +/** + * This structure describes statistics state. + */ +typedef struct pj_math_stat { + int n; /**< number of samples */ + int max; /**< maximum value */ + int min; /**< minimum value */ + int last; /**< last value */ + int mean; /**< mean */ + + /* Private members */ +#if PJ_HAS_FLOATING_POINT + float fmean_; /**< mean(floating point) */ +#else + int mean_res_; /**< mean residue */ +#endif + pj_highprec_t m2_; /**< variance * n */ +} pj_math_stat; + +/** + * Calculate integer square root of an integer. + * + * @param i Integer to be calculated. + * + * @return Square root result. + */ +PJ_INLINE(unsigned) pj_isqrt(unsigned i) +{ + unsigned res = 1, prev; + + /* Rough guess, calculate half bit of input */ + prev = i >> 2; + while (prev) { + prev >>= 2; + res <<= 1; + } + + /* Babilonian method */ + do { + prev = res; + res = (prev + i / prev) >> 1; + } while ((prev + res) >> 1 != res); + + return res; +} + +/** + * Initialize statistics state. + * + * @param stat Statistic state. + */ +PJ_INLINE(void) pj_math_stat_init(pj_math_stat *stat) +{ + pj_bzero(stat, sizeof(pj_math_stat)); +} + +/** + * Update statistics state as a new sample comes. + * + * @param stat Statistic state. + * @param val The new sample data. + */ +PJ_INLINE(void) pj_math_stat_update(pj_math_stat *stat, int val) +{ +#if PJ_HAS_FLOATING_POINT + float delta; +#else + int delta; +#endif + + stat->last = val; + + if (stat->n++) { + if (stat->min > val) + stat->min = val; + if (stat->max < val) + stat->max = val; + } else { + stat->min = stat->max = val; + } + +#if PJ_HAS_FLOATING_POINT + delta = val - stat->fmean_; + stat->fmean_ += delta / stat->n; + + /* Return mean value with 'rounding' */ + stat->mean = (int)(stat->fmean_ + 0.5); + + stat->m2_ += (int)(delta * (val - stat->fmean_)); +#else + delta = val - stat->mean; + stat->mean += delta / stat->n; + stat->mean_res_ += delta % stat->n; + if (stat->mean_res_ >= stat->n) { + ++stat->mean; + stat->mean_res_ -= stat->n; + } else if (stat->mean_res_ <= -stat->n) { + --stat->mean; + stat->mean_res_ += stat->n; + } + + stat->m2_ += delta * (val - stat->mean); +#endif +} + +/** + * Get the standard deviation of specified statistics state. + * + * @param stat Statistic state. + * + * @return The standard deviation. + */ +PJ_INLINE(unsigned) pj_math_stat_get_stddev(const pj_math_stat *stat) +{ + if (stat->n == 0) + return 0; + return (pj_isqrt((unsigned)(stat->m2_ / stat->n))); +} + +/** + * Set the standard deviation of statistics state. This is useful when + * the statistic state is operated in 'read-only' mode as a storage of + * statistical data. + * + * @param stat Statistic state. + * + * @param dev The standard deviation. + */ +PJ_INLINE(void) pj_math_stat_set_stddev(pj_math_stat *stat, unsigned dev) +{ + if (stat->n == 0) + stat->n = 1; + stat->m2_ = dev * dev * stat->n; +} + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_MATH_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/os.h b/src/tuya_p2p/pjproject/pjlib/include/pj/os.h new file mode 100755 index 000000000..52f76c288 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/os.h @@ -0,0 +1,1408 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_OS_H__ +#define __PJ_OS_H__ + +/** + * @file os.h + * @brief OS dependent functions + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_OS Operating System Dependent Functionality. + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_SYS_INFO System Information + * @ingroup PJ_OS + * @{ + */ + +/** + * These enumeration contains constants to indicate support of miscellaneous + * system features. These will go in "flags" field of #pj_sys_info structure. + */ +typedef enum pj_sys_info_flag { + /** + * Support for Apple iOS background feature. + */ + PJ_SYS_HAS_IOS_BG = 1 + +} pj_sys_info_flag; + +/** + * This structure contains information about the system. Use #pj_get_sys_info() + * to obtain the system information. + */ +typedef struct pj_sys_info { + /** + * Null terminated string containing processor information (e.g. "i386", + * "x86_64"). It may contain empty string if the value cannot be obtained. + */ + pj_str_t machine; + + /** + * Null terminated string identifying the system operation (e.g. "Linux", + * "win32", "wince"). It may contain empty string if the value cannot be + * obtained. + */ + pj_str_t os_name; + + /** + * A number containing the operating system version number. By convention, + * this field is divided into four bytes, where the highest order byte + * contains the most major version of the OS, the next less significant + * byte contains the less major version, and so on. How the OS version + * number is mapped into these four bytes would be specific for each OS. + * For example, Linux-2.6.32-28 would yield "os_ver" value of 0x0206201c, + * while for Windows 7 it will be 0x06010000 (because dwMajorVersion is + * 6 and dwMinorVersion is 1 for Windows 7). + * + * This field may contain zero if the OS version cannot be obtained. + */ + pj_uint32_t os_ver; + + /** + * Null terminated string identifying the SDK name that is used to build + * the library (e.g. "glibc", "uclibc", "msvc", "wince"). It may contain + * empty string if the value cannot eb obtained. + */ + pj_str_t sdk_name; + + /** + * A number containing the SDK version, using the numbering convention as + * the "os_ver" field. The value will be zero if the version cannot be + * obtained. + */ + pj_uint32_t sdk_ver; + + /** + * A longer null terminated string identifying the underlying system with + * as much information as possible. + */ + pj_str_t info; + + /** + * Other flags containing system specific information. The value is + * bitmask of #pj_sys_info_flag constants. + */ + pj_uint32_t flags; + +} pj_sys_info; + +/** + * Obtain the system information. + * + * @return System information structure. + */ +PJ_DECL(const pj_sys_info *) pj_get_sys_info(void); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_THREAD Threads + * @ingroup PJ_OS + * @{ + * This module provides multithreading API. + * + * \section pj_thread_examples_sec Examples + * + * For examples, please see: + * - \ref page_pjlib_thread_test + * - \ref page_pjlib_sleep_test + * + */ + +/** + * Thread creation flags: + * - PJ_THREAD_SUSPENDED: specify that the thread should be created suspended. + */ +typedef enum pj_thread_create_flags { PJ_THREAD_SUSPENDED = 1 } pj_thread_create_flags; + +/** + * Type of thread entry function. + */ +typedef int(PJ_THREAD_FUNC pj_thread_proc)(void *); + +/** + * Size of thread struct. + */ +#if !defined(PJ_THREAD_DESC_SIZE) +#define PJ_THREAD_DESC_SIZE (64) +#endif + +/** + * Thread structure, to thread's state when the thread is created by external + * or native API. + */ +typedef long pj_thread_desc[PJ_THREAD_DESC_SIZE]; + +/** + * Get process ID. + * @return process ID. + */ +PJ_DECL(pj_uint32_t) pj_getpid(void); + +/** + * Create a new thread. + * + * @param pool The memory pool from which the thread record + * will be allocated from. + * @param thread_name The optional name to be assigned to the thread. + * @param proc Thread entry function. + * @param arg Argument to be passed to the thread entry function. + * @param stack_size The size of the stack for the new thread, or ZERO or + * PJ_THREAD_DEFAULT_STACK_SIZE to let the + * library choose the reasonable size for the stack. + * For some systems, the stack will be allocated from + * the pool, so the pool must have suitable capacity. + * @param flags Flags for thread creation, which is bitmask combination + * from enum pj_thread_create_flags. + * @param thread Pointer to hold the newly created thread. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) +pj_thread_create(pj_pool_t *pool, const char *thread_name, pj_thread_proc *proc, void *arg, pj_size_t stack_size, + unsigned flags, pj_thread_t **thread); + +/** + * Register a thread that was created by external or native API to PJLIB. + * This function must be called in the context of the thread being registered. + * When the thread is created by external function or API call, + * it must be 'registered' to PJLIB using pj_thread_register(), so that it can + * cooperate with PJLIB's framework. During registration, some data needs to + * be maintained, and this data must remain available during the thread's + * lifetime. + * + * @param thread_name The optional name to be assigned to the thread. + * @param desc Thread descriptor, which must be available throughout + * the lifetime of the thread. + * @param thread Pointer to hold the created thread handle. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_thread_register(const char *thread_name, pj_thread_desc desc, pj_thread_t **thread); + +/** + * Check if this thread has been registered to PJLIB. + * + * @return Non-zero if it is registered. + */ +PJ_DECL(pj_bool_t) pj_thread_is_registered(void); + +/** + * Get thread priority value for the thread. + * + * @param thread Thread handle. + * + * @return Thread priority value, or -1 on error. + */ +PJ_DECL(int) pj_thread_get_prio(pj_thread_t *thread); + +/** + * Set the thread priority. The priority value must be in the priority + * value range, which can be retrieved with #pj_thread_get_prio_min() and + * #pj_thread_get_prio_max() functions. + * + * For Android, this function will only set the priority of the calling thread + * (the thread param must be set to NULL or the calling thread handle). + * + * @param thread Thread handle. + * @param prio New priority to be set to the thread. + * + * @return PJ_SUCCESS on success or the error code. + */ +PJ_DECL(pj_status_t) pj_thread_set_prio(pj_thread_t *thread, int prio); + +/** + * Get the lowest priority value available for this thread. + * + * @param thread Thread handle. + * @return Minimum thread priority value, or -1 on error. + */ +PJ_DECL(int) pj_thread_get_prio_min(pj_thread_t *thread); + +/** + * Get the highest priority value available for this thread. + * + * @param thread Thread handle. + * @return Minimum thread priority value, or -1 on error. + */ +PJ_DECL(int) pj_thread_get_prio_max(pj_thread_t *thread); + +/** + * Return native handle from pj_thread_t for manipulation using native + * OS APIs. + * + * @param thread PJLIB thread descriptor. + * + * @return Native thread handle. For example, when the + * backend thread uses pthread, this function will + * return pointer to pthread_t, and on Windows, + * this function will return HANDLE. + */ +PJ_DECL(void *) pj_thread_get_os_handle(pj_thread_t *thread); + +/** + * Get thread name. + * + * @param thread The thread handle. + * + * @return Thread name as null terminated string. + */ +PJ_DECL(const char *) pj_thread_get_name(pj_thread_t *thread); + +/** + * Resume a suspended thread. + * + * @param thread The thread handle. + * + * @return zero on success. + */ +PJ_DECL(pj_status_t) pj_thread_resume(pj_thread_t *thread); + +/** + * Get the current thread. + * + * @return Thread handle of current thread. + */ +PJ_DECL(pj_thread_t *) pj_thread_this(void); + +/** + * Join thread, and block the caller thread until the specified thread exits. + * If it is called from within the thread itself, it will return immediately + * with failure status. + * If the specified thread has already been dead, or it does not exist, + * the function will return immediately with successful status. + * + * @param thread The thread handle. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_thread_join(pj_thread_t *thread); + +/** + * Destroy thread and release resources allocated for the thread. + * However, the memory allocated for the pj_thread_t itself will only be released + * when the pool used to create the thread is destroyed. + * + * @param thread The thread handle. + * + * @return zero on success. + */ +PJ_DECL(pj_status_t) pj_thread_destroy(pj_thread_t *thread); + +/** + * Put the current thread to sleep for the specified miliseconds. + * + * @param msec Miliseconds delay. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_thread_sleep(unsigned msec); + +/** + * @def PJ_CHECK_STACK() + * PJ_CHECK_STACK() macro is used to check the sanity of the stack. + * The OS implementation may check that no stack overflow occurs, and + * it also may collect statistic about stack usage. + */ +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + +#define PJ_CHECK_STACK() pj_thread_check_stack(__FILE__, __LINE__) + +/** @internal + * The implementation of stack checking. + */ +PJ_DECL(void) pj_thread_check_stack(const char *file, int line); + +/** @internal + * Get maximum stack usage statistic. + */ +PJ_DECL(pj_uint32_t) pj_thread_get_stack_max_usage(pj_thread_t *thread); + +/** @internal + * Dump thread stack status. + */ +PJ_DECL(pj_status_t) pj_thread_get_stack_info(pj_thread_t *thread, const char **file, int *line); +#else + +#define PJ_CHECK_STACK() +/** pj_thread_get_stack_max_usage() for the thread */ +#define pj_thread_get_stack_max_usage(thread) 0 +/** pj_thread_get_stack_info() for the thread */ +#define pj_thread_get_stack_info(thread, f, l) (*(f) = "", *(l) = 0) +#endif /* PJ_OS_HAS_CHECK_STACK */ + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_SYMBIAN_OS Symbian OS Specific + * @ingroup PJ_OS + * @{ + * Functionalities specific to Symbian OS. + * + * Symbian OS strongly discourages the use of polling since this wastes + * CPU power, and instead provides Active Object and Active Scheduler + * pattern to allow application (in this case, PJLIB) to register asynchronous + * tasks. PJLIB port for Symbian complies to this recommended behavior. + * As the result, few things have been changed in PJLIB for Symbian: + * - the timer heap (see @ref PJ_TIMER) is implemented with active + * object framework, and each timer entry registered to the timer + * heap will register an Active Object to the Active Scheduler. + * Because of this, polling the timer heap with pj_timer_heap_poll() + * is no longer necessary, and this function will just evaluate + * to nothing. + * - the ioqueue (see @ref PJ_IOQUEUE) is also implemented with + * active object framework, with each asynchronous operation will + * register an Active Object to the Active Scheduler. Because of + * this, polling the ioqueue with pj_ioqueue_poll() is no longer + * necessary, and this function will just evaluate to nothing. + * + * Since timer heap and ioqueue polling are no longer necessary, Symbian + * application can now poll for all events by calling + * \a User::WaitForAnyRequest() and \a CActiveScheduler::RunIfReady(). + * PJLIB provides a thin wrapper which calls these two functions, + * called pj_symbianos_poll(). + */ + +/** + * Wait the completion of any Symbian active objects. When the timeout + * value is not specified (the \a ms_timeout argument is -1), this + * function is a thin wrapper which calls \a User::WaitForAnyRequest() + * and \a CActiveScheduler::RunIfReady(). If the timeout value is + * specified, this function will schedule a timer entry to the timer + * heap (which is an Active Object), to limit the wait time for event + * occurences. Scheduling a timer entry is an expensive operation, + * therefore application should only specify a timeout value when it's + * really necessary (for example, when it's not sure there are other + * Active Objects currently running in the application). + * + * @param priority The minimum priority of the Active Objects to + * poll, which values are from CActive::TPriority + * constants. If -1 is given, CActive::EPriorityStandard. + * priority will be used. + * @param ms_timeout Optional timeout to wait. Application should + * specify -1 to let the function wait indefinitely + * for any events. + * + * @return PJ_TRUE if there have been any events executed + * during the polling. This function will only return + * PJ_FALSE if \a ms_timeout argument is specified + * (i.e. the value is not -1) and there was no event + * executed when the timeout timer elapsed. + */ +PJ_DECL(pj_bool_t) pj_symbianos_poll(int priority, int ms_timeout); + +/** + * This structure declares Symbian OS specific parameters that can be + * specified when calling #pj_symbianos_set_params(). + */ +typedef struct pj_symbianos_params { + /** + * Optional RSocketServ instance to be used by PJLIB. If this + * value is NULL, PJLIB will create a new RSocketServ instance + * when pj_init() is called. + */ + void *rsocketserv; + + /** + * Optional RConnection instance to be used by PJLIB when creating + * sockets. If this value is NULL, no RConnection will be + * specified when creating sockets. + */ + void *rconnection; + + /** + * Optional RHostResolver instance to be used by PJLIB. If this value + * is NULL, a new RHostResolver instance will be created when + * pj_init() is called. + */ + void *rhostresolver; + + /** + * Optional RHostResolver for IPv6 instance to be used by PJLIB. + * If this value is NULL, a new RHostResolver instance will be created + * when pj_init() is called. + */ + void *rhostresolver6; + +} pj_symbianos_params; + +/** + * Specify Symbian OS parameters to be used by PJLIB. This function MUST + * be called before #pj_init() is called. + * + * @param prm Symbian specific parameters. + * + * @return PJ_SUCCESS if the parameters can be applied + * successfully. + */ +PJ_DECL(pj_status_t) pj_symbianos_set_params(pj_symbianos_params *prm); + +/** + * Notify PJLIB that the access point connection has been down or unusable + * and PJLIB should not try to access the Symbian socket API (especially ones + * that send packets). Sending packet when RConnection is reconnected to + * different access point may cause the WaitForRequest() for the function to + * block indefinitely. + * + * @param up If set to PJ_FALSE it will cause PJLIB to not try + * to access socket API, and error will be returned + * immediately instead. + */ +PJ_DECL(void) pj_symbianos_set_connection_status(pj_bool_t up); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_TLS Thread Local Storage. + * @ingroup PJ_OS + * @{ + */ + +/** + * Allocate thread local storage index. The initial value of the variable at + * the index is zero. + * + * @param index Pointer to hold the return value. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_thread_local_alloc(long *index); + +/** + * Deallocate thread local variable. + * + * @param index The variable index. + */ +PJ_DECL(void) pj_thread_local_free(long index); + +/** + * Set the value of thread local variable. + * + * @param index The index of the variable. + * @param value The value. + */ +PJ_DECL(pj_status_t) pj_thread_local_set(long index, void *value); + +/** + * Get the value of thread local variable. + * + * @param index The index of the variable. + * @return The value. + */ +PJ_DECL(void *) pj_thread_local_get(long index); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_ATOMIC Atomic Variables + * @ingroup PJ_OS + * @{ + * + * This module provides API to manipulate atomic variables. + * + * \section pj_atomic_examples_sec Examples + * + * For some example codes, please see: + * - @ref page_pjlib_atomic_test + */ + +/** + * Create atomic variable. + * + * @param pool The pool. + * @param initial The initial value of the atomic variable. + * @param atomic Pointer to hold the atomic variable upon return. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_atomic_create(pj_pool_t *pool, pj_atomic_value_t initial, pj_atomic_t **atomic); + +/** + * Destroy atomic variable. + * + * @param atomic_var the atomic variable. + * + * @return PJ_SUCCESS if success. + */ +PJ_DECL(pj_status_t) pj_atomic_destroy(pj_atomic_t *atomic_var); + +/** + * Set the value of an atomic type, and return the previous value. + * + * @param atomic_var the atomic variable. + * @param value value to be set to the variable. + */ +PJ_DECL(void) pj_atomic_set(pj_atomic_t *atomic_var, pj_atomic_value_t value); + +/** + * Get the value of an atomic type. + * + * @param atomic_var the atomic variable. + * + * @return the value of the atomic variable. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_get(pj_atomic_t *atomic_var); + +/** + * Increment the value of an atomic type. + * + * @param atomic_var the atomic variable. + */ +PJ_DECL(void) pj_atomic_inc(pj_atomic_t *atomic_var); + +/** + * Increment the value of an atomic type and get the result. + * + * @param atomic_var the atomic variable. + * + * @return The incremented value. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_inc_and_get(pj_atomic_t *atomic_var); + +/** + * Decrement the value of an atomic type. + * + * @param atomic_var the atomic variable. + */ +PJ_DECL(void) pj_atomic_dec(pj_atomic_t *atomic_var); + +/** + * Decrement the value of an atomic type and get the result. + * + * @param atomic_var the atomic variable. + * + * @return The decremented value. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_dec_and_get(pj_atomic_t *atomic_var); + +/** + * Add a value to an atomic type. + * + * @param atomic_var The atomic variable. + * @param value Value to be added. + */ +PJ_DECL(void) pj_atomic_add(pj_atomic_t *atomic_var, pj_atomic_value_t value); + +/** + * Add a value to an atomic type and get the result. + * + * @param atomic_var The atomic variable. + * @param value Value to be added. + * + * @return The result after the addition. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_add_and_get(pj_atomic_t *atomic_var, pj_atomic_value_t value); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_MUTEX Mutexes. + * @ingroup PJ_OS + * @{ + * + * Mutex manipulation. Alternatively, application can use higher abstraction + * for lock objects, which provides uniform API for all kinds of lock + * mechanisms, including mutex. See @ref PJ_LOCK for more information. + */ + +/** + * Mutex types: + * - PJ_MUTEX_DEFAULT: default mutex type, which is system dependent. + * - PJ_MUTEX_SIMPLE: non-recursive mutex. + * - PJ_MUTEX_RECURSE: recursive mutex. + */ +typedef enum pj_mutex_type_e { PJ_MUTEX_DEFAULT, PJ_MUTEX_SIMPLE, PJ_MUTEX_RECURSE } pj_mutex_type_e; + +/** + * Create mutex of the specified type. + * + * @param pool The pool. + * @param name Name to be associated with the mutex (for debugging). + * @param type The type of the mutex, of type #pj_mutex_type_e. + * @param mutex Pointer to hold the returned mutex instance. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_create(pj_pool_t *pool, const char *name, int type, pj_mutex_t **mutex); + +/** + * Create simple, non-recursive mutex. + * This function is a simple wrapper for #pj_mutex_create to create + * non-recursive mutex. + * + * @param pool The pool. + * @param name Mutex name. + * @param mutex Pointer to hold the returned mutex instance. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_create_simple(pj_pool_t *pool, const char *name, pj_mutex_t **mutex); + +/** + * Create recursive mutex. + * This function is a simple wrapper for #pj_mutex_create to create + * recursive mutex. + * + * @param pool The pool. + * @param name Mutex name. + * @param mutex Pointer to hold the returned mutex instance. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_create_recursive(pj_pool_t *pool, const char *name, pj_mutex_t **mutex); + +/** + * Acquire mutex lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_lock(pj_mutex_t *mutex); + +/** + * Release mutex lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_unlock(pj_mutex_t *mutex); + +/** + * Try to acquire mutex lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code if the + * lock couldn't be acquired. + */ +PJ_DECL(pj_status_t) pj_mutex_trylock(pj_mutex_t *mutex); + +/** + * Destroy mutex. + * + * @param mutex Te mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_destroy(pj_mutex_t *mutex); + +/** + * Determine whether calling thread is owning the mutex (only available when + * PJ_DEBUG is set). + * @param mutex The mutex. + * @return Non-zero if yes. + */ +PJ_DECL(pj_bool_t) pj_mutex_is_locked(pj_mutex_t *mutex); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_RW_MUTEX Reader/Writer Mutex + * @ingroup PJ_OS + * @{ + * Reader/writer mutex is a classic synchronization object where multiple + * readers can acquire the mutex, but only a single writer can acquire the + * mutex. + */ + +/** + * Opaque declaration for reader/writer mutex. + * Reader/writer mutex is a classic synchronization object where multiple + * readers can acquire the mutex, but only a single writer can acquire the + * mutex. + */ +typedef struct pj_rwmutex_t pj_rwmutex_t; + +/** + * Create reader/writer mutex. + * + * @param pool Pool to allocate memory for the mutex. + * @param name Name to be assigned to the mutex. + * @param mutex Pointer to receive the newly created mutex. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, pj_rwmutex_t **mutex); + +/** + * Lock the mutex for reading. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex); + +/** + * Lock the mutex for writing. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex); + +/** + * Release read lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex); + +/** + * Release write lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex); + +/** + * Destroy reader/writer mutex. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_CRIT_SEC Critical sections. + * @ingroup PJ_OS + * @{ + * Critical section protection can be used to protect regions where: + * - mutual exclusion protection is needed. + * - it's rather too expensive to create a mutex. + * - the time spent in the region is very very brief. + * + * Critical section is a global object, and it prevents any threads from + * entering any regions that are protected by critical section once a thread + * is already in the section. + * + * Critial section is \a not recursive! + * + * Application MUST NOT call any functions that may cause current + * thread to block (such as allocating memory, performing I/O, locking mutex, + * etc.) while holding the critical section. + */ +/** + * Enter critical section. + */ +PJ_DECL(void) pj_enter_critical_section(void); + +/** + * Leave critical section. + */ +PJ_DECL(void) pj_leave_critical_section(void); + +/** + * @} + */ + +/* **************************************************************************/ +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +/** + * @defgroup PJ_SEM Semaphores. + * @ingroup PJ_OS + * @{ + * + * This module provides abstraction for semaphores, where available. + */ + +/** + * Create semaphore. + * + * @param pool The pool. + * @param name Name to be assigned to the semaphore (for logging purpose) + * @param initial The initial count of the semaphore. + * @param max The maximum count of the semaphore. + * @param sem Pointer to hold the semaphore created. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_create(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_sem_t **sem); + +/** + * Wait for semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_wait(pj_sem_t *sem); + +/** + * Try wait for semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_trywait(pj_sem_t *sem); + +/** + * Release semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_post(pj_sem_t *sem); + +/** + * Destroy semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_destroy(pj_sem_t *sem); + +/** + * @} + */ +#endif /* PJ_HAS_SEMAPHORE */ + +/* **************************************************************************/ +#if defined(PJ_HAS_EVENT_OBJ) && PJ_HAS_EVENT_OBJ != 0 +/** + * @defgroup PJ_EVENT Event Object. + * @ingroup PJ_OS + * @{ + * + * This module provides abstraction to event object (e.g. Win32 Event) where + * available. Event objects can be used for synchronization among threads. + */ + +/** + * Create event object. + * + * @param pool The pool. + * @param name The name of the event object (for logging purpose). + * @param manual_reset Specify whether the event is manual-reset + * @param initial Specify the initial state of the event object. + * @param event Pointer to hold the returned event object. + * + * @return event handle, or NULL if failed. + */ +PJ_DECL(pj_status_t) +pj_event_create(pj_pool_t *pool, const char *name, pj_bool_t manual_reset, pj_bool_t initial, pj_event_t **event); + +/** + * Wait for event to be signaled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_wait(pj_event_t *event); + +/** + * Try wait for event object to be signalled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_trywait(pj_event_t *event); + +/** + * Set the event object state to signaled. For auto-reset event, this + * will only release the first thread that are waiting on the event. For + * manual reset event, the state remains signaled until the event is reset. + * If there is no thread waiting on the event, the event object state + * remains signaled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_set(pj_event_t *event); + +/** + * Set the event object to signaled state to release appropriate number of + * waiting threads and then reset the event object to non-signaled. For + * manual-reset event, this function will release all waiting threads. For + * auto-reset event, this function will only release one waiting thread. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_pulse(pj_event_t *event); + +/** + * Set the event object state to non-signaled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_reset(pj_event_t *event); + +/** + * Destroy the event object. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_destroy(pj_event_t *event); + +/** + * @} + */ +#endif /* PJ_HAS_EVENT_OBJ */ + +/* **************************************************************************/ +/** + * @addtogroup PJ_TIME Time Data Type and Manipulation. + * @ingroup PJ_OS + * @{ + * This module provides API for manipulating time. + * + * \section pj_time_examples_sec Examples + * + * For examples, please see: + * - \ref page_pjlib_sleep_test + */ + +/** + * Get current time of day in local representation. + * + * @param tv Variable to store the result. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_gettimeofday(pj_time_val *tv); + +/** + * Parse time value into date/time representation. + * + * @param tv The time. + * @param pt Variable to store the date time result. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_decode(const pj_time_val *tv, pj_parsed_time *pt); + +/** + * Encode date/time to time value. + * + * @param pt The date/time. + * @param tv Variable to store time value result. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_encode(const pj_parsed_time *pt, pj_time_val *tv); + +/** + * Convert local time to GMT. + * + * @param tv Time to convert. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_local_to_gmt(pj_time_val *tv); + +/** + * Convert GMT to local time. + * + * @param tv Time to convert. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_gmt_to_local(pj_time_val *tv); + +/** + * @} + */ + +/* **************************************************************************/ +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 + +/** + * @defgroup PJ_TERM Terminal + * @ingroup PJ_OS + * @{ + */ + +/** + * Set current terminal color. + * + * @param color The RGB color. + * + * @return zero on success. + */ +PJ_DECL(pj_status_t) pj_term_set_color(pj_color_t color); + +/** + * Get current terminal foreground color. + * + * @return RGB color. + */ +PJ_DECL(pj_color_t) pj_term_get_color(void); + +/** + * @} + */ + +#endif /* PJ_TERM_HAS_COLOR */ + +/* **************************************************************************/ +/** + * @defgroup PJ_TIMESTAMP High Resolution Timestamp + * @ingroup PJ_OS + * @{ + * + * PJLIB provides High Resolution Timestamp API to access highest + * resolution timestamp value provided by the platform. The API is usefull + * to measure precise elapsed time, and can be used in applications such + * as profiling. + * + * The timestamp value is represented in cycles, and can be related to + * normal time (in seconds or sub-seconds) using various functions provided. + * + * \section pj_timestamp_examples_sec Examples + * + * For examples, please see: + * - \ref page_pjlib_sleep_test + * - \ref page_pjlib_timestamp_test + */ + +/* + * High resolution timer. + */ +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 + +/** + * Get monotonic time since some unspecified starting point. + * + * @param tv Variable to store the result. + * + * @return PJ_SUCCESS if successful. + */ +PJ_DECL(pj_status_t) pj_gettickcount(pj_time_val *tv); + +/** + * Acquire high resolution timer value. The time value are stored + * in cycles. + * + * @param ts High resolution timer value. + * @return PJ_SUCCESS or the appropriate error code. + * + * @see pj_get_timestamp_freq(). + */ +PJ_DECL(pj_status_t) pj_get_timestamp(pj_timestamp *ts); + +/** + * Get high resolution timer frequency, in cycles per second. + * + * @param freq Timer frequency, in cycles per second. + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq); + +/** + * Set timestamp from 32bit values. + * @param t The timestamp to be set. + * @param hi The high 32bit part. + * @param lo The low 32bit part. + */ +PJ_INLINE(void) pj_set_timestamp32(pj_timestamp *t, pj_uint32_t hi, pj_uint32_t lo) +{ + t->u32.hi = hi; + t->u32.lo = lo; +} + +/** + * Compare timestamp t1 and t2. + * @param t1 t1. + * @param t2 t2. + * @return -1 if (t1 < t2), 1 if (t1 > t2), or 0 if (t1 == t2) + */ +PJ_INLINE(int) pj_cmp_timestamp(const pj_timestamp *t1, const pj_timestamp *t2) +{ +#if PJ_HAS_INT64 + if (t1->u64 < t2->u64) + return -1; + else if (t1->u64 > t2->u64) + return 1; + else + return 0; +#else + if (t1->u32.hi < t2->u32.hi || (t1->u32.hi == t2->u32.hi && t1->u32.lo < t2->u32.lo)) + return -1; + else if (t1->u32.hi > t2->u32.hi || (t1->u32.hi == t2->u32.hi && t1->u32.lo > t2->u32.lo)) + return 1; + else + return 0; +#endif +} + +/** + * Add timestamp t2 to t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_add_timestamp(pj_timestamp *t1, const pj_timestamp *t2) +{ +#if PJ_HAS_INT64 + t1->u64 += t2->u64; +#else + pj_uint32_t old = t1->u32.lo; + t1->u32.hi += t2->u32.hi; + t1->u32.lo += t2->u32.lo; + if (t1->u32.lo < old) + ++t1->u32.hi; +#endif +} + +/** + * Add timestamp t2 to t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_add_timestamp32(pj_timestamp *t1, pj_uint32_t t2) +{ +#if PJ_HAS_INT64 + t1->u64 += t2; +#else + pj_uint32_t old = t1->u32.lo; + t1->u32.lo += t2; + if (t1->u32.lo < old) + ++t1->u32.hi; +#endif +} + +/** + * Substract timestamp t2 from t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_sub_timestamp(pj_timestamp *t1, const pj_timestamp *t2) +{ +#if PJ_HAS_INT64 + t1->u64 -= t2->u64; +#else + t1->u32.hi -= t2->u32.hi; + if (t1->u32.lo >= t2->u32.lo) + t1->u32.lo -= t2->u32.lo; + else { + t1->u32.lo -= t2->u32.lo; + --t1->u32.hi; + } +#endif +} + +/** + * Substract timestamp t2 from t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_sub_timestamp32(pj_timestamp *t1, pj_uint32_t t2) +{ +#if PJ_HAS_INT64 + t1->u64 -= t2; +#else + if (t1->u32.lo >= t2) + t1->u32.lo -= t2; + else { + t1->u32.lo -= t2; + --t1->u32.hi; + } +#endif +} + +/** + * Get the timestamp difference between t2 and t1 (that is t2 minus t1), + * and return a 32bit signed integer difference. + */ +PJ_INLINE(pj_int32_t) pj_timestamp_diff32(const pj_timestamp *t1, const pj_timestamp *t2) +{ + /* Be careful with the signess (I think!) */ +#if PJ_HAS_INT64 + pj_int64_t diff = t2->u64 - t1->u64; + return (pj_int32_t)diff; +#else + pj_int32 diff = t2->u32.lo - t1->u32.lo; + return diff; +#endif +} + +/** + * Calculate the elapsed time, and store it in pj_time_val. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time as #pj_time_val. + * + * @see pj_elapsed_usec(), pj_elapsed_cycle(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_time_val) pj_elapsed_time(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time as 32-bit miliseconds. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in milisecond. + * + * @see pj_elapsed_time(), pj_elapsed_cycle(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_msec(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Variant of #pj_elapsed_msec() which returns 64bit value. + */ +PJ_DECL(pj_uint64_t) pj_elapsed_msec64(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time in 32-bit microseconds. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in microsecond. + * + * @see pj_elapsed_time(), pj_elapsed_cycle(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_usec(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time in 32-bit nanoseconds. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in nanoseconds. + * + * @see pj_elapsed_time(), pj_elapsed_cycle(), pj_elapsed_usec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_nanosec(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time in 32-bit cycles. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in cycles. + * + * @see pj_elapsed_usec(), pj_elapsed_time(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_cycle(const pj_timestamp *start, const pj_timestamp *stop); + +#endif /* PJ_HAS_HIGH_RES_TIMER */ + +/** @} */ + +/* **************************************************************************/ +/** + * @defgroup PJ_APP_OS Application execution + * @ingroup PJ_OS + * @{ + */ + +/** + * Type for application main function. + */ +typedef int (*pj_main_func_ptr)(int argc, char *argv[]); + +/** + * Run the application. This function has to be called in the main thread + * and after doing the necessary initialization according to the flags + * provided, it will call main_func() function. + * + * @param main_func Application's main function. + * @param argc Number of arguments from the main() function, which + * will be passed to main_func() function. + * @param argv The arguments from the main() function, which will + * be passed to main_func() function. + * @param flags Flags for application execution, currently must be 0. + * + * @return main_func()'s return value. + */ +PJ_DECL(int) pj_run_app(pj_main_func_ptr main_func, int argc, char *argv[], unsigned flags); + +/** @} */ + +/* **************************************************************************/ +/** + * Internal PJLIB function to initialize the threading subsystem. + * @return PJ_SUCCESS or the appropriate error code. + */ +pj_status_t pj_thread_init(void); + +PJ_END_DECL + +#endif /* __PJ_OS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool.h new file mode 100755 index 000000000..28423a65e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool.h @@ -0,0 +1,877 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +/* See if we use pool's alternate API. + * The alternate API is used e.g. to implement pool debugging. + */ +#if PJ_HAS_POOL_ALT_API +#include +#endif + +#ifndef __PJ_POOL_H__ +#define __PJ_POOL_H__ + +/** + * @file pool.h + * @brief Memory Pool. + */ + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_POOL_GROUP Fast Memory Pool + * @brief + * Memory pools allow dynamic memory allocation comparable to malloc or the + * new in operator C++. Those implementations are not desirable for very + * high performance applications or real-time systems, because of the + * performance bottlenecks and it suffers from fragmentation issue. + * + * \section PJ_POOL_INTRO_SEC PJLIB's Memory Pool + * \subsection PJ_POOL_ADVANTAGE_SUBSEC Advantages + * + * PJLIB's pool has many advantages over traditional malloc/new operator and + * over other memory pool implementations, because: + * - unlike other memory pool implementation, it allows allocation of + * memory chunks of different sizes, + * - it's very very fast. + * \n + * Memory chunk allocation is not only an O(1) + * operation, but it's also very simple (just + * few pointer arithmetic operations) and it doesn't require locking + * any mutex, + * - it's memory efficient. + * \n + * Pool doesn't keep track individual memory chunks allocated by + * applications, so there is no additional overhead needed for each + * memory allocation (other than possible additional of few bytes, up to + * PJ_POOL_ALIGNMENT-1, for aligning the memory). + * But see the @ref PJ_POOL_CAVEATS_SUBSEC below. + * - it prevents memory leaks. + * \n + * Memory pool inherently has garbage collection functionality. In fact, + * there is no need to free the chunks allocated from the memory pool. + * All chunks previously allocated from the pool will be freed once the + * pool itself is destroyed. This would prevent memory leaks that haunt + * programmers for decades, and it provides additional performance + * advantage over traditional malloc/new operator. + * + * Even more, PJLIB's memory pool provides some additional usability and + * flexibility for applications: + * - memory leaks are easily traceable, since memory pool is assigned name, + * and application can inspect what pools currently active in the system. + * - by design, memory allocation from a pool is not thread safe. We assumed + * that a pool will be owned by a higher level object, and thread safety + * should be handled by that object. This enables very fast pool operations + * and prevents unnecessary locking operations, + * - by default, the memory pool API behaves more like C++ new operator, + * in that it will throw PJ_NO_MEMORY_EXCEPTION exception (see + * @ref PJ_EXCEPT) when memory chunk allocation fails. This enables failure + * handling to be done on more high level function (instead of checking + * the result of pj_pool_alloc() everytime). If application doesn't like + * this, the default behavior can be changed on global basis by supplying + * different policy to the pool factory. + * - any memory allocation backend allocator/deallocator may be used. By + * default, the policy uses malloc() and free() to manage the pool's block, + * but application may use different strategy, for example to allocate + * memory blocks from a globally static memory location. + * + * + * \subsection PJ_POOL_PERFORMANCE_SUBSEC Performance + * + * The result of PJLIB's memory design and careful implementation is a + * memory allocation strategy that can speed-up the memory allocations + * and deallocations by up to 30 times compared to standard + * malloc()/free() (more than 150 million allocations per second on a + * P4/3.0GHz Linux machine). + * + * (Note: your mileage may vary, of course. You can see how much PJLIB's + * pool improves the performance over malloc()/free() in your target + * system by running pjlib-test application). + * + * + * \subsection PJ_POOL_CAVEATS_SUBSEC Caveats + * + * There are some caveats though! + * + * When creating pool, PJLIB requires applications to specify the initial + * pool size, and as soon as the pool is created, PJLIB allocates memory + * from the system by that size. Application designers MUST choose the + * initial pool size carefully, since choosing too big value will result in + * wasting system's memory. + * + * But the pool can grow. Application designer can specify how the + * pool will grow in size, by specifying the size increment when creating + * the pool. + * + * The pool, however, cannot shrink! Since there is no + * function to deallocate memory chunks, there is no way for the pool to + * release back unused memory to the system. + * Application designers must be aware that constant memory allocations + * from pool that has infinite life-time may cause the memory usage of + * the application to grow over time. + * + * + * \section PJ_POOL_USING_SEC Using Memory Pool + * + * This section describes how to use PJLIB's memory pool framework. + * As we hope the readers will witness, PJLIB's memory pool API is quite + * straightforward. + * + * \subsection PJ_POOL_USING_F Create Pool Factory + * First, application needs to initialize a pool factory (this normally + * only needs to be done once in one application). PJLIB provides + * a pool factory implementation called caching pool (see @ref + * PJ_CACHING_POOL), and it is initialized by calling #pj_caching_pool_init(). + * + * \subsection PJ_POOL_USING_P Create The Pool + * Then application creates the pool object itself with #pj_pool_create(), + * specifying among other thing the pool factory where the pool should + * be created from, the pool name, initial size, and increment/expansion + * size. + * + * \subsection PJ_POOL_USING_M Allocate Memory as Required + * Then whenever application needs to allocate dynamic memory, it would + * call #pj_pool_alloc(), #pj_pool_calloc(), or #pj_pool_zalloc() to + * allocate memory chunks from the pool. + * + * \subsection PJ_POOL_USING_DP Destroy the Pool + * When application has finished with the pool, it should call + * #pj_pool_release() to release the pool object back to the factory. + * Depending on the types of the factory, this may release the memory back + * to the operating system. + * + * \subsection PJ_POOL_USING_Dc Destroy the Pool Factory + * And finally, before application quites, it should deinitialize the + * pool factory, to make sure that all memory blocks allocated by the + * factory are released back to the operating system. After this, of + * course no more memory pool allocation can be requested. + * + * \subsection PJ_POOL_USING_EX Example + * Below is a sample complete program that utilizes PJLIB's memory pool. + * + * \code + + #include + + #define THIS_FILE "pool_sample.c" + + static void my_perror(const char *title, pj_status_t status) + { + PJ_PERROR(1,(THIS_FILE, status, title)); + } + + static void pool_demo_1(pj_pool_factory *pfactory) + { + unsigned i; + pj_pool_t *pool; + + // Must create pool before we can allocate anything + pool = pj_pool_create(pfactory, // the factory + "pool1", // pool's name + 4000, // initial size + 4000, // increment size + NULL); // use default callback. + if (pool == NULL) { + my_perror("Error creating pool", PJ_ENOMEM); + return; + } + + // Demo: allocate some memory chunks + for (i=0; i<1000; ++i) { + void *p; + + p = pj_pool_alloc(pool, (pj_rand()+1) % 512); + + // Do something with p + ... + + // Look! No need to free p!! + } + + // Done with silly demo, must free pool to release all memory. + pj_pool_release(pool); + } + + int main() + { + pj_caching_pool cp; + pj_status_t status; + + // Must init PJLIB before anything else + status = pj_init(); + if (status != PJ_SUCCESS) { + my_perror("Error initializing PJLIB", status); + return 1; + } + + // Create the pool factory, in this case, a caching pool, + // using default pool policy. + pj_caching_pool_init(&cp, NULL, 1024*1024 ); + + // Do a demo + pool_demo_1(&cp.factory); + + // Done with demos, destroy caching pool before exiting app. + pj_caching_pool_destroy(&cp); + + return 0; + } + + \endcode + * + * More information about pool factory, the pool object, and caching pool + * can be found on the Module Links below. + */ + +/** + * @defgroup PJ_POOL Memory Pool Object + * @ingroup PJ_POOL_GROUP + * @brief + * The memory pool is an opaque object created by pool factory. + * Application uses this object to request a memory chunk, by calling + * #pj_pool_alloc(), #pj_pool_calloc(), or #pj_pool_zalloc(). + * When the application has finished using + * the pool, it must call #pj_pool_release() to free all the chunks previously + * allocated and release the pool back to the factory. + * + * A memory pool is initialized with an initial amount of memory, which is + * called a block. Pool can be configured to dynamically allocate more memory + * blocks when it runs out of memory. + * + * The pool doesn't keep track of individual memory allocations + * by user, and the user doesn't have to free these indidual allocations. This + * makes memory allocation simple and very fast. All the memory allocated from + * the pool will be destroyed when the pool itself is destroyed. + * + * \section PJ_POOL_THREADING_SEC More on Threading Policies + * - By design, memory allocation from a pool is not thread safe. We assumed + * that a pool will be owned by an object, and thread safety should be + * handled by that object. Thus these functions are not thread safe: + * - #pj_pool_alloc, + * - #pj_pool_calloc, + * - and other pool statistic functions. + * - Threading in the pool factory is decided by the policy set for the + * factory when it was created. + * + * \section PJ_POOL_EXAMPLES_SEC Examples + * + * For some sample codes on how to use the pool, please see: + * - @ref page_pjlib_pool_test + * + * @{ + */ + +/** + * The type for function to receive callback from the pool when it is unable + * to allocate memory. The elegant way to handle this condition is to throw + * exception, and this is what is expected by most of this library + * components. + */ +typedef void pj_pool_callback(pj_pool_t *pool, pj_size_t size); + +/** + * This class, which is used internally by the pool, describes a single + * block of memory from which user memory allocations will be allocated from. + */ +typedef struct pj_pool_block { + PJ_DECL_LIST_MEMBER(struct pj_pool_block); /**< List's prev and next. */ + unsigned char *buf; /**< Start of buffer. */ + unsigned char *cur; /**< Current alloc ptr. */ + unsigned char *end; /**< End of buffer. */ +} pj_pool_block; + +/** + * This structure describes the memory pool. Only implementors of pool factory + * need to care about the contents of this structure. + */ +struct pj_pool_t { + PJ_DECL_LIST_MEMBER(struct pj_pool_t); /**< Standard list elements. */ + + /** Pool name */ + char obj_name[PJ_MAX_OBJ_NAME]; + + /** Pool factory. */ + pj_pool_factory *factory; + + /** Data put by factory */ + void *factory_data; + + /** Current capacity allocated by the pool. */ + pj_size_t capacity; + + /** Size of memory block to be allocated when the pool runs out of memory */ + pj_size_t increment_size; + + /** List of memory blocks allcoated by the pool. */ + pj_pool_block block_list; + + /** The callback to be called when the pool is unable to allocate memory. */ + pj_pool_callback *callback; +}; + +/** + * Guidance on how much memory required for initial pool administrative data. + */ +#define PJ_POOL_SIZE (sizeof(struct pj_pool_t)) + +/** + * Pool memory alignment (must be power of 2). + */ +#ifndef PJ_POOL_ALIGNMENT +#define PJ_POOL_ALIGNMENT 4 +#endif + +/** + * Create a new pool from the pool factory. This wrapper will call create_pool + * member of the pool factory. + * + * @param factory The pool factory. + * @param name The name to be assigned to the pool. The name should + * not be longer than PJ_MAX_OBJ_NAME (32 chars), or + * otherwise it will be truncated. + * @param initial_size The size of initial memory blocks taken by the pool. + * Note that the pool will take 68+20 bytes for + * administrative area from this block. + * @param increment_size the size of each additional blocks to be allocated + * when the pool is running out of memory. If user + * requests memory which is larger than this size, then + * an error occurs. + * Note that each time a pool allocates additional block, + * it needs PJ_POOL_SIZE more to store some + * administrative info. + * @param callback Callback to be called when error occurs in the pool. + * If this value is NULL, then the callback from pool + * factory policy will be used. + * Note that when an error occurs during pool creation, + * the callback itself is not called. Instead, NULL + * will be returned. + * + * @return The memory pool, or NULL. + */ +PJ_IDECL(pj_pool_t *) +pj_pool_create(pj_pool_factory *factory, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback); + +/** + * Release the pool back to pool factory. + * + * @param pool Memory pool. + */ +PJ_IDECL(void) pj_pool_release(pj_pool_t *pool); + +/** + * Release the pool back to pool factory and set the pool pointer to zero. + * + * @param ppool Pointer to memory pool. + */ +PJ_IDECL(void) pj_pool_safe_release(pj_pool_t **ppool); + +/** + * Release the pool back to pool factory and set the pool pointer to zero. + * The memory pool content will be wiped out first before released. + * + * @param ppool Pointer to memory pool. + */ +PJ_IDECL(void) pj_pool_secure_release(pj_pool_t **ppool); + +/** + * Get pool object name. + * + * @param pool the pool. + * + * @return pool name as NULL terminated string. + */ +PJ_IDECL(const char *) pj_pool_getobjname(const pj_pool_t *pool); + +/** + * Reset the pool to its state when it was initialized. + * This means that if additional blocks have been allocated during runtime, + * then they will be freed. Only the original block allocated during + * initialization is retained. This function will also reset the internal + * counters, such as pool capacity and used size. + * + * @param pool the pool. + */ +PJ_DECL(void) pj_pool_reset(pj_pool_t *pool); + +/** + * Get the pool capacity, that is, the system storage that have been allocated + * by the pool, and have been used/will be used to allocate user requests. + * There's no guarantee that the returned value represent a single + * contiguous block, because the capacity may be spread in several blocks. + * + * @param pool the pool. + * + * @return the capacity. + */ +PJ_IDECL(pj_size_t) pj_pool_get_capacity(pj_pool_t *pool); + +/** + * Get the total size of user allocation request. + * + * @param pool the pool. + * + * @return the total size. + */ +PJ_IDECL(pj_size_t) pj_pool_get_used_size(pj_pool_t *pool); + +/** + * Allocate storage with the specified size from the pool. + * If there's no storage available in the pool, then the pool can allocate more + * blocks if the increment size is larger than the requested size. + * + * @param pool the pool. + * @param size the requested size. + * + * @return pointer to the allocated memory. + * + * @see PJ_POOL_ALLOC_T + */ +PJ_IDECL(void *) pj_pool_alloc(pj_pool_t *pool, pj_size_t size); + +/** + * Allocate storage from the pool, and initialize it to zero. + * This function behaves like pj_pool_alloc(), except that the storage will + * be initialized to zero. + * + * @param pool the pool. + * @param count the number of elements in the array. + * @param elem the size of individual element. + * + * @return pointer to the allocated memory. + */ +PJ_IDECL(void *) pj_pool_calloc(pj_pool_t *pool, pj_size_t count, pj_size_t elem); + +/** + * Allocate storage from the pool and initialize it to zero. + * + * @param pool The pool. + * @param size The size to be allocated. + * + * @return Pointer to the allocated memory. + * + * @see PJ_POOL_ZALLOC_T + */ +PJ_INLINE(void *) pj_pool_zalloc(pj_pool_t *pool, pj_size_t size) +{ + return pj_pool_calloc(pool, 1, size); +} + +/** + * This macro allocates memory from the pool and returns the instance of + * the specified type. It provides a stricker type safety than pj_pool_alloc() + * since the return value of this macro will be type-casted to the specified + * type. + * + * @param pool The pool + * @param type The type of object to be allocated + * + * @return Memory buffer of the specified type. + */ +#define PJ_POOL_ALLOC_T(pool, type) ((type *)pj_pool_alloc(pool, sizeof(type))) + +/** + * This macro allocates memory from the pool, zeroes the buffer, and + * returns the instance of the specified type. It provides a stricker type + * safety than pj_pool_zalloc() since the return value of this macro will be + * type-casted to the specified type. + * + * @param pool The pool + * @param type The type of object to be allocated + * + * @return Memory buffer of the specified type. + */ +#define PJ_POOL_ZALLOC_T(pool, type) ((type *)pj_pool_zalloc(pool, sizeof(type))) + +/* + * Internal functions + */ +/** Internal function */ +PJ_IDECL(void *) pj_pool_alloc_from_block(pj_pool_block *block, pj_size_t size); +/** Internal function */ +PJ_DECL(void *) pj_pool_allocate_find(pj_pool_t *pool, pj_size_t size); + +/** + * @} // PJ_POOL + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_POOL_FACTORY Pool Factory and Policy + * @ingroup PJ_POOL_GROUP + * @brief + * A pool object must be created through a factory. A factory not only provides + * generic interface functions to create and release pool, but also provides + * strategy to manage the life time of pools. One sample implementation, + * \a pj_caching_pool, can be set to keep the pools released by application for + * future use as long as the total memory is below the limit. + * + * The pool factory interface declared in PJLIB is designed to be extensible. + * Application can define its own strategy by creating it's own pool factory + * implementation, and this strategy can be used even by existing library + * without recompilation. + * + * \section PJ_POOL_FACTORY_ITF Pool Factory Interface + * The pool factory defines the following interface: + * - \a policy: the memory pool factory policy. + * - \a create_pool(): create a new memory pool. + * - \a release_pool(): release memory pool back to factory. + * + * \section PJ_POOL_FACTORY_POL Pool Factory Policy. + * + * A pool factory only defines functions to create and release pool and how + * to manage pools, but the rest of the functionalities are controlled by + * policy. A pool policy defines: + * - how memory block is allocated and deallocated (the default implementation + * allocates and deallocate memory by calling malloc() and free()). + * - callback to be called when memory allocation inside a pool fails (the + * default implementation will throw PJ_NO_MEMORY_EXCEPTION exception). + * - concurrency when creating and releasing pool from/to the factory. + * + * A pool factory can be given different policy during creation to make + * it behave differently. For example, caching pool factory can be configured + * to allocate and deallocate from a static/contiguous/preallocated memory + * instead of using malloc()/free(). + * + * What strategy/factory and what policy to use is not defined by PJLIB, but + * instead is left to application to make use whichever is most efficient for + * itself. + * + * The pool factory policy controls the behaviour of memory factories, and + * defines the following interface: + * - \a block_alloc(): allocate memory block from backend memory mgmt/system. + * - \a block_free(): free memory block back to backend memory mgmt/system. + * @{ + */ + +/* We unfortunately don't have support for factory policy options as now, + so we keep this commented at the moment. +enum PJ_POOL_FACTORY_OPTION +{ + PJ_POOL_FACTORY_SERIALIZE = 1 +}; +*/ + +/** + * This structure declares pool factory interface. + */ +typedef struct pj_pool_factory_policy { + /** + * Allocate memory block (for use by pool). This function is called + * by memory pool to allocate memory block. + * + * @param factory Pool factory. + * @param size The size of memory block to allocate. + * + * @return Memory block. + */ + void *(*block_alloc)(pj_pool_factory *factory, pj_size_t size); + + /** + * Free memory block. + * + * @param factory Pool factory. + * @param mem Memory block previously allocated by block_alloc(). + * @param size The size of memory block. + */ + void (*block_free)(pj_pool_factory *factory, void *mem, pj_size_t size); + + /** + * Default callback to be called when memory allocation fails. + */ + pj_pool_callback *callback; + + /** + * Option flags. + */ + unsigned flags; + +} pj_pool_factory_policy; + +/** + * This constant denotes the exception number that will be thrown by default + * memory factory policy when memory allocation fails. + * + * @see pj_NO_MEMORY_EXCEPTION() + */ +PJ_DECL_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +/** + * Get #PJ_NO_MEMORY_EXCEPTION constant. + */ +PJ_DECL(int) pj_NO_MEMORY_EXCEPTION(void); + +/** + * This global variable points to default memory pool factory policy. + * The behaviour of the default policy is: + * - block allocation and deallocation use malloc() and free(). + * - callback will raise PJ_NO_MEMORY_EXCEPTION exception. + * - access to pool factory is not serialized (i.e. not thread safe). + * + * @see pj_pool_factory_get_default_policy + */ +PJ_DECL_DATA(pj_pool_factory_policy) pj_pool_factory_default_policy; + +/** + * Get the default pool factory policy. + * + * @return the pool policy. + */ +PJ_DECL(const pj_pool_factory_policy *) pj_pool_factory_get_default_policy(void); + +/** + * This structure contains the declaration for pool factory interface. + */ +struct pj_pool_factory { + /** + * Memory pool policy. + */ + pj_pool_factory_policy policy; + + /** + * Create a new pool from the pool factory. + * + * @param factory The pool factory. + * @param name the name to be assigned to the pool. The name should + * not be longer than PJ_MAX_OBJ_NAME (32 chars), or + * otherwise it will be truncated. + * @param initial_size the size of initial memory blocks taken by the pool. + * Note that the pool will take 68+20 bytes for + * administrative area from this block. + * @param increment_size the size of each additional blocks to be allocated + * when the pool is running out of memory. If user + * requests memory which is larger than this size, then + * an error occurs. + * Note that each time a pool allocates additional block, + * it needs 20 bytes (equal to sizeof(pj_pool_block)) to + * store some administrative info. + * @param callback Cllback to be called when error occurs in the pool. + * Note that when an error occurs during pool creation, + * the callback itself is not called. Instead, NULL + * will be returned. + * + * @return the memory pool, or NULL. + */ + pj_pool_t *(*create_pool)(pj_pool_factory *factory, const char *name, pj_size_t initial_size, + pj_size_t increment_size, pj_pool_callback *callback); + + /** + * Release the pool to the pool factory. + * + * @param factory The pool factory. + * @param pool The pool to be released. + */ + void (*release_pool)(pj_pool_factory *factory, pj_pool_t *pool); + + /** + * Dump pool status to log. + * + * @param factory The pool factory. + */ + void (*dump_status)(pj_pool_factory *factory, pj_bool_t detail); + + /** + * This is optional callback to be called by allocation policy when + * it allocates a new memory block. The factory may use this callback + * for example to keep track of the total number of memory blocks + * currently allocated by applications. + * + * @param factory The pool factory. + * @param size Size requested by application. + * + * @return MUST return PJ_TRUE, otherwise the block + * allocation is cancelled. + */ + pj_bool_t (*on_block_alloc)(pj_pool_factory *factory, pj_size_t size); + + /** + * This is optional callback to be called by allocation policy when + * it frees memory block. The factory may use this callback + * for example to keep track of the total number of memory blocks + * currently allocated by applications. + * + * @param factory The pool factory. + * @param size Size freed. + */ + void (*on_block_free)(pj_pool_factory *factory, pj_size_t size); +}; + +/** + * This function is intended to be used by pool factory implementors. + * @param factory Pool factory. + * @param name Pool name. + * @param initial_size Initial size. + * @param increment_size Increment size. + * @param callback Callback. + * @return The pool object, or NULL. + */ +PJ_DECL(pj_pool_t *) +pj_pool_create_int(pj_pool_factory *factory, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback); + +/** + * This function is intended to be used by pool factory implementors. + * @param pool The pool. + * @param name Pool name. + * @param increment_size Increment size. + * @param callback Callback function. + */ +PJ_DECL(void) pj_pool_init_int(pj_pool_t *pool, const char *name, pj_size_t increment_size, pj_pool_callback *callback); + +/** + * This function is intended to be used by pool factory implementors. + * @param pool The memory pool. + */ +PJ_DECL(void) pj_pool_destroy_int(pj_pool_t *pool); + +/** + * Dump pool factory state. + * @param pf The pool factory. + * @param detail Detail state required. + */ +PJ_INLINE(void) pj_pool_factory_dump(pj_pool_factory *pf, pj_bool_t detail) +{ + (*pf->dump_status)(pf, detail); +} + +/** + * @} // PJ_POOL_FACTORY + */ + +/* **************************************************************************/ + +/** + * @defgroup PJ_CACHING_POOL Caching Pool Factory + * @ingroup PJ_POOL_GROUP + * @brief + * Caching pool is one sample implementation of pool factory where the + * factory can reuse memory to create a pool. Application defines what the + * maximum memory the factory can hold, and when a pool is released the + * factory decides whether to destroy the pool or to keep it for future use. + * If the total amount of memory in the internal cache is still within the + * limit, the factory will keep the pool in the internal cache, otherwise the + * pool will be destroyed, thus releasing the memory back to the system. + * + * @{ + */ + +/** + * Number of unique sizes, to be used as index to the free list. + * Each pool in the free list is organized by it's size. + */ +#define PJ_CACHING_POOL_ARRAY_SIZE 16 + +/** + * Declaration for caching pool. Application doesn't normally need to + * care about the contents of this struct, it is only provided here because + * application need to define an instance of this struct (we can not allocate + * the struct from a pool since there is no pool factory yet!). + */ +struct pj_caching_pool { + /** Pool factory interface, must be declared first. */ + pj_pool_factory factory; + + /** Current factory's capacity, i.e. number of bytes that are allocated + * and available for application in this factory. The factory's + * capacity represents the size of all pools kept by this factory + * in it's free list, which will be returned to application when it + * requests to create a new pool. + */ + pj_size_t capacity; + + /** Maximum size that can be held by this factory. Once the capacity + * has exceeded @a max_capacity, further #pj_pool_release() will + * flush the pool. If the capacity is still below the @a max_capacity, + * #pj_pool_release() will save the pool to the factory's free list. + */ + pj_size_t max_capacity; + + /** + * Number of pools currently held by applications. This number gets + * incremented everytime #pj_pool_create() is called, and gets + * decremented when #pj_pool_release() is called. + */ + pj_size_t used_count; + + /** + * Total size of memory currently used by application. + */ + pj_size_t used_size; + + /** + * The maximum size of memory used by application throughout the life + * of the caching pool. + */ + pj_size_t peak_used_size; + + /** + * Lists of pools in the cache, indexed by pool size. + */ + pj_list free_list[PJ_CACHING_POOL_ARRAY_SIZE]; + + /** + * List of pools currently allocated by applications. + */ + pj_list used_list; + + /** + * Internal pool. + */ + char pool_buf[256 * (sizeof(size_t) / 4)]; + + /** + * Mutex. + */ + pj_lock_t *lock; +}; + +/** + * Initialize caching pool. + * + * @param ch_pool The caching pool factory to be initialized. + * @param policy Pool factory policy. + * @param max_capacity The total capacity to be retained in the cache. When + * the pool is returned to the cache, it will be kept in + * recycling list if the total capacity of pools in this + * list plus the capacity of the pool is still below this + * value. + */ +PJ_DECL(void) +pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity); + +/** + * Destroy caching pool, and release all the pools in the recycling list. + * + * @param ch_pool The caching pool. + */ +PJ_DECL(void) pj_caching_pool_destroy(pj_caching_pool *ch_pool); + +/** + * @} // PJ_CACHING_POOL + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#include "pool_i.h" +#endif + +PJ_END_DECL + +#endif /* __PJ_POOL_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h new file mode 100755 index 000000000..7ddbc654d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_POOL_ALT_H__ +#define __PJ_POOL_ALT_H__ + +#define __PJ_POOL_H__ + +PJ_BEGIN_DECL + +/** + * The type for function to receive callback from the pool when it is unable + * to allocate memory. The elegant way to handle this condition is to throw + * exception, and this is what is expected by most of this library + * components. + */ +typedef void pj_pool_callback(pj_pool_t *pool, pj_size_t size); + +struct pj_pool_mem { + struct pj_pool_mem *next; + + /* data follows immediately */ +}; + +struct pj_pool_t { + struct pj_pool_mem *first_mem; + pj_pool_factory *factory; + char obj_name[32]; + pj_size_t used_size; + pj_pool_callback *cb; +}; + +#define PJ_POOL_SIZE (sizeof(struct pj_pool_t)) + +/** + * This constant denotes the exception number that will be thrown by default + * memory factory policy when memory allocation fails. + */ +PJ_DECL_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +/** + * Get #PJ_NO_MEMORY_EXCEPTION constant. + */ +PJ_DECL(int) pj_NO_MEMORY_EXCEPTION(void); + +/* + * Declare all pool API as macro that calls the implementation + * function. + */ +#define pj_pool_create(fc, nm, init, inc, cb) pj_pool_create_imp(__FILE__, __LINE__, fc, nm, init, inc, cb) + +#define pj_pool_release(pool) pj_pool_release_imp(pool) +#define pj_pool_safe_release(pool) pj_pool_safe_release_imp(pool) +#define pj_pool_secure_release(pool) pj_pool_secure_release_imp(pool) +#define pj_pool_getobjname(pool) pj_pool_getobjname_imp(pool) +#define pj_pool_reset(pool) pj_pool_reset_imp(pool) +#define pj_pool_get_capacity(pool) pj_pool_get_capacity_imp(pool) +#define pj_pool_get_used_size(pool) pj_pool_get_used_size_imp(pool) +#define pj_pool_alloc(pool, sz) pj_pool_alloc_imp(__FILE__, __LINE__, pool, sz) + +#define pj_pool_calloc(pool, cnt, elem) pj_pool_calloc_imp(__FILE__, __LINE__, pool, cnt, elem) + +#define pj_pool_zalloc(pool, sz) pj_pool_zalloc_imp(__FILE__, __LINE__, pool, sz) + +/* + * Declare prototypes for pool implementation API. + */ + +/* Create pool */ +PJ_DECL(pj_pool_t *) +pj_pool_create_imp(const char *file, int line, void *factory, const char *name, pj_size_t initial_size, + pj_size_t increment_size, pj_pool_callback *callback); + +/* Release pool */ +PJ_DECL(void) pj_pool_release_imp(pj_pool_t *pool); + +/* Safe release pool */ +PJ_DECL(void) pj_pool_safe_release_imp(pj_pool_t **pool); + +/* Secure release pool */ +PJ_DECL(void) pj_pool_secure_release_imp(pj_pool_t **pool); + +/* Get pool name */ +PJ_DECL(const char *) pj_pool_getobjname_imp(pj_pool_t *pool); + +/* Reset pool */ +PJ_DECL(void) pj_pool_reset_imp(pj_pool_t *pool); + +/* Get capacity */ +PJ_DECL(pj_size_t) pj_pool_get_capacity_imp(pj_pool_t *pool); + +/* Get total used size */ +PJ_DECL(pj_size_t) pj_pool_get_used_size_imp(pj_pool_t *pool); + +/* Allocate memory from the pool */ +PJ_DECL(void *) pj_pool_alloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz); + +/* Allocate memory from the pool and zero the memory */ +PJ_DECL(void *) pj_pool_calloc_imp(const char *file, int line, pj_pool_t *pool, unsigned cnt, unsigned elemsz); + +/* Allocate memory from the pool and zero the memory */ +PJ_DECL(void *) pj_pool_zalloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz); + +#define PJ_POOL_ZALLOC_T(pool, type) ((type *)pj_pool_zalloc(pool, sizeof(type))) +#define PJ_POOL_ALLOC_T(pool, type) ((type *)pj_pool_alloc(pool, sizeof(type))) +#ifndef PJ_POOL_ALIGNMENT +#define PJ_POOL_ALIGNMENT 4 +#endif + +/** + * This structure declares pool factory interface. + */ +typedef struct pj_pool_factory_policy { + /** + * Allocate memory block (for use by pool). This function is called + * by memory pool to allocate memory block. + * + * @param factory Pool factory. + * @param size The size of memory block to allocate. + * + * @return Memory block. + */ + void *(*block_alloc)(pj_pool_factory *factory, pj_size_t size); + + /** + * Free memory block. + * + * @param factory Pool factory. + * @param mem Memory block previously allocated by block_alloc(). + * @param size The size of memory block. + */ + void (*block_free)(pj_pool_factory *factory, void *mem, pj_size_t size); + + /** + * Default callback to be called when memory allocation fails. + */ + pj_pool_callback *callback; + + /** + * Option flags. + */ + unsigned flags; + +} pj_pool_factory_policy; + +struct pj_pool_factory { + pj_pool_factory_policy policy; + int dummy; +}; + +struct pj_caching_pool { + pj_pool_factory factory; + + /* just to make it compilable */ + unsigned used_count; + unsigned used_size; + unsigned peak_used_size; +}; + +/* just to make it compilable */ +typedef struct pj_pool_block { + int dummy; +} pj_pool_block; + +#define pj_caching_pool_init(cp, pol, mac) +#define pj_caching_pool_destroy(cp) +#define pj_pool_factory_dump(pf, detail) + +PJ_END_DECL + +#endif /* __PJ_POOL_ALT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h new file mode 100755 index 000000000..bee1b0a21 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __POOL_STACK_H__ +#define __POOL_STACK_H__ + +#include + +/** + * @defgroup PJ_POOL_BUFFER Stack/Buffer Based Memory Pool Allocator + * @ingroup PJ_POOL_GROUP + * @brief Stack/buffer based pool. + * + * This section describes an implementation of memory pool which uses + * memory allocated from the stack. Application creates this pool + * by specifying a buffer (which can be allocated from static memory or + * stack variable), and then use normal pool API to access/use the pool. + * + * If the buffer specified during pool creation is a buffer located in the + * stack, the pool will be invalidated (or implicitly destroyed) when the + * execution leaves the enclosing block containing the buffer. Note + * that application must make sure that any objects allocated from this + * pool (such as mutexes) have been destroyed before the pool gets + * invalidated. + * + * Sample usage: + * + * \code + #include + + static void test() + { + char buffer[500]; + pj_pool_t *pool; + void *p; + + pool = pj_pool_create_on_buf("thepool", buffer, sizeof(buffer)); + + // Use the pool as usual + p = pj_pool_alloc(pool, ...); + ... + + // No need to release the pool + } + + int main() + { + pj_init(); + test(); + return 0; + } + + \endcode + * + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Create the pool using the specified buffer as the pool's memory. + * Subsequent allocations made from the pool will use the memory from + * this buffer. + * + * If the buffer specified in the parameter is a buffer located in the + * stack, the pool will be invalid (or implicitly destroyed) when the + * execution leaves the enclosing block containing the buffer. Note + * that application must make sure that any objects allocated from this + * pool (such as mutexes) have been destroyed before the pool gets + * invalidated. + * + * @param name Optional pool name. + * @param buf Buffer to be used by the pool. + * @param size The size of the buffer. + * + * @return The memory pool instance. + */ +PJ_DECL(pj_pool_t *) pj_pool_create_on_buf(const char *name, void *buf, pj_size_t size); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __POOL_STACK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h new file mode 100755 index 000000000..a235dad06 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +PJ_IDEF(pj_size_t) pj_pool_get_capacity(pj_pool_t *pool) +{ + return pool->capacity; +} + +PJ_IDEF(pj_size_t) pj_pool_get_used_size(pj_pool_t *pool) +{ + pj_pool_block *b = pool->block_list.next; + pj_size_t used_size = sizeof(pj_pool_t); + while (b != &pool->block_list) { + used_size += (b->cur - b->buf) + sizeof(pj_pool_block); + b = b->next; + } + return used_size; +} + +PJ_IDEF(void *) pj_pool_alloc_from_block(pj_pool_block *block, pj_size_t size) +{ + /* The operation below is valid for size==0. + * When size==0, the function will return the pointer to the pool + * memory address, but no memory will be allocated. + */ + if (size & (PJ_POOL_ALIGNMENT - 1)) { + size = (size + PJ_POOL_ALIGNMENT) & ~(PJ_POOL_ALIGNMENT - 1); + } + if ((pj_size_t)(block->end - block->cur) >= size) { + void *ptr = block->cur; + block->cur += size; + return ptr; + } + return NULL; +} + +PJ_IDEF(void *) pj_pool_alloc(pj_pool_t *pool, pj_size_t size) +{ + void *ptr = pj_pool_alloc_from_block(pool->block_list.next, size); + if (!ptr) + ptr = pj_pool_allocate_find(pool, size); + return ptr; +} + +PJ_IDEF(void *) pj_pool_calloc(pj_pool_t *pool, pj_size_t count, pj_size_t size) +{ + void *buf = pj_pool_alloc(pool, size * count); + if (buf) + pj_bzero(buf, size * count); + return buf; +} + +PJ_IDEF(const char *) pj_pool_getobjname(const pj_pool_t *pool) +{ + return pool->obj_name; +} + +PJ_IDEF(pj_pool_t *) +pj_pool_create(pj_pool_factory *f, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback) +{ + return (*f->create_pool)(f, name, initial_size, increment_size, callback); +} + +PJ_IDEF(void) pj_pool_release(pj_pool_t *pool) +{ +#if PJ_POOL_RELEASE_WIPE_DATA + pj_pool_block *b; + + b = pool->block_list.next; + while (b != &pool->block_list) { + volatile unsigned char *p = b->buf; + while (p < b->end) + *p++ = 0; + b = b->next; + } +#endif + + if (pool->factory->release_pool) + (*pool->factory->release_pool)(pool->factory, pool); +} + +PJ_IDEF(void) pj_pool_safe_release(pj_pool_t **ppool) +{ + pj_pool_t *pool = *ppool; + *ppool = NULL; + if (pool) + pj_pool_release(pool); +} + +PJ_IDEF(void) pj_pool_secure_release(pj_pool_t **ppool) +{ + pj_pool_block *b; + pj_pool_t *pool = *ppool; + *ppool = NULL; + + if (!pool) + return; + + b = pool->block_list.next; + while (b != &pool->block_list) { + volatile unsigned char *p = b->buf; + while (p < b->end) + *p++ = 0; + b = b->next; + } + + pj_pool_release(pool); +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/rand.h b/src/tuya_p2p/pjproject/pjlib/include/pj/rand.h new file mode 100755 index 000000000..3baeb22c8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/rand.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_RAND_H__ +#define __PJ_RAND_H__ + +/** + * @file rand.h + * @brief Random Number Generator. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_RAND Random Number Generator + * @ingroup PJ_MISC + * @{ + * This module contains functions for generating random numbers. + * This abstraction is needed not only because not all platforms have + * \a rand() and \a srand(), but also on some platforms \a rand() + * only has 16-bit randomness, which is not good enough. + */ + +/** + * Put in seed to random number generator. + * + * @param seed Seed value. + */ +PJ_DECL(void) pj_srand(unsigned int seed); + +/** + * Generate random integer with 32bit randomness. + * + * @return a random integer. + */ +PJ_DECL(int) pj_rand(void); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_RAND_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h b/src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h new file mode 100755 index 000000000..fb137fd73 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_RBTREE_H__ +#define __PJ_RBTREE_H__ + +/** + * @file rbtree.h + * @brief Red/Black Tree + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_RBTREE Red/Black Balanced Tree + * @ingroup PJ_DS + * @brief + * Red/Black tree is the variant of balanced tree, where the search, insert, + * and delete operation is \b guaranteed to take at most \a O( lg(n) ). + * @{ + */ +/** + * Color type for Red-Black tree. + */ +typedef enum pj_rbcolor_t { PJ_RBCOLOR_BLACK, PJ_RBCOLOR_RED } pj_rbcolor_t; + +/** + * The type of the node of the R/B Tree. + */ +typedef struct pj_rbtree_node { + /** Pointers to the node's parent. */ + struct pj_rbtree_node *parent; + + /** Pointers to the node's left sibling. */ + struct pj_rbtree_node *left; + + /** Pointers to the node's right sibling. */ + struct pj_rbtree_node *right; + + /** Key associated with the node. */ + const void *key; + + /** User data associated with the node. */ + void *user_data; + + /** The R/B Tree node color. */ + pj_rbcolor_t color; + +} pj_rbtree_node; + +/** + * The type of function use to compare key value of tree node. + * @return + * 0 if the keys are equal + * <0 if key1 is lower than key2 + * >0 if key1 is greater than key2. + */ +typedef int pj_rbtree_comp(const void *key1, const void *key2); + +/** + * Declaration of a red-black tree. All elements in the tree must have UNIQUE + * key. + * A red black tree always maintains the balance of the tree, so that the + * tree height will not be greater than lg(N). Insert, search, and delete + * operation will take lg(N) on the worst case. But for insert and delete, + * there is additional time needed to maintain the balance of the tree. + */ +typedef struct pj_rbtree { + pj_rbtree_node null_node; /**< Constant to indicate NULL node. */ + pj_rbtree_node *null; /**< Constant to indicate NULL node. */ + pj_rbtree_node *root; /**< Root tree node. */ + unsigned size; /**< Number of elements in the tree. */ + pj_rbtree_comp *comp; /**< Key comparison function. */ +} pj_rbtree; + +/** + * Guidance on how much memory required for each of the node. + */ +#define PJ_RBTREE_NODE_SIZE (sizeof(pj_rbtree_node)) + +/** + * Guidance on memory required for the tree. + */ +#define PJ_RBTREE_SIZE (sizeof(pj_rbtree)) + +/** + * Initialize the tree. + * @param tree the tree to be initialized. + * @param comp key comparison function to be used for this tree. + */ +PJ_DECL(void) pj_rbtree_init(pj_rbtree *tree, pj_rbtree_comp *comp); + +/** + * Get the first element in the tree. + * The first element always has the least value for the key, according to + * the comparison function. + * @param tree the tree. + * @return the tree node, or NULL if the tree has no element. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_first(pj_rbtree *tree); + +/** + * Get the last element in the tree. + * The last element always has the greatest key value, according to the + * comparison function defined for the tree. + * @param tree the tree. + * @return the tree node, or NULL if the tree has no element. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_last(pj_rbtree *tree); + +/** + * Get the successive element for the specified node. + * The successive element is an element with greater key value. + * @param tree the tree. + * @param node the node. + * @return the successive node, or NULL if the node has no successor. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_next(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * The the previous node for the specified node. + * The previous node is an element with less key value. + * @param tree the tree. + * @param node the node. + * @return the previous node, or NULL if the node has no previous node. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_prev(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Insert a new node. + * The node will be inserted at sorted location. The key of the node must + * be UNIQUE, i.e. it hasn't existed in the tree. + * @param tree the tree. + * @param node the node to be inserted. + * @return zero on success, or -1 if the key already exist. + */ +PJ_DECL(int) pj_rbtree_insert(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Find a node which has the specified key. + * @param tree the tree. + * @param key the key to search. + * @return the tree node with the specified key, or NULL if the key can not + * be found. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_find(pj_rbtree *tree, const void *key); + +/** + * Erase a node from the tree. + * @param tree the tree. + * @param node the node to be erased. + * @return the tree node itself. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_erase(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Get the maximum tree height from the specified node. + * @param tree the tree. + * @param node the node, or NULL to get the root of the tree. + * @return the maximum height, which should be at most lg(N) + */ +PJ_DECL(unsigned) pj_rbtree_max_height(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Get the minumum tree height from the specified node. + * @param tree the tree. + * @param node the node, or NULL to get the root of the tree. + * @return the height + */ +PJ_DECL(unsigned) pj_rbtree_min_height(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_RBTREE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/sock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/sock.h new file mode 100755 index 000000000..740cf3809 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/sock.h @@ -0,0 +1,1466 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SOCK_H__ +#define __PJ_SOCK_H__ + +/** + * @file sock.h + * @brief Socket Abstraction. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SOCK Socket Abstraction + * @ingroup PJ_IO + * @{ + * + * The PJLIB socket abstraction layer is a thin and very portable abstraction + * for socket API. It provides API similar to BSD socket API. The abstraction + * is needed because BSD socket API is not always available on all platforms, + * therefore it wouldn't be possible to create a trully portable network + * programs unless we provide such abstraction. + * + * Applications can use this API directly in their application, just + * as they would when using traditional BSD socket API, provided they + * call #pj_init() first. + * + * \section pj_sock_examples_sec Examples + * + * For some examples on how to use the socket API, please see: + * + * - \ref page_pjlib_sock_test + * - \ref page_pjlib_select_test + * - \ref page_pjlib_sock_perf_test + */ + +/** + * Supported address families. + * APPLICATION MUST USE THESE VALUES INSTEAD OF NORMAL AF_*, BECAUSE + * THE LIBRARY WILL DO TRANSLATION TO THE NATIVE VALUE. + */ + +/** Address family is unspecified. @see pj_AF_UNSPEC() */ +extern const pj_uint16_t PJ_AF_UNSPEC; + +/** Unix domain socket. @see pj_AF_UNIX() */ +extern const pj_uint16_t PJ_AF_UNIX; + +/** POSIX name for AF_UNIX */ +#define PJ_AF_LOCAL PJ_AF_UNIX; + +/** Internet IP protocol. @see pj_AF_INET() */ +extern const pj_uint16_t PJ_AF_INET; + +/** IP version 6. @see pj_AF_INET6() */ +extern const pj_uint16_t PJ_AF_INET6; + +/** Packet family. @see pj_AF_PACKET() */ +extern const pj_uint16_t PJ_AF_PACKET; + +/** IRDA sockets. @see pj_AF_IRDA() */ +extern const pj_uint16_t PJ_AF_IRDA; + +/* + * Accessor functions for various address family constants. These + * functions are provided because Symbian doesn't allow exporting + * global variables from a DLL. + */ + +#if defined(PJ_DLL) +/** Get #PJ_AF_UNSPEC value */ +PJ_DECL(pj_uint16_t) pj_AF_UNSPEC(void); +/** Get #PJ_AF_UNIX value. */ +PJ_DECL(pj_uint16_t) pj_AF_UNIX(void); +/** Get #PJ_AF_INET value. */ +PJ_DECL(pj_uint16_t) pj_AF_INET(void); +/** Get #PJ_AF_INET6 value. */ +PJ_DECL(pj_uint16_t) pj_AF_INET6(void); +/** Get #PJ_AF_PACKET value. */ +PJ_DECL(pj_uint16_t) pj_AF_PACKET(void); +/** Get #PJ_AF_IRDA value. */ +PJ_DECL(pj_uint16_t) pj_AF_IRDA(void); +#else +/* When pjlib is not built as DLL, these accessor functions are + * simply a macro to get their constants + */ +/** Get #PJ_AF_UNSPEC value */ +#define pj_AF_UNSPEC() PJ_AF_UNSPEC +/** Get #PJ_AF_UNIX value. */ +#define pj_AF_UNIX() PJ_AF_UNIX +/** Get #PJ_AF_INET value. */ +#define pj_AF_INET() PJ_AF_INET +/** Get #PJ_AF_INET6 value. */ +#define pj_AF_INET6() PJ_AF_INET6 +/** Get #PJ_AF_PACKET value. */ +#define pj_AF_PACKET() PJ_AF_PACKET +/** Get #PJ_AF_IRDA value. */ +#define pj_AF_IRDA() PJ_AF_IRDA +#endif + +/** + * Supported types of sockets. + * APPLICATION MUST USE THESE VALUES INSTEAD OF NORMAL SOCK_*, BECAUSE + * THE LIBRARY WILL TRANSLATE THE VALUE TO THE NATIVE VALUE. + */ + +/** Sequenced, reliable, connection-based byte streams. + * @see pj_SOCK_STREAM() */ +extern const pj_uint16_t PJ_SOCK_STREAM; + +/** Connectionless, unreliable datagrams of fixed maximum lengths. + * @see pj_SOCK_DGRAM() */ +extern const pj_uint16_t PJ_SOCK_DGRAM; + +/** Raw protocol interface. @see pj_SOCK_RAW() */ +extern const pj_uint16_t PJ_SOCK_RAW; + +/** Reliably-delivered messages. @see pj_SOCK_RDM() */ +extern const pj_uint16_t PJ_SOCK_RDM; + +/* + * Accessor functions for various constants. These functions are provided + * because Symbian doesn't allow exporting global variables from a DLL. + */ + +#if defined(PJ_DLL) +/** Get #PJ_SOCK_STREAM constant */ +PJ_DECL(int) pj_SOCK_STREAM(void); +/** Get #PJ_SOCK_DGRAM constant */ +PJ_DECL(int) pj_SOCK_DGRAM(void); +/** Get #PJ_SOCK_RAW constant */ +PJ_DECL(int) pj_SOCK_RAW(void); +/** Get #PJ_SOCK_RDM constant */ +PJ_DECL(int) pj_SOCK_RDM(void); +#else +/** Get #PJ_SOCK_STREAM constant */ +#define pj_SOCK_STREAM() PJ_SOCK_STREAM +/** Get #PJ_SOCK_DGRAM constant */ +#define pj_SOCK_DGRAM() PJ_SOCK_DGRAM +/** Get #PJ_SOCK_RAW constant */ +#define pj_SOCK_RAW() PJ_SOCK_RAW +/** Get #PJ_SOCK_RDM constant */ +#define pj_SOCK_RDM() PJ_SOCK_RDM +#endif + +/** + * Socket level specified in #pj_sock_setsockopt() or #pj_sock_getsockopt(). + * APPLICATION MUST USE THESE VALUES INSTEAD OF NORMAL SOL_*, BECAUSE + * THE LIBRARY WILL TRANSLATE THE VALUE TO THE NATIVE VALUE. + */ +/** Socket level. @see pj_SOL_SOCKET() */ +extern const pj_uint16_t PJ_SOL_SOCKET; +/** IP level. @see pj_SOL_IP() */ +extern const pj_uint16_t PJ_SOL_IP; +/** TCP level. @see pj_SOL_TCP() */ +extern const pj_uint16_t PJ_SOL_TCP; +/** UDP level. @see pj_SOL_UDP() */ +extern const pj_uint16_t PJ_SOL_UDP; +/** IP version 6. @see pj_SOL_IPV6() */ +extern const pj_uint16_t PJ_SOL_IPV6; + +/* + * Accessor functions for various constants. These functions are provided + * because Symbian doesn't allow exporting global variables from a DLL. + */ + +#if defined(PJ_DLL) +/** Get #PJ_SOL_SOCKET constant */ +PJ_DECL(pj_uint16_t) pj_SOL_SOCKET(void); +/** Get #PJ_SOL_IP constant */ +PJ_DECL(pj_uint16_t) pj_SOL_IP(void); +/** Get #PJ_SOL_TCP constant */ +PJ_DECL(pj_uint16_t) pj_SOL_TCP(void); +/** Get #PJ_SOL_UDP constant */ +PJ_DECL(pj_uint16_t) pj_SOL_UDP(void); +/** Get #PJ_SOL_IPV6 constant */ +PJ_DECL(pj_uint16_t) pj_SOL_IPV6(void); +#else +/** Get #PJ_SOL_SOCKET constant */ +#define pj_SOL_SOCKET() PJ_SOL_SOCKET +/** Get #PJ_SOL_IP constant */ +#define pj_SOL_IP() PJ_SOL_IP +/** Get #PJ_SOL_TCP constant */ +#define pj_SOL_TCP() PJ_SOL_TCP +/** Get #PJ_SOL_UDP constant */ +#define pj_SOL_UDP() PJ_SOL_UDP +/** Get #PJ_SOL_IPV6 constant */ +#define pj_SOL_IPV6() PJ_SOL_IPV6 +#endif + +/* IP_TOS + * + * Note: + * TOS CURRENTLY DOES NOT WORK IN Windows 2000 and above! + * See http://support.microsoft.com/kb/248611 + */ +/** IP_TOS optname in setsockopt(). @see pj_IP_TOS() */ +extern const pj_uint16_t PJ_IP_TOS; + +/* + * IP TOS related constats. + * + * Note: + * TOS CURRENTLY DOES NOT WORK IN Windows 2000 and above! + * See http://support.microsoft.com/kb/248611 + */ +/** Minimize delays. @see pj_IPTOS_LOWDELAY() */ +extern const pj_uint16_t PJ_IPTOS_LOWDELAY; + +/** Optimize throughput. @see pj_IPTOS_THROUGHPUT() */ +extern const pj_uint16_t PJ_IPTOS_THROUGHPUT; + +/** Optimize for reliability. @see pj_IPTOS_RELIABILITY() */ +extern const pj_uint16_t PJ_IPTOS_RELIABILITY; + +/** "filler data" where slow transmission does't matter. + * @see pj_IPTOS_MINCOST() */ +extern const pj_uint16_t PJ_IPTOS_MINCOST; + +#if defined(PJ_DLL) +/** Get #PJ_IP_TOS constant */ +PJ_DECL(int) pj_IP_TOS(void); + +/** Get #PJ_IPTOS_LOWDELAY constant */ +PJ_DECL(int) pj_IPTOS_LOWDELAY(void); + +/** Get #PJ_IPTOS_THROUGHPUT constant */ +PJ_DECL(int) pj_IPTOS_THROUGHPUT(void); + +/** Get #PJ_IPTOS_RELIABILITY constant */ +PJ_DECL(int) pj_IPTOS_RELIABILITY(void); + +/** Get #PJ_IPTOS_MINCOST constant */ +PJ_DECL(int) pj_IPTOS_MINCOST(void); +#else +/** Get #PJ_IP_TOS constant */ +#define pj_IP_TOS() PJ_IP_TOS + +/** Get #PJ_IPTOS_LOWDELAY constant */ +#define pj_IPTOS_LOWDELAY() PJ_IP_TOS_LOWDELAY + +/** Get #PJ_IPTOS_THROUGHPUT constant */ +#define pj_IPTOS_THROUGHPUT() PJ_IP_TOS_THROUGHPUT + +/** Get #PJ_IPTOS_RELIABILITY constant */ +#define pj_IPTOS_RELIABILITY() PJ_IP_TOS_RELIABILITY + +/** Get #PJ_IPTOS_MINCOST constant */ +#define pj_IPTOS_MINCOST() PJ_IP_TOS_MINCOST +#endif + +/** IPV6_TCLASS optname in setsockopt(). @see pj_IPV6_TCLASS() */ +extern const pj_uint16_t PJ_IPV6_TCLASS; + +#if defined(PJ_DLL) +/** Get #PJ_IPV6_TCLASS constant */ +PJ_DECL(int) pj_IPV6_TCLASS(void); +#else +/** Get #PJ_IPV6_TCLASS constant */ +#define pj_IPV6_TCLASS() PJ_IPV6_TCLASS +#endif + +/** + * Values to be specified as \c optname when calling #pj_sock_setsockopt() + * or #pj_sock_getsockopt(). + */ + +/** Socket type. @see pj_SO_TYPE() */ +extern const pj_uint16_t PJ_SO_TYPE; + +/** Buffer size for receive. @see pj_SO_RCVBUF() */ +extern const pj_uint16_t PJ_SO_RCVBUF; + +/** Buffer size for send. @see pj_SO_SNDBUF() */ +extern const pj_uint16_t PJ_SO_SNDBUF; + +/** Disables the Nagle algorithm for send coalescing. @see pj_TCP_NODELAY */ +extern const pj_uint16_t PJ_TCP_NODELAY; + +/** Allows the socket to be bound to an address that is already in use. + * @see pj_SO_REUSEADDR */ +extern const pj_uint16_t PJ_SO_REUSEADDR; + +/** Do not generate SIGPIPE. @see pj_SO_NOSIGPIPE */ +extern const pj_uint16_t PJ_SO_NOSIGPIPE; + +/** Set the protocol-defined priority for all packets to be sent on socket. + */ +extern const pj_uint16_t PJ_SO_PRIORITY; + +/** IP multicast interface. @see pj_IP_MULTICAST_IF() */ +extern const pj_uint16_t PJ_IP_MULTICAST_IF; + +/** IP multicast ttl. @see pj_IP_MULTICAST_TTL() */ +extern const pj_uint16_t PJ_IP_MULTICAST_TTL; + +/** IP multicast loopback. @see pj_IP_MULTICAST_LOOP() */ +extern const pj_uint16_t PJ_IP_MULTICAST_LOOP; + +/** Add an IP group membership. @see pj_IP_ADD_MEMBERSHIP() */ +extern const pj_uint16_t PJ_IP_ADD_MEMBERSHIP; + +/** Drop an IP group membership. @see pj_IP_DROP_MEMBERSHIP() */ +extern const pj_uint16_t PJ_IP_DROP_MEMBERSHIP; + +#if defined(PJ_DLL) +/** Get #PJ_SO_TYPE constant */ +PJ_DECL(pj_uint16_t) pj_SO_TYPE(void); + +/** Get #PJ_SO_RCVBUF constant */ +PJ_DECL(pj_uint16_t) pj_SO_RCVBUF(void); + +/** Get #PJ_SO_SNDBUF constant */ +PJ_DECL(pj_uint16_t) pj_SO_SNDBUF(void); + +/** Get #PJ_TCP_NODELAY constant */ +PJ_DECL(pj_uint16_t) pj_TCP_NODELAY(void); + +/** Get #PJ_SO_REUSEADDR constant */ +PJ_DECL(pj_uint16_t) pj_SO_REUSEADDR(void); + +/** Get #PJ_SO_NOSIGPIPE constant */ +PJ_DECL(pj_uint16_t) pj_SO_NOSIGPIPE(void); + +/** Get #PJ_SO_PRIORITY constant */ +PJ_DECL(pj_uint16_t) pj_SO_PRIORITY(void); + +/** Get #PJ_IP_MULTICAST_IF constant */ +PJ_DECL(pj_uint16_t) pj_IP_MULTICAST_IF(void); + +/** Get #PJ_IP_MULTICAST_TTL constant */ +PJ_DECL(pj_uint16_t) pj_IP_MULTICAST_TTL(void); + +/** Get #PJ_IP_MULTICAST_LOOP constant */ +PJ_DECL(pj_uint16_t) pj_IP_MULTICAST_LOOP(void); + +/** Get #PJ_IP_ADD_MEMBERSHIP constant */ +PJ_DECL(pj_uint16_t) pj_IP_ADD_MEMBERSHIP(void); + +/** Get #PJ_IP_DROP_MEMBERSHIP constant */ +PJ_DECL(pj_uint16_t) pj_IP_DROP_MEMBERSHIP(void); +#else +/** Get #PJ_SO_TYPE constant */ +#define pj_SO_TYPE() PJ_SO_TYPE + +/** Get #PJ_SO_RCVBUF constant */ +#define pj_SO_RCVBUF() PJ_SO_RCVBUF + +/** Get #PJ_SO_SNDBUF constant */ +#define pj_SO_SNDBUF() PJ_SO_SNDBUF + +/** Get #PJ_TCP_NODELAY constant */ +#define pj_TCP_NODELAY() PJ_TCP_NODELAY + +/** Get #PJ_SO_REUSEADDR constant */ +#define pj_SO_REUSEADDR() PJ_SO_REUSEADDR + +/** Get #PJ_SO_NOSIGPIPE constant */ +#define pj_SO_NOSIGPIPE() PJ_SO_NOSIGPIPE + +/** Get #PJ_SO_PRIORITY constant */ +#define pj_SO_PRIORITY() PJ_SO_PRIORITY + +/** Get #PJ_IP_MULTICAST_IF constant */ +#define pj_IP_MULTICAST_IF() PJ_IP_MULTICAST_IF + +/** Get #PJ_IP_MULTICAST_TTL constant */ +#define pj_IP_MULTICAST_TTL() PJ_IP_MULTICAST_TTL + +/** Get #PJ_IP_MULTICAST_LOOP constant */ +#define pj_IP_MULTICAST_LOOP() PJ_IP_MULTICAST_LOOP + +/** Get #PJ_IP_ADD_MEMBERSHIP constant */ +#define pj_IP_ADD_MEMBERSHIP() PJ_IP_ADD_MEMBERSHIP + +/** Get #PJ_IP_DROP_MEMBERSHIP constant */ +#define pj_IP_DROP_MEMBERSHIP() PJ_IP_DROP_MEMBERSHIP +#endif + +/* + * Flags to be specified in #pj_sock_recv, #pj_sock_send, etc. + */ + +/** Out-of-band messages. @see pj_MSG_OOB() */ +extern const int PJ_MSG_OOB; + +/** Peek, don't remove from buffer. @see pj_MSG_PEEK() */ +extern const int PJ_MSG_PEEK; + +/** Don't route. @see pj_MSG_DONTROUTE() */ +extern const int PJ_MSG_DONTROUTE; + +#if defined(PJ_DLL) +/** Get #PJ_MSG_OOB constant */ +PJ_DECL(int) pj_MSG_OOB(void); + +/** Get #PJ_MSG_PEEK constant */ +PJ_DECL(int) pj_MSG_PEEK(void); + +/** Get #PJ_MSG_DONTROUTE constant */ +PJ_DECL(int) pj_MSG_DONTROUTE(void); +#else +/** Get #PJ_MSG_OOB constant */ +#define pj_MSG_OOB() PJ_MSG_OOB + +/** Get #PJ_MSG_PEEK constant */ +#define pj_MSG_PEEK() PJ_MSG_PEEK + +/** Get #PJ_MSG_DONTROUTE constant */ +#define pj_MSG_DONTROUTE() PJ_MSG_DONTROUTE +#endif + +/** + * Flag to be specified in #pj_sock_shutdown(). + */ +typedef enum pj_socket_sd_type { + PJ_SD_RECEIVE = 0, /**< No more receive. */ + PJ_SHUT_RD = 0, /**< Alias for SD_RECEIVE. */ + PJ_SD_SEND = 1, /**< No more sending. */ + PJ_SHUT_WR = 1, /**< Alias for SD_SEND. */ + PJ_SD_BOTH = 2, /**< No more send and receive. */ + PJ_SHUT_RDWR = 2 /**< Alias for SD_BOTH. */ +} pj_socket_sd_type; + +/** Address to accept any incoming messages. */ +#define PJ_INADDR_ANY ((pj_uint32_t)0) + +/** Address indicating an error return */ +#define PJ_INADDR_NONE ((pj_uint32_t)0xffffffff) + +/** Address to send to all hosts. */ +#define PJ_INADDR_BROADCAST ((pj_uint32_t)0xffffffff) + +/** + * Maximum length specifiable by #pj_sock_listen(). + * If the build system doesn't override this value, then the lowest + * denominator (five, in Win32 systems) will be used. + */ +#if !defined(PJ_SOMAXCONN) +#define PJ_SOMAXCONN 5 +#endif + +/** + * Constant for invalid socket returned by #pj_sock_socket() and + * #pj_sock_accept(). + */ +#define PJ_INVALID_SOCKET (-1) + +/* Undefining UNIX standard library macro such as s_addr is not + * recommended as it may cause build issues for anyone who uses + * the macro. See #2311 for more details. + */ +#if 0 +/* Must undefine s_addr because of pj_in_addr below */ +#undef s_addr + +typedef struct pj_in_addr +{ + pj_uint32_t s_addr; /**< The 32bit IP address. */ +} pj_in_addr; + +#else +/** + * This structure describes Internet address. + */ +typedef struct in_addr pj_in_addr; +#endif + +/** + * Maximum length of text representation of an IPv4 address. + */ +#define PJ_INET_ADDRSTRLEN 16 + +/** + * Maximum length of text representation of an IPv6 address. + */ +#define PJ_INET6_ADDRSTRLEN 46 + +/** + * The size of sin_zero field in pj_sockaddr_in structure. Most OSes + * use 8, but others such as the BSD TCP/IP stack in eCos uses 24. + */ +#ifndef PJ_SOCKADDR_IN_SIN_ZERO_LEN +#define PJ_SOCKADDR_IN_SIN_ZERO_LEN 8 +#endif + +/** + * This structure describes Internet socket address. + * If PJ_SOCKADDR_HAS_LEN is not zero, then sin_zero_len member is added + * to this struct. As far the application is concerned, the value of + * this member will always be zero. Internally, PJLIB may modify the value + * before calling OS socket API, and reset the value back to zero before + * returning the struct to application. + */ +struct pj_sockaddr_in { +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + pj_uint8_t sin_zero_len; /**< Just ignore this. */ + pj_uint8_t sin_family; /**< Address family. */ +#else + pj_uint16_t sin_family; /**< Address family. */ +#endif + pj_uint16_t sin_port; /**< Transport layer port number. */ + pj_in_addr sin_addr; /**< IP address. */ + char sin_zero_pad[PJ_SOCKADDR_IN_SIN_ZERO_LEN]; /**< Padding.*/ +}; + +/* Undefining C standard library macro such as s6_addr is not + * recommended as it may cause build issues for anyone who uses + * the macro. See #2311 for more details. + */ +#if 0 +#undef s6_addr + +typedef union pj_in6_addr +{ + /* This is the main entry */ + pj_uint8_t s6_addr[16]; /**< 8-bit array */ + + /* While these are used for proper alignment */ + pj_uint32_t u6_addr32[4]; + + /* Do not use this with Winsock2, as this will align pj_sockaddr_in6 + * to 64-bit boundary and Winsock2 doesn't like it! + * Update 26/04/2010: + * This is now disabled, see https://github.com/pjsip/pjproject/issues/1058 + */ +#if 0 && defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 && (!defined(PJ_WIN32) || PJ_WIN32 == 0) + pj_int64_t u6_addr64[2]; +#endif + +} pj_in6_addr; +#else +/** + * This structure describes IPv6 address. + */ +typedef struct in6_addr pj_in6_addr; +#endif + +/** Initializer value for pj_in6_addr. */ +#define PJ_IN6ADDR_ANY_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } \ + } \ + } + +/** Initializer value for pj_in6_addr. */ +#define PJ_IN6ADDR_LOOPBACK_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \ + } \ + } \ + } + +/** + * This structure describes IPv6 socket address. + * If PJ_SOCKADDR_HAS_LEN is not zero, then sin_zero_len member is added + * to this struct. As far the application is concerned, the value of + * this member will always be zero. Internally, PJLIB may modify the value + * before calling OS socket API, and reset the value back to zero before + * returning the struct to application. + */ +typedef struct pj_sockaddr_in6 { +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + pj_uint8_t sin6_zero_len; /**< Just ignore this. */ + pj_uint8_t sin6_family; /**< Address family. */ +#else + pj_uint16_t sin6_family; /**< Address family */ +#endif + pj_uint16_t sin6_port; /**< Transport layer port number. */ + pj_uint32_t sin6_flowinfo; /**< IPv6 flow information */ + pj_in6_addr sin6_addr; /**< IPv6 address. */ + pj_uint32_t sin6_scope_id; /**< Set of interfaces for a scope */ +} pj_sockaddr_in6; + +/** + * This structure describes common attributes found in transport addresses. + * If PJ_SOCKADDR_HAS_LEN is not zero, then sa_zero_len member is added + * to this struct. As far the application is concerned, the value of + * this member will always be zero. Internally, PJLIB may modify the value + * before calling OS socket API, and reset the value back to zero before + * returning the struct to application. + */ +typedef struct pj_addr_hdr { +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + pj_uint8_t sa_zero_len; + pj_uint8_t sa_family; +#else + pj_uint16_t sa_family; /**< Common data: address family. */ +#endif +} pj_addr_hdr; + +/** + * This union describes a generic socket address. + */ +typedef union pj_sockaddr { + pj_addr_hdr addr; /**< Generic transport address. */ + pj_sockaddr_in ipv4; /**< IPv4 transport address. */ + pj_sockaddr_in6 ipv6; /**< IPv6 transport address. */ +} pj_sockaddr; + +/** + * This structure provides multicast group information for IPv4 addresses. + */ +typedef struct pj_ip_mreq { + pj_in_addr imr_multiaddr; /**< IP multicast address of group. */ + pj_in_addr imr_interface; /**< local IP address of interface. */ +} pj_ip_mreq; + +/** + * Options to be set for the socket. + */ +typedef struct pj_sockopt_params { + /** The number of options to be applied. */ + unsigned cnt; + + /** Array of options to be applied. */ + struct { + /** The level at which the option is defined. */ + int level; + + /** Option name. */ + int optname; + + /** Pointer to the buffer in which the option is specified. */ + void *optval; + + /** Buffer size of the buffer pointed by optval. */ + int optlen; + } options[PJ_MAX_SOCKOPT_PARAMS]; +} pj_sockopt_params; + +/***************************************************************************** + * + * SOCKET ADDRESS MANIPULATION. + * + ***************************************************************************** + */ + +/** + * Convert 16-bit value from network byte order to host byte order. + * + * @param netshort 16-bit network value. + * @return 16-bit host value. + */ +PJ_DECL(pj_uint16_t) pj_ntohs(pj_uint16_t netshort); + +/** + * Convert 16-bit value from host byte order to network byte order. + * + * @param hostshort 16-bit host value. + * @return 16-bit network value. + */ +PJ_DECL(pj_uint16_t) pj_htons(pj_uint16_t hostshort); + +/** + * Convert 32-bit value from network byte order to host byte order. + * + * @param netlong 32-bit network value. + * @return 32-bit host value. + */ +PJ_DECL(pj_uint32_t) pj_ntohl(pj_uint32_t netlong); + +/** + * Convert 32-bit value from host byte order to network byte order. + * + * @param hostlong 32-bit host value. + * @return 32-bit network value. + */ +PJ_DECL(pj_uint32_t) pj_htonl(pj_uint32_t hostlong); + +/** + * Convert an Internet host address given in network byte order + * to string in standard numbers and dots notation. + * + * @param inaddr The host address. + * @return The string address. + */ +PJ_DECL(char *) pj_inet_ntoa(pj_in_addr inaddr); + +/** + * This function converts the Internet host address cp from the standard + * numbers-and-dots notation into binary data and stores it in the structure + * that inp points to. + * + * @param cp IP address in standard numbers-and-dots notation. + * @param inp Structure that holds the output of the conversion. + * + * @return nonzero if the address is valid, zero if not. + */ +PJ_DECL(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp); + +/** + * This function converts an address in its standard text presentation form + * into its numeric binary form. It supports both IPv4 and IPv6 address + * conversion. + * + * @param af Specify the family of the address. The PJ_AF_INET and + * PJ_AF_INET6 address families shall be supported. + * @param src Points to the string being passed in. + * @param dst Points to a buffer into which the function stores the + * numeric address; this shall be large enough to hold the + * numeric address (32 bits for PJ_AF_INET, 128 bits for + * PJ_AF_INET6). + * + * @return PJ_SUCCESS if conversion was successful. + */ +PJ_DECL(pj_status_t) pj_inet_pton(int af, const pj_str_t *src, void *dst); + +/** + * This function converts a numeric address into a text string suitable + * for presentation. It supports both IPv4 and IPv6 address + * conversion. + * @see pj_sockaddr_print() + * + * @param af Specify the family of the address. This can be PJ_AF_INET + * or PJ_AF_INET6. + * @param src Points to a buffer holding an IPv4 address if the af argument + * is PJ_AF_INET, or an IPv6 address if the af argument is + * PJ_AF_INET6; the address must be in network byte order. + * @param dst Points to a buffer where the function stores the resulting + * text string; it shall not be NULL. + * @param size Specifies the size of this buffer, which shall be large + * enough to hold the text string (PJ_INET_ADDRSTRLEN characters + * for IPv4, PJ_INET6_ADDRSTRLEN characters for IPv6). + * + * @return PJ_SUCCESS if conversion was successful. + */ +PJ_DECL(pj_status_t) pj_inet_ntop(int af, const void *src, char *dst, int size); + +/** + * Converts numeric address into its text string representation. + * @see pj_sockaddr_print() + * + * @param af Specify the family of the address. This can be PJ_AF_INET + * or PJ_AF_INET6. + * @param src Points to a buffer holding an IPv4 address if the af argument + * is PJ_AF_INET, or an IPv6 address if the af argument is + * PJ_AF_INET6; the address must be in network byte order. + * @param dst Points to a buffer where the function stores the resulting + * text string; it shall not be NULL. + * @param size Specifies the size of this buffer, which shall be large + * enough to hold the text string (PJ_INET_ADDRSTRLEN characters + * for IPv4, PJ_INET6_ADDRSTRLEN characters for IPv6). + * + * @return The address string or NULL if failed. + */ +PJ_DECL(char *) pj_inet_ntop2(int af, const void *src, char *dst, int size); + +/** + * Print socket address. + * + * @param addr The socket address. + * @param buf Text buffer. + * @param size Size of buffer. + * @param flags Bitmask combination of these value: + * - 1: port number is included. + * - 2: square bracket is included for IPv6 address. + * + * @return The address string. + */ +PJ_DECL(char *) pj_sockaddr_print(const pj_sockaddr_t *addr, char *buf, int size, unsigned flags); + +/** + * Convert address string with numbers and dots to binary IP address. + * + * @param cp The IP address in numbers and dots notation. + * @return If success, the IP address is returned in network + * byte order. If failed, PJ_INADDR_NONE will be + * returned. + * @remark + * This is an obsolete interface to #pj_inet_aton(); it is obsolete + * because -1 is a valid address (255.255.255.255), and #pj_inet_aton() + * provides a cleaner way to indicate error return. + */ +PJ_DECL(pj_in_addr) pj_inet_addr(const pj_str_t *cp); + +/** + * Convert address string with numbers and dots to binary IP address. + * + * @param cp The IP address in numbers and dots notation. + * @return If success, the IP address is returned in network + * byte order. If failed, PJ_INADDR_NONE will be + * returned. + * @remark + * This is an obsolete interface to #pj_inet_aton(); it is obsolete + * because -1 is a valid address (255.255.255.255), and #pj_inet_aton() + * provides a cleaner way to indicate error return. + */ +PJ_DECL(pj_in_addr) pj_inet_addr2(const char *cp); + +/** + * Initialize IPv4 socket address based on the address and port info. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + * + * @see pj_sockaddr_init() + * + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * dotted numbers or a hostname to be resolved. + * @param port The port number, in host byte order. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_in_init(pj_sockaddr_in *addr, const pj_str_t *cp, pj_uint16_t port); + +/** + * Initialize IP socket address based on the address and port info. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + * + * @see pj_sockaddr_in_init() + * + * @param af Internet address family. + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * dotted numbers or a hostname to be resolved. + * @param port The port number, in host byte order. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_init(int af, pj_sockaddr *addr, const pj_str_t *cp, pj_uint16_t port); + +/** + * Compare two socket addresses. + * + * @param addr1 First address. + * @param addr2 Second address. + * + * @return Zero on equal, -1 if addr1 is less than addr2, + * and +1 if addr1 is more than addr2. + */ +PJ_DECL(int) pj_sockaddr_cmp(const pj_sockaddr_t *addr1, const pj_sockaddr_t *addr2); + +/** + * Get pointer to the address part of a socket address. + * + * @param addr Socket address. + * + * @return Pointer to address part (sin_addr or sin6_addr, + * depending on address family) + */ +PJ_DECL(void *) pj_sockaddr_get_addr(const pj_sockaddr_t *addr); + +/** + * Check that a socket address contains a non-zero address part. + * + * @param addr Socket address. + * + * @return Non-zero if address is set to non-zero. + */ +PJ_DECL(pj_bool_t) pj_sockaddr_has_addr(const pj_sockaddr_t *addr); + +/** + * Get the address part length of a socket address, based on its address + * family. For PJ_AF_INET, the length will be sizeof(pj_in_addr), and + * for PJ_AF_INET6, the length will be sizeof(pj_in6_addr). + * + * @param addr Socket address. + * + * @return Length in bytes. + */ +PJ_DECL(unsigned) pj_sockaddr_get_addr_len(const pj_sockaddr_t *addr); + +/** + * Get the socket address length, based on its address + * family. For PJ_AF_INET, the length will be sizeof(pj_sockaddr_in), and + * for PJ_AF_INET6, the length will be sizeof(pj_sockaddr_in6). + * + * @param addr Socket address. + * + * @return Length in bytes. + */ +PJ_DECL(unsigned) pj_sockaddr_get_len(const pj_sockaddr_t *addr); + +/** + * Copy only the address part (sin_addr/sin6_addr) of a socket address. + * + * @param dst Destination socket address. + * @param src Source socket address. + * + * @see pj_sockaddr_cp() + */ +PJ_DECL(void) pj_sockaddr_copy_addr(pj_sockaddr *dst, const pj_sockaddr *src); +/** + * Copy socket address. This will copy the whole structure depending + * on the address family of the source socket address. + * + * @param dst Destination socket address. + * @param src Source socket address. + * + * @see pj_sockaddr_copy_addr() + */ +PJ_DECL(void) pj_sockaddr_cp(pj_sockaddr_t *dst, const pj_sockaddr_t *src); + +/** + * If the source's and desired address family matches, copy the address, + * otherwise synthesize a new address with the desired address family, + * from the source address. This can be useful to generate an IPv4-mapped + * IPv6 address. + * + * @param dst_af Desired address family. + * @param dst Destination socket address, invalid if synthesis is + * required and failed. + * @param src Source socket address. + * + * @return PJ_SUCCESS on success, or the error status + * if synthesis is required and failed. + */ +PJ_DECL(pj_status_t) pj_sockaddr_synthesize(int dst_af, pj_sockaddr_t *dst, const pj_sockaddr_t *src); + +/** + * Get the IP address of an IPv4 socket address. + * The address is returned as 32bit value in host byte order. + * + * @param addr The IP socket address. + * @return 32bit address, in host byte order. + */ +PJ_DECL(pj_in_addr) pj_sockaddr_in_get_addr(const pj_sockaddr_in *addr); + +/** + * Set the IP address of an IPv4 socket address. + * + * @param addr The IP socket address. + * @param hostaddr The host address, in host byte order. + */ +PJ_DECL(void) pj_sockaddr_in_set_addr(pj_sockaddr_in *addr, pj_uint32_t hostaddr); + +/** + * Set the IP address of an IP socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard numbers and dots notation or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address. + * + * @see pj_sockaddr_set_str_addr() + * + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * dotted numbers or a hostname to be resolved. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_in_set_str_addr(pj_sockaddr_in *addr, const pj_str_t *cp); + +/** + * Set the IP address of an IPv4 or IPv6 socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard IPv6 or IPv6 address or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address according to the address family. + * + * @param af Address family. + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * IP numbers (IPv4 or IPv6) or a hostname to be resolved. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_set_str_addr(int af, pj_sockaddr *addr, const pj_str_t *cp); + +/** + * Get the port number of a socket address, in host byte order. + * This function can be used for both IPv4 and IPv6 socket address. + * + * @param addr Socket address. + * + * @return Port number, in host byte order. + */ +PJ_DECL(pj_uint16_t) pj_sockaddr_get_port(const pj_sockaddr_t *addr); + +/** + * Get the transport layer port number of an Internet socket address. + * The port is returned in host byte order. + * + * @param addr The IP socket address. + * @return Port number, in host byte order. + */ +PJ_DECL(pj_uint16_t) pj_sockaddr_in_get_port(const pj_sockaddr_in *addr); + +/** + * Set the port number of an Internet socket address. + * + * @param addr The socket address. + * @param hostport The port number, in host byte order. + */ +PJ_DECL(pj_status_t) pj_sockaddr_set_port(pj_sockaddr *addr, pj_uint16_t hostport); + +/** + * Set the port number of an IPv4 socket address. + * + * @see pj_sockaddr_set_port() + * + * @param addr The IP socket address. + * @param hostport The port number, in host byte order. + */ +PJ_DECL(void) pj_sockaddr_in_set_port(pj_sockaddr_in *addr, pj_uint16_t hostport); + +/** + * Parse string containing IP address and optional port into socket address, + * possibly also with address family detection. This function supports both + * IPv4 and IPv6 parsing, however IPv6 parsing may only be done if IPv6 is + * enabled during compilation. + * + * This function supports parsing several formats. Sample IPv4 inputs and + * their default results:: + * - "10.0.0.1:80": address 10.0.0.1 and port 80. + * - "10.0.0.1": address 10.0.0.1 and port zero. + * - "10.0.0.1:": address 10.0.0.1 and port zero. + * - "10.0.0.1:0": address 10.0.0.1 and port zero. + * - ":80": address 0.0.0.0 and port 80. + * - ":": address 0.0.0.0 and port 0. + * - "localhost": address 127.0.0.1 and port 0. + * - "localhost:": address 127.0.0.1 and port 0. + * - "localhost:80": address 127.0.0.1 and port 80. + * + * Sample IPv6 inputs and their default results: + * - "[fec0::01]:80": address fec0::01 and port 80 + * - "[fec0::01]": address fec0::01 and port 0 + * - "[fec0::01]:": address fec0::01 and port 0 + * - "[fec0::01]:0": address fec0::01 and port 0 + * - "fec0::01": address fec0::01 and port 0 + * - "fec0::01:80": address fec0::01:80 and port 0 + * - "::": address zero (::) and port 0 + * - "[::]": address zero (::) and port 0 + * - "[::]:": address zero (::) and port 0 + * - ":::": address zero (::) and port 0 + * - "[::]:80": address zero (::) and port 0 + * - ":::80": address zero (::) and port 80 + * + * Note: when the IPv6 socket address contains port number, the IP + * part of the socket address should be enclosed with square brackets, + * otherwise the port number will be included as part of the IP address + * (see "fec0::01:80" example above). + * + * @param af Optionally specify the address family to be used. If the + * address family is to be deducted from the input, specify + * pj_AF_UNSPEC() here. Other supported values are + * #pj_AF_INET() and #pj_AF_INET6() + * @param options Additional options to assist the parsing, must be zero + * for now. + * @param str The input string to be parsed. + * @param addr Pointer to store the result. + * + * @return PJ_SUCCESS if the parsing is successful. + * + * @see pj_sockaddr_parse2() + */ +PJ_DECL(pj_status_t) pj_sockaddr_parse(int af, unsigned options, const pj_str_t *str, pj_sockaddr *addr); + +/** + * This function is similar to #pj_sockaddr_parse(), except that it will not + * convert the hostpart into IP address (thus possibly resolving the hostname + * into a #pj_sockaddr. + * + * Unlike #pj_sockaddr_parse(), this function has a limitation that if port + * number is specified in an IPv6 input string, the IP part of the IPv6 socket + * address MUST be enclosed in square brackets, otherwise the port number will + * be considered as part of the IPv6 IP address. + * + * @param af Optionally specify the address family to be used. If the + * address family is to be deducted from the input, specify + * #pj_AF_UNSPEC() here. Other supported values are + * #pj_AF_INET() and #pj_AF_INET6() + * @param options Additional options to assist the parsing, must be zero + * for now. + * @param str The input string to be parsed. + * @param hostpart Optional pointer to store the host part of the socket + * address, with any brackets removed. + * @param port Optional pointer to store the port number. If port number + * is not found, this will be set to zero upon return. + * @param raf Optional pointer to store the detected address family of + * the input address. + * + * @return PJ_SUCCESS if the parsing is successful. + * + * @see pj_sockaddr_parse() + */ +PJ_DECL(pj_status_t) +pj_sockaddr_parse2(int af, unsigned options, const pj_str_t *str, pj_str_t *hostpart, pj_uint16_t *port, int *raf); + +/***************************************************************************** + * + * HOST NAME AND ADDRESS. + * + ***************************************************************************** + */ + +/** + * Get system's host name. + * + * @return The hostname, or empty string if the hostname can not + * be identified. + */ +PJ_DECL(const pj_str_t *) pj_gethostname(void); + +/** + * Get host's IP address, which the the first IP address that is resolved + * from the hostname. + * + * @return The host's IP address, PJ_INADDR_NONE if the host + * IP address can not be identified. + */ +PJ_DECL(pj_in_addr) pj_gethostaddr(void); + +/***************************************************************************** + * + * SOCKET API. + * + ***************************************************************************** + */ + +/** + * Create new socket/endpoint for communication. + * + * @param family Specifies a communication domain; this selects the + * protocol family which will be used for communication. + * @param type The socket has the indicated type, which specifies the + * communication semantics. + * @param protocol Specifies a particular protocol to be used with the + * socket. Normally only a single protocol exists to support + * a particular socket type within a given protocol family, + * in which a case protocol can be specified as 0. + * @param sock New socket descriptor, or PJ_INVALID_SOCKET on error. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_socket(int family, int type, int protocol, pj_sock_t *sock); + +/** + * Close the socket descriptor. + * + * @param sockfd The socket descriptor. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_close(pj_sock_t sockfd); + +/** + * This function gives the socket sockfd the local address my_addr. my_addr is + * addrlen bytes long. Traditionally, this is called assigning a name to + * a socket. When a socket is created with #pj_sock_socket(), it exists in a + * name space (address family) but has no name assigned. + * + * @param sockfd The socket desriptor. + * @param my_addr The local address to bind the socket to. + * @param addrlen The length of the address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_bind(pj_sock_t sockfd, const pj_sockaddr_t *my_addr, int addrlen); + +/** + * Bind the IP socket sockfd to the given address and port. + * + * @param sockfd The socket descriptor. + * @param addr Local address to bind the socket to, in host byte order. + * @param port The local port to bind the socket to, in host byte order. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_bind_in(pj_sock_t sockfd, pj_uint32_t addr, pj_uint16_t port); + +/** + * Bind the IP socket sockfd to the given address and a random port in the + * specified range. + * + * @param sockfd The socket desriptor. + * @param addr The local address and port to bind the socket to. + * @param port_range The port range, relative the to start port number + * specified in port field in addr. Note that if the + * port is zero, this param will be ignored. + * @param max_try Maximum retries. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) +pj_sock_bind_random(pj_sock_t sockfd, const pj_sockaddr_t *addr, pj_uint16_t port_range, pj_uint16_t max_try); + +#if PJ_HAS_TCP +/** + * Listen for incoming connection. This function only applies to connection + * oriented sockets (such as PJ_SOCK_STREAM or PJ_SOCK_SEQPACKET), and it + * indicates the willingness to accept incoming connections. + * + * @param sockfd The socket descriptor. + * @param backlog Defines the maximum length the queue of pending + * connections may grow to. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_listen(pj_sock_t sockfd, int backlog); + +/** + * Accept new connection on the specified connection oriented server socket. + * + * @param serverfd The server socket. + * @param newsock New socket on success, of PJ_INVALID_SOCKET if failed. + * @param addr A pointer to sockaddr type. If the argument is not NULL, + * it will be filled by the address of connecting entity. + * @param addrlen Initially specifies the length of the address, and upon + * return will be filled with the exact address length. + * + * @return Zero on success, or the error number. + */ +PJ_DECL(pj_status_t) pj_sock_accept(pj_sock_t serverfd, pj_sock_t *newsock, pj_sockaddr_t *addr, int *addrlen); +#endif + +/** + * The file descriptor sockfd must refer to a socket. If the socket is of + * type PJ_SOCK_DGRAM then the serv_addr address is the address to which + * datagrams are sent by default, and the only address from which datagrams + * are received. If the socket is of type PJ_SOCK_STREAM or PJ_SOCK_SEQPACKET, + * this call attempts to make a connection to another socket. The + * other socket is specified by serv_addr, which is an address (of length + * addrlen) in the communications space of the socket. Each communications + * space interprets the serv_addr parameter in its own way. + * + * @param sockfd The socket descriptor. + * @param serv_addr Server address to connect to. + * @param addrlen The length of server address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_connect(pj_sock_t sockfd, const pj_sockaddr_t *serv_addr, int addrlen); + +/** + * Return the address of peer which is connected to socket sockfd. + * + * @param sockfd The socket descriptor. + * @param addr Pointer to sockaddr structure to which the address + * will be returned. + * @param namelen Initially the length of the addr. Upon return the value + * will be set to the actual length of the address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_getpeername(pj_sock_t sockfd, pj_sockaddr_t *addr, int *namelen); + +/** + * Return the current name of the specified socket. + * + * @param sockfd The socket descriptor. + * @param addr Pointer to sockaddr structure to which the address + * will be returned. + * @param namelen Initially the length of the addr. Upon return the value + * will be set to the actual length of the address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_getsockname(pj_sock_t sockfd, pj_sockaddr_t *addr, int *namelen); + +/** + * Get socket option associated with a socket. Options may exist at multiple + * protocol levels; they are always present at the uppermost socket level. + * + * @param sockfd The socket descriptor. + * @param level The level which to get the option from. + * @param optname The option name. + * @param optval Identifies the buffer which the value will be + * returned. + * @param optlen Initially contains the length of the buffer, upon + * return will be set to the actual size of the value. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) +pj_sock_getsockopt(pj_sock_t sockfd, pj_uint16_t level, pj_uint16_t optname, void *optval, int *optlen); +/** + * Manipulate the options associated with a socket. Options may exist at + * multiple protocol levels; they are always present at the uppermost socket + * level. + * + * @param sockfd The socket descriptor. + * @param level The level which to get the option from. + * @param optname The option name. + * @param optval Identifies the buffer which contain the value. + * @param optlen The length of the value. + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) +pj_sock_setsockopt(pj_sock_t sockfd, pj_uint16_t level, pj_uint16_t optname, const void *optval, int optlen); + +/** + * Set socket options associated with a socket. This method will apply all the + * options specified, and ignore any errors that might be raised. + * + * @param sockfd The socket descriptor. + * @param params The socket options. + * + * @return PJ_SUCCESS or the last error code. + */ +PJ_DECL(pj_status_t) pj_sock_setsockopt_params(pj_sock_t sockfd, const pj_sockopt_params *params); + +/** + * Helper function to set socket buffer size using #pj_sock_setsockopt() + * with capability to auto retry with lower buffer setting value until + * the highest possible value is successfully set. + * + * @param sockfd The socket descriptor. + * @param optname The option name, valid values are pj_SO_RCVBUF() + * and pj_SO_SNDBUF(). + * @param auto_retry Option whether auto retry with lower value is + * enabled. + * @param buf_size On input, specify the prefered buffer size setting, + * on output, the buffer size setting applied. + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) +pj_sock_setsockopt_sobuf(pj_sock_t sockfd, pj_uint16_t optname, pj_bool_t auto_retry, unsigned *buf_size); + +/** + * Receives data stream or message coming to the specified socket. + * + * @param sockfd The socket descriptor. + * @param buf The buffer to receive the data or message. + * @param len On input, the length of the buffer. On return, + * contains the length of data received. + * @param flags Flags (such as pj_MSG_PEEK()). + * + * @return PJ_SUCCESS or the error code. + */ +PJ_DECL(pj_status_t) pj_sock_recv(pj_sock_t sockfd, void *buf, pj_ssize_t *len, unsigned flags); + +/** + * Receives data stream or message coming to the specified socket. + * + * @param sockfd The socket descriptor. + * @param buf The buffer to receive the data or message. + * @param len On input, the length of the buffer. On return, + * contains the length of data received. + * @param flags Flags (such as pj_MSG_PEEK()). + * @param from If not NULL, it will be filled with the source + * address of the connection. + * @param fromlen Initially contains the length of from address, + * and upon return will be filled with the actual + * length of the address. + * + * @return PJ_SUCCESS or the error code. + */ +PJ_DECL(pj_status_t) +pj_sock_recvfrom(pj_sock_t sockfd, void *buf, pj_ssize_t *len, unsigned flags, pj_sockaddr_t *from, int *fromlen); + +/** + * Transmit data to the socket. + * + * @param sockfd Socket descriptor. + * @param buf Buffer containing data to be sent. + * @param len On input, the length of the data in the buffer. + * Upon return, it will be filled with the length + * of data sent. + * @param flags Flags (such as pj_MSG_DONTROUTE()). + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) pj_sock_send(pj_sock_t sockfd, const void *buf, pj_ssize_t *len, unsigned flags); + +/** + * Transmit data to the socket to the specified address. + * + * @param sockfd Socket descriptor. + * @param buf Buffer containing data to be sent. + * @param len On input, the length of the data in the buffer. + * Upon return, it will be filled with the length + * of data sent. + * @param flags Flags (such as pj_MSG_DONTROUTE()). + * @param to The address to send. + * @param tolen The length of the address in bytes. + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) +pj_sock_sendto(pj_sock_t sockfd, const void *buf, pj_ssize_t *len, unsigned flags, const pj_sockaddr_t *to, int tolen); + +#if PJ_HAS_TCP +/** + * The shutdown call causes all or part of a full-duplex connection on the + * socket associated with sockfd to be shut down. + * + * @param sockfd The socket descriptor. + * @param how If how is PJ_SHUT_RD, further receptions will be + * disallowed. If how is PJ_SHUT_WR, further transmissions + * will be disallowed. If how is PJ_SHUT_RDWR, further + * receptions andtransmissions will be disallowed. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_shutdown(pj_sock_t sockfd, int how); +#endif + +/***************************************************************************** + * + * Utilities. + * + ***************************************************************************** + */ + +/** + * Print socket address string. This method will enclose the address string + * with square bracket if it's IPv6 address. + * + * @param host_str The host address string. + * @param port The port address. + * @param buf Text buffer. + * @param size Size of buffer. + * @param flag Bitmask combination of these value: + * - 1: port number is included. + * + * @return The address string. + */ +PJ_DECL(char *) pj_addr_str_print(const pj_str_t *host_str, int port, char *buf, int size, unsigned flag); + +/** + * Create socket pair + * @param family Specifies a communication domain; this selects the + * protocol family which will be used for communication. + * On Unix, support AF_UNIX, AF_INET and AF_INET6, + * On Win32, not support AF_UNIX. + * @param type The socket has the indicated type, which specifies the + * communication semantics. + * @param protocol Specifies a particular protocol to be used with the + * socket. Normally only a single protocol exists to support + * a particular socket type within a given protocol family, + * in which a case protocol can be specified as 0. + * @param sv The new sockets vector + * + * @return Zero on success. + * + */ +PJ_DECL(pj_status_t) pj_sock_socketpair(int family, int type, int protocol, pj_sock_t sv[2]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h new file mode 100755 index 000000000..7c399407b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SOCK_QOS_H__ +#define __PJ_SOCK_QOS_H__ + +/** + * @file sock_qos.h + * @brief Socket QoS API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup socket_qos Socket Quality of Service (QoS) API: TOS, DSCP, WMM, IEEE 802.1p + * @ingroup PJ_SOCK + * @{ + + + \section intro QoS Technologies + + QoS settings are available for both Layer 2 and 3 of TCP/IP protocols: + + \subsection intro_ieee8021p Layer 2: IEEE 802.1p for Ethernet + + IEEE 802.1p tagging will mark frames sent by a host for prioritized + delivery using a 3-bit Priority field in the virtual local area network + (VLAN) header of the Ethernet frame. The VLAN header is placed inside + the Ethernet header, between the Source Address field and either the + Length field (for an IEEE 802.3 frame) or the EtherType field (for an + Ethernet II frame). + + \subsection intro_wmm Layer 2: WMM + + At the Network Interface layer for IEEE 802.11 wireless, the Wi-Fi + Alliance certification for Wi-Fi Multimedia (WMM) defines four access + categories for prioritizing network traffic. These access categories + are (in order of highest to lowest priority) voice, video, best-effort, + and background. Host support for WMM prioritization requires that both + wireless network adapters and their drivers support WMM. Wireless + access points (APs) must have WMM enabled. + + \subsection intro_dscp Layer 3: DSCP + + At the Internet layer, you can use Differentiated Services/Diffserv and + set the value of the Differentiated Services Code Point (DSCP) in the + IP header. As defined in RFC 2474, the DSCP value is the high-order 6 bits + of the IP version 4 (IPv4) TOS field and the IP version 6 (IPv6) Traffic + Class field. + + \subsection intro_other Layer 3: Other + + Other mechanisms exist (such as RSVP, IntServ) but this will not be + implemented. + + + \section availability QoS Availability + + \subsection linux Linux + + DSCP is available via IP TOS option. + + Ethernet 802.1p tagging is done by setting setsockopt(SO_PRIORITY) option + of the socket, then with the set_egress_map option of the vconfig utility + to convert this to set vlan-qos field of the packet. + + WMM is not known to be available. + + \subsection windows Windows and Windows Mobile + + (It's a mess!) + + DSCP is settable with setsockopt() on Windows 2000 or older, but Windows + would silently ignore this call on WinXP or later, unless administrator + modifies the registry. On Windows 2000, Windows XP, and Windows Server + 2003, GQoS (Generic QoS) API is the standard API, but this API may not be + supported in the future. On Vista and Windows 7, the is a new QoS2 API, + also known as Quality Windows Audio-Video Experience (qWAVE). + + IEEE 802.1p tagging is available via Traffic Control (TC) API, available + on Windows XP SP2, but this needs administrator access. For Vista and + later, it's in qWAVE. + + WMM is available for mobile platforms on Windows Mobile 6 platform and + Windows Embedded CE 6, via setsockopt(IP_DSCP_TRAFFIC_TYPE). qWAVE + supports this as well. + + \subsection symbian Symbian S60 3rd Ed + + Both DSCP and WMM is supported via RSocket::SetOpt() with will set both + Layer 2 and Layer 3 QoS settings accordingly. Internally, PJLIB sets the + DSCP field of the socket, and based on certain DSCP values mapping, + Symbian will set the WMM tag accordingly. + + \section api PJLIB's QoS API Abstraction + + Based on the above, the following API is implemented. + + Declare the following "standard" traffic types. + + \code + typedef enum pj_qos_type + { + PJ_QOS_TYPE_BEST_EFFORT, + PJ_QOS_TYPE_BACKGROUND, + PJ_QOS_TYPE_VIDEO, + PJ_QOS_TYPE_VOICE, + PJ_QOS_TYPE_CONTROL, + PJ_QOS_TYPE_SIGNALLING + } pj_qos_type; + \endcode + + The traffic classes above will determine how the Layer 2 and 3 QoS + settings will be used. The standard mapping between the classes above + to the corresponding Layer 2 and 3 settings are as follows: + + \code + ================================================================= + PJLIB Traffic Type IP DSCP WMM 802.1p + ----------------------------------------------------------------- + BEST_EFFORT 0x00 BE (Bulk Effort) 0 + BACKGROUND 0x08 BK (Bulk) 2 + VIDEO 0x28 VI (Video) 5 + VOICE 0x30 VO (Voice) 6 + CONTROL 0x38 VO (Voice) 7 + SIGNALLING 0x28 VI (Video) 5 + ================================================================= + \endcode + + There are two sets of API provided to manipulate the QoS parameters. + + \subsection portable_api Portable API + + The first set of API is: + + \code + // Set QoS parameters + PJ_DECL(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, + pj_qos_type val); + + // Get QoS parameters + PJ_DECL(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, + pj_qos_type *p_val); + \endcode + + The API will set the traffic type according to the DSCP class, for both + Layer 2 and Layer 3 QoS settings, where it's available. If any of the + layer QoS setting is not settable, the API will silently ignore it. + If both layers are not setable, the API will return error. + + The API above is the recommended use of QoS, since it is the most + portable across all platforms. + + \subsection detail_api Fine Grained Control API + + The second set of API is intended for application that wants to fine + tune the QoS parameters. + + The Layer 2 and 3 QoS parameters are stored in pj_qos_params structure: + + \code + typedef enum pj_qos_flag + { + PJ_QOS_PARAM_HAS_DSCP = 1, + PJ_QOS_PARAM_HAS_SO_PRIO = 2, + PJ_QOS_PARAM_HAS_WMM = 4 + } pj_qos_flag; + + typedef enum pj_qos_wmm_prio + { + PJ_QOS_WMM_PRIO_BULK_EFFORT, + PJ_QOS_WMM_PRIO_BULK, + PJ_QOS_WMM_PRIO_VIDEO, + PJ_QOS_WMM_PRIO_VOICE + } pj_qos_wmm_prio; + + typedef struct pj_qos_params + { + pj_uint8_t flags; // Determines which values to + // set, bitmask of pj_qos_flag + pj_uint8_t dscp_val; // The 6 bits DSCP value to set + pj_uint8_t so_prio; // SO_PRIORITY value + pj_qos_wmm_prio wmm_prio; // WMM priority value + } pj_qos_params; + \endcode + + The second set of API with more fine-grained control over the parameters + are: + + \code + // Retrieve QoS params for the specified traffic type + PJ_DECL(pj_status_t) pj_qos_get_params(pj_qos_type type, + pj_qos_params *p); + + // Set QoS parameters to the socket + PJ_DECL(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, + const pj_qos_params *p); + + // Get QoS parameters from the socket + PJ_DECL(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, + pj_qos_params *p); + \endcode + + + Important: + + The pj_sock_set/get_qos_params() APIs are not portable, and it's probably + only going to be implemented on Linux. Application should always try to + use pj_sock_set_qos_type() instead. + */ + +/** + * High level traffic classification. + */ +typedef enum pj_qos_type { + PJ_QOS_TYPE_BEST_EFFORT, /**< Best effort traffic (default value). + Any QoS function calls with specifying + this value are effectively no-op */ + PJ_QOS_TYPE_BACKGROUND, /**< Background traffic. */ + PJ_QOS_TYPE_VIDEO, /**< Video traffic. */ + PJ_QOS_TYPE_VOICE, /**< Voice traffic. */ + PJ_QOS_TYPE_CONTROL, /**< Control traffic. */ + PJ_QOS_TYPE_SIGNALLING /**< Signalling traffic. */ +} pj_qos_type; + +/** + * Bitmask flag to indicate which QoS layer setting is set in the + * \a flags field of the #pj_qos_params structure. + */ +typedef enum pj_qos_flag { + PJ_QOS_PARAM_HAS_DSCP = 1, /**< DSCP field is set. */ + PJ_QOS_PARAM_HAS_SO_PRIO = 2, /**< Socket SO_PRIORITY */ + PJ_QOS_PARAM_HAS_WMM = 4 /**< WMM field is set. */ +} pj_qos_flag; + +/** + * Standard WMM priorities. + */ +typedef enum pj_qos_wmm_prio { + PJ_QOS_WMM_PRIO_BULK_EFFORT, /**< Bulk effort priority */ + PJ_QOS_WMM_PRIO_BULK, /**< Bulk priority. */ + PJ_QOS_WMM_PRIO_VIDEO, /**< Video priority */ + PJ_QOS_WMM_PRIO_VOICE /**< Voice priority */ +} pj_qos_wmm_prio; + +/** + * QoS parameters to be set or retrieved to/from the socket. + */ +typedef struct pj_qos_params { + pj_uint8_t flags; /**< Determines which values to + set, bitmask of pj_qos_flag */ + pj_uint8_t dscp_val; /**< The 6 bits DSCP value to set */ + pj_uint8_t so_prio; /**< SO_PRIORITY value */ + pj_qos_wmm_prio wmm_prio; /**< WMM priority value */ +} pj_qos_params; + +/** + * This is the high level and portable API to enable QoS on the specified + * socket, by setting the traffic type to the specified parameter. + * + * @param sock The socket. + * @param type Traffic type to be set. + * + * @return PJ_SUCCESS if at least Layer 2 or Layer 3 setting is + * successfully set. If both Layer 2 and Layer 3 settings + * can't be set, this function will return error. + */ +PJ_DECL(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, pj_qos_type type); + +/** + * This is the high level and portable API to get the traffic type that has + * been set on the socket. On occasions where the Layer 2 or Layer 3 settings + * were modified by using low level API, this function may return approximation + * of the closest QoS type that matches the settings. + * + * @param sock The socket. + * @param p_type Pointer to receive the traffic type of the socket. + * + * @return PJ_SUCCESS if traffic type for the socket can be obtained + * or approximated.. + */ +PJ_DECL(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, pj_qos_type *p_type); + +/** + * This is a convenience function to apply QoS to the socket, and print error + * logging if the operations failed. Both QoS traffic type and the low level + * QoS parameters can be applied with this function. + * + * @param sock The socket handle. + * @param qos_type QoS traffic type. The QoS traffic type will be applied + * only if the value is not PJ_QOS_TYPE_BEST_EFFORT, + * @param qos_params Optional low-level QoS parameters. This will be + * applied only if this argument is not NULL and the + * flags inside the structure is non-zero. Upon return, + * the flags will indicate which parameters have been + * applied successfully. + * @param log_level This function will print to log at this level upon + * encountering errors. + * @param log_sender Optional sender name in the log. + * @param sock_name Optional name to help identify the socket in the log. + * + * @return PJ_SUCCESS if at least Layer 2 or Layer 3 setting is + * successfully set. If both Layer 2 and Layer 3 settings + * can't be set, this function will return error. + * + * @see pj_sock_apply_qos2() + */ +PJ_DECL(pj_status_t) +pj_sock_apply_qos(pj_sock_t sock, pj_qos_type qos_type, pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name); + +/** + * Variant of #pj_sock_apply_qos() where the \a qos_params parameter is + * const. + * + * @see pj_sock_apply_qos() + */ +PJ_DECL(pj_status_t) +pj_sock_apply_qos2(pj_sock_t sock, pj_qos_type qos_type, const pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name); + +/** + * Retrieve the standard mapping of QoS params for the specified traffic + * type. + * + * @param type The traffic type from which the QoS parameters + * are to be retrieved. + * @param p_param Pointer to receive the QoS parameters. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_qos_get_params(pj_qos_type type, pj_qos_params *p_param); + +/** + * Retrieve the traffic type that matches the specified QoS parameters. + * If no exact matching is found, this function will return an + * approximation of the closest matching traffic type for the specified + * QoS parameters. + * + * @param param Structure containing QoS parameters to map into + * "standard" traffic types. + * @param p_type Pointer to receive the traffic type. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_qos_get_type(const pj_qos_params *param, pj_qos_type *p_type); + +/** + * This is a low level API to set QoS parameters to the socket. + * + * @param sock The socket. + * @param param Structure containing QoS parameters to be applied + * to the socket. Upon return, the \a flags field + * of this structure will be set with bitmask value + * indicating which QoS settings have successfully + * been applied to the socket. + * + * @return PJ_SUCCESS if at least one field setting has been + * successfully set. If no setting can't be set, + * this function will return error. + */ +PJ_DECL(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, pj_qos_params *param); + +/** + * This is a low level API to get QoS parameters from the socket. + * + * @param sock The socket. + * @param p_param Pointer to receive the parameters. Upon returning + * successfully, the \a flags field of this structure + * will be initialized with the appropriate bitmask + * to indicate which fields have been successfully + * retrieved. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, pj_qos_params *p_param); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SOCK_QOS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h new file mode 100755 index 000000000..1ec4c2946 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SELECT_H__ +#define __PJ_SELECT_H__ + +/** + * @file sock_select.h + * @brief Socket select(). + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SOCK_SELECT Socket select() API. + * @ingroup PJ_IO + * @{ + * This module provides portable abstraction for \a select() like API. + * The abstraction is needed so that it can utilize various event + * dispatching mechanisms that are available across platforms. + * + * The API is very similar to normal \a select() usage. + * + * \section pj_sock_select_examples_sec Examples + * + * For some examples on how to use the select API, please see: + * + * - \ref page_pjlib_select_test + */ + +/** + * Portable structure declarations for pj_fd_set. + * The implementation of pj_sock_select() does not use this structure + * per-se, but instead it will use the native fd_set structure. However, + * we must make sure that the size of pj_fd_set_t can accomodate the + * native fd_set structure. + */ +typedef struct pj_fd_set_t { + pj_sock_t data[PJ_IOQUEUE_MAX_HANDLES + 4]; /**< Opaque buffer for fd_set */ +} pj_fd_set_t; + +/** + * Initialize the descriptor set pointed to by fdsetp to the null set. + * + * @param fdsetp The descriptor set. + */ +PJ_DECL(void) PJ_FD_ZERO(pj_fd_set_t *fdsetp); + +/** + * This is an internal function, application shouldn't use this. + * + * Get the number of descriptors in the set. This is defined in sock_select.c + * This function will only return the number of sockets set from PJ_FD_SET + * operation. When the set is modified by other means (such as by select()), + * the count will not be reflected here. + * + * @param fdsetp The descriptor set. + * + * @return Number of descriptors in the set. + */ +PJ_DECL(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp); + +/** + * Add the file descriptor fd to the set pointed to by fdsetp. + * If the file descriptor fd is already in this set, there shall be no effect + * on the set, nor will an error be returned. + * + * @param fd The socket descriptor. + * @param fdsetp The descriptor set. + */ +PJ_DECL(void) PJ_FD_SET(pj_sock_t fd, pj_fd_set_t *fdsetp); + +/** + * Remove the file descriptor fd from the set pointed to by fdsetp. + * If fd is not a member of this set, there shall be no effect on the set, + * nor will an error be returned. + * + * @param fd The socket descriptor. + * @param fdsetp The descriptor set. + */ +PJ_DECL(void) PJ_FD_CLR(pj_sock_t fd, pj_fd_set_t *fdsetp); + +/** + * Evaluate to non-zero if the file descriptor fd is a member of the set + * pointed to by fdsetp, and shall evaluate to zero otherwise. + * + * @param fd The socket descriptor. + * @param fdsetp The descriptor set. + * + * @return Nonzero if fd is member of the descriptor set. + */ +PJ_DECL(pj_bool_t) PJ_FD_ISSET(pj_sock_t fd, const pj_fd_set_t *fdsetp); + +/** + * This function wait for a number of file descriptors to change status. + * The behaviour is the same as select() function call which appear in + * standard BSD socket libraries. + * + * @param n On Unices, this specifies the highest-numbered + * descriptor in any of the three set, plus 1. On Windows, + * the value is ignored. + * @param readfds Optional pointer to a set of sockets to be checked for + * readability. + * @param writefds Optional pointer to a set of sockets to be checked for + * writability. + * @param exceptfds Optional pointer to a set of sockets to be checked for + * errors. + * @param timeout Maximum time for select to wait, or null for blocking + * operations. + * + * @return The total number of socket handles that are ready, or + * zero if the time limit expired, or -1 if an error occurred. + */ +PJ_DECL(int) +pj_sock_select(int n, pj_fd_set_t *readfds, pj_fd_set_t *writefds, pj_fd_set_t *exceptfds, const pj_time_val *timeout); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SELECT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h new file mode 100755 index 000000000..5725e6f82 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h @@ -0,0 +1,1384 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SSL_SOCK_H__ +#define __PJ_SSL_SOCK_H__ + +/** + * @file ssl_sock.h + * @brief Secure socket + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SSL_SOCK Secure socket I/O + * @brief Secure socket provides security on socket operation using standard + * security protocols such as SSL and TLS. + * @ingroup PJ_IO + * @{ + * + * Secure socket wraps normal socket and applies security features, i.e: + * privacy and data integrity, on the socket traffic, using standard security + * protocols such as SSL and TLS. + * + * Secure socket employs active socket operations, which is similar to (and + * described more detail) in \ref PJ_ACTIVESOCK. + */ + +/** + * This opaque structure describes the secure socket. + */ +typedef struct pj_ssl_sock_t pj_ssl_sock_t; + +/** + * Opaque declaration of endpoint certificate or credentials. This may contains + * certificate, private key, and trusted Certificate Authorities list. + */ +typedef struct pj_ssl_cert_t pj_ssl_cert_t; + +/** + * Bitwise flag for SSL certificate verification. + */ +typedef enum pj_ssl_cert_verify_flag_t { + /** + * No error in verification. + */ + PJ_SSL_CERT_ESUCCESS = 0, + + /** + * The issuer certificate cannot be found. + */ + PJ_SSL_CERT_EISSUER_NOT_FOUND = (1 << 0), + + /** + * The certificate is untrusted. + */ + PJ_SSL_CERT_EUNTRUSTED = (1 << 1), + + /** + * The certificate has expired or not yet valid. + */ + PJ_SSL_CERT_EVALIDITY_PERIOD = (1 << 2), + + /** + * One or more fields of the certificate cannot be decoded due to + * invalid format. + */ + PJ_SSL_CERT_EINVALID_FORMAT = (1 << 3), + + /** + * The certificate cannot be used for the specified purpose. + */ + PJ_SSL_CERT_EINVALID_PURPOSE = (1 << 4), + + /** + * The issuer info in the certificate does not match to the (candidate) + * issuer certificate, e.g: issuer name not match to subject name + * of (candidate) issuer certificate. + */ + PJ_SSL_CERT_EISSUER_MISMATCH = (1 << 5), + + /** + * The CRL certificate cannot be found or cannot be read properly. + */ + PJ_SSL_CERT_ECRL_FAILURE = (1 << 6), + + /** + * The certificate has been revoked. + */ + PJ_SSL_CERT_EREVOKED = (1 << 7), + + /** + * The certificate chain length is too long. + */ + PJ_SSL_CERT_ECHAIN_TOO_LONG = (1 << 8), + + /** + * The server identity does not match to any identities specified in + * the certificate, e.g: subjectAltName extension, subject common name. + * This flag will only be set by application as SSL socket does not + * perform server identity verification. + */ + PJ_SSL_CERT_EIDENTITY_NOT_MATCH = (1 << 30), + + /** + * Unknown verification error. + */ + PJ_SSL_CERT_EUNKNOWN = (1 << 31) + +} pj_ssl_cert_verify_flag_t; + +/** + * Type of SSL certificate name. + */ +typedef enum pj_ssl_cert_name_type { + PJ_SSL_CERT_NAME_UNKNOWN = 0, + PJ_SSL_CERT_NAME_RFC822, + PJ_SSL_CERT_NAME_DNS, + PJ_SSL_CERT_NAME_URI, + PJ_SSL_CERT_NAME_IP +} pj_ssl_cert_name_type; + +/** + * Describe structure of certificate info. + */ +typedef struct pj_ssl_cert_info { + + unsigned version; /**< Certificate version */ + + pj_uint8_t serial_no[20]; /**< Serial number, array of + octets, first index is + MSB */ + + struct { + pj_str_t cn; /**< Common name */ + pj_str_t info; /**< One line subject, fields + are separated by slash, e.g: + "CN=sample.org/OU=HRD" */ + } subject; /**< Subject */ + + struct { + pj_str_t cn; /**< Common name */ + pj_str_t info; /**< One line subject, fields + are separated by slash.*/ + } issuer; /**< Issuer */ + + struct { + pj_time_val start; /**< Validity start */ + pj_time_val end; /**< Validity end */ + pj_bool_t gmt; /**< Flag if validity date/time + use GMT */ + } validity; /**< Validity */ + + struct { + unsigned cnt; /**< # of entry */ + struct { + pj_ssl_cert_name_type type; + /**< Name type */ + pj_str_t name; /**< The name */ + } * entry; /**< Subject alt name entry */ + } subj_alt_name; /**< Subject alternative + name extension */ + + pj_str_t raw; /**< Raw certificate in PEM format, only + available for remote certificate. */ + + struct { + unsigned cnt; /**< # of entry */ + pj_str_t *cert_raw; + } raw_chain; + +} pj_ssl_cert_info; + +/** + * The SSL certificate buffer. + */ +typedef pj_str_t pj_ssl_cert_buffer; + +/** + * Create credential from files. TLS server application can provide multiple + * certificates (RSA, ECC, and DSA) by supplying certificate name with "_rsa" + * suffix, e.g: "pjsip_rsa.pem", the library will automatically check for + * other certificates with "_ecc" and "_dsa" suffix. + * + * @param pool The pool. + * @param CA_file The file of trusted CA list. + * @param cert_file The file of certificate. + * @param privkey_file The file of private key. + * @param privkey_pass The password of private key, if any. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_load_from_files(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *cert_file, + const pj_str_t *privkey_file, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert); + +/** + * Create credential from files. TLS server application can provide multiple + * certificates (RSA, ECC, and DSA) by supplying certificate name with "_rsa" + * suffix, e.g: "pjsip_rsa.pem", the library will automatically check for + * other certificates with "_ecc" and "_dsa" suffix. + * + * This is the same as pj_ssl_cert_load_from_files() but also + * accepts an additional param CA_path to load CA certificates from + * a directory. + * + * @param pool The pool. + * @param CA_file The file of trusted CA list. + * @param CA_path The path to a directory of trusted CA list. + * @param cert_file The file of certificate. + * @param privkey_file The file of private key. + * @param privkey_pass The password of private key, if any. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_load_from_files2(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *CA_path, + const pj_str_t *cert_file, const pj_str_t *privkey_file, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert); + +/** + * Create credential from data buffer. The certificate expected is in + * PEM format. + * + * @param pool The pool. + * @param CA_buf The buffer of trusted CA list. + * @param cert_buf The buffer of certificate. + * @param privkey_buf The buffer of private key. + * @param privkey_pass The password of private key, if any. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_load_from_buffer(pj_pool_t *pool, const pj_ssl_cert_buffer *CA_buf, const pj_ssl_cert_buffer *cert_buf, + const pj_ssl_cert_buffer *privkey_buf, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert); + +/** + * Dump SSL certificate info. + * + * @param ci The certificate info. + * @param indent String for left indentation. + * @param buf The buffer where certificate info will be printed on. + * @param buf_size The buffer size. + * + * @return The length of the dump result, or -1 when buffer size + * is not sufficient. + */ +PJ_DECL(pj_ssize_t) +pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci, const char *indent, char *buf, pj_size_t buf_size); + +/** + * Get SSL certificate verification error messages from verification status. + * + * @param verify_status The SSL certificate verification status. + * @param error_strings Array of strings to receive the verification error + * messages. + * @param count On input it specifies maximum error messages should be + * retrieved. On output it specifies the number of error + * messages retrieved. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_get_verify_status_strings(pj_uint32_t verify_status, const char *error_strings[], unsigned *count); + +/** + * Wipe out the keys in the SSL certificate. + * + * @param cert The SSL certificate. + * + */ +PJ_DECL(void) pj_ssl_cert_wipe_keys(pj_ssl_cert_t *cert); + +/** + * Cipher suites enumeration. + */ +typedef enum pj_ssl_cipher { + + /* Unsupported cipher */ + PJ_TLS_UNKNOWN_CIPHER = -1, + + /* NULL */ + PJ_TLS_NULL_WITH_NULL_NULL = 0x00000000, + + /* TLS/SSLv3 */ + PJ_TLS_RSA_WITH_NULL_MD5 = 0x00000001, + PJ_TLS_RSA_WITH_NULL_SHA = 0x00000002, + PJ_TLS_RSA_WITH_NULL_SHA256 = 0x0000003B, + PJ_TLS_RSA_WITH_RC4_128_MD5 = 0x00000004, + PJ_TLS_RSA_WITH_RC4_128_SHA = 0x00000005, + PJ_TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x0000000A, + PJ_TLS_RSA_WITH_AES_128_CBC_SHA = 0x0000002F, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA = 0x00000035, + PJ_TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x0000003C, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x0000003D, + PJ_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x0000000D, + PJ_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x00000010, + PJ_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x00000013, + PJ_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x00000016, + PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x00000030, + PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x00000031, + PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x00000032, + PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x00000033, + PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x00000036, + PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x00000037, + PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x00000038, + PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x00000039, + PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x0000003E, + PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x0000003F, + PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x00000040, + PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x00000067, + PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x00000068, + PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x00000069, + PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x0000006A, + PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x0000006B, + PJ_TLS_DH_anon_WITH_RC4_128_MD5 = 0x00000018, + PJ_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x0000001B, + PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x00000034, + PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x0000003A, + PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x0000006C, + PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x0000006D, + + /* TLS (deprecated) */ + PJ_TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x00000003, + PJ_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x00000006, + PJ_TLS_RSA_WITH_IDEA_CBC_SHA = 0x00000007, + PJ_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x00000008, + PJ_TLS_RSA_WITH_DES_CBC_SHA = 0x00000009, + PJ_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0000000B, + PJ_TLS_DH_DSS_WITH_DES_CBC_SHA = 0x0000000C, + PJ_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0000000E, + PJ_TLS_DH_RSA_WITH_DES_CBC_SHA = 0x0000000F, + PJ_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x00000011, + PJ_TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x00000012, + PJ_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x00000014, + PJ_TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x00000015, + PJ_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x00000017, + PJ_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x00000019, + PJ_TLS_DH_anon_WITH_DES_CBC_SHA = 0x0000001A, + + /* SSLv3 */ + PJ_SSL_FORTEZZA_KEA_WITH_NULL_SHA = 0x0000001C, + PJ_SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA = 0x0000001D, + PJ_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA = 0x0000001E, + + /* SSLv2 */ + PJ_SSL_CK_RC4_128_WITH_MD5 = 0x00010080, + PJ_SSL_CK_RC4_128_EXPORT40_WITH_MD5 = 0x00020080, + PJ_SSL_CK_RC2_128_CBC_WITH_MD5 = 0x00030080, + PJ_SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 = 0x00040080, + PJ_SSL_CK_IDEA_128_CBC_WITH_MD5 = 0x00050080, + PJ_SSL_CK_DES_64_CBC_WITH_MD5 = 0x00060040, + PJ_SSL_CK_DES_192_EDE3_CBC_WITH_MD5 = 0x000700C0 + +} pj_ssl_cipher; + +/** + * Get cipher list supported by SSL/TLS backend. + * + * @param ciphers The ciphers buffer to receive cipher list. + * @param cipher_num Maximum number of ciphers to be received. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[], unsigned *cipher_num); + +/** + * Check if the specified cipher is supported by SSL/TLS backend. + * + * @param cipher The cipher. + * + * @return PJ_TRUE when supported. + */ +PJ_DECL(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher); + +/** + * Get cipher name string. + * + * @param cipher The cipher. + * + * @return The cipher name or NULL if cipher is not recognized/ + * supported. + */ +PJ_DECL(const char *) pj_ssl_cipher_name(pj_ssl_cipher cipher); + +/** + * Get cipher ID from cipher name string. Note that on different backends + * (e.g. OpenSSL or Symbian implementation), cipher names may not be + * equivalent for the same cipher ID. + * + * @param cipher_name The cipher name string. + * + * @return The cipher ID or PJ_TLS_UNKNOWN_CIPHER if the cipher + * name string is not recognized/supported. + */ +PJ_DECL(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name); + +/** + * Elliptic curves enumeration. + */ +typedef enum pj_ssl_curve { + PJ_TLS_UNKNOWN_CURVE = 0, + PJ_TLS_CURVE_SECT163K1 = 1, + PJ_TLS_CURVE_SECT163R1 = 2, + PJ_TLS_CURVE_SECT163R2 = 3, + PJ_TLS_CURVE_SECT193R1 = 4, + PJ_TLS_CURVE_SECT193R2 = 5, + PJ_TLS_CURVE_SECT233K1 = 6, + PJ_TLS_CURVE_SECT233R1 = 7, + PJ_TLS_CURVE_SECT239K1 = 8, + PJ_TLS_CURVE_SECT283K1 = 9, + PJ_TLS_CURVE_SECT283R1 = 10, + PJ_TLS_CURVE_SECT409K1 = 11, + PJ_TLS_CURVE_SECT409R1 = 12, + PJ_TLS_CURVE_SECT571K1 = 13, + PJ_TLS_CURVE_SECT571R1 = 14, + PJ_TLS_CURVE_SECP160K1 = 15, + PJ_TLS_CURVE_SECP160R1 = 16, + PJ_TLS_CURVE_SECP160R2 = 17, + PJ_TLS_CURVE_SECP192K1 = 18, + PJ_TLS_CURVE_SECP192R1 = 19, + PJ_TLS_CURVE_SECP224K1 = 20, + PJ_TLS_CURVE_SECP224R1 = 21, + PJ_TLS_CURVE_SECP256K1 = 22, + PJ_TLS_CURVE_SECP256R1 = 23, + PJ_TLS_CURVE_SECP384R1 = 24, + PJ_TLS_CURVE_SECP521R1 = 25, + PJ_TLS_CURVE_BRAINPOOLP256R1 = 26, + PJ_TLS_CURVE_BRAINPOOLP384R1 = 27, + PJ_TLS_CURVE_BRAINPOOLP512R1 = 28, + PJ_TLS_CURVE_ARBITRARY_EXPLICIT_PRIME_CURVES = 0XFF01, + PJ_TLS_CURVE_ARBITRARY_EXPLICIT_CHAR2_CURVES = 0XFF02 +} pj_ssl_curve; + +/** + * Get curve list supported by SSL/TLS backend. + * + * @param curves The curves buffer to receive curve list. + * @param curve_num Maximum number of curves to be received. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_curve_get_availables(pj_ssl_curve curves[], unsigned *curve_num); + +/** + * Check if the specified curve is supported by SSL/TLS backend. + * + * @param curve The curve. + * + * @return PJ_TRUE when supported. + */ +PJ_DECL(pj_bool_t) pj_ssl_curve_is_supported(pj_ssl_curve curve); + +/** + * Get curve name string. + * + * @param curve The curve. + * + * @return The curve name or NULL if curve is not recognized/ + * supported. + */ +PJ_DECL(const char *) pj_ssl_curve_name(pj_ssl_curve curve); + +/** + * Get curve ID from curve name string. Note that on different backends + * (e.g. OpenSSL or Symbian implementation), curve names may not be + * equivalent for the same curve ID. + * + * @param curve_name The curve name string. + * + * @return The curve ID or PJ_TLS_UNKNOWN_CURVE if the curve + * name string is not recognized/supported. + */ +PJ_DECL(pj_ssl_curve) pj_ssl_curve_id(const char *curve_name); + +/** + * Entropy enumeration + */ +typedef enum pj_ssl_entropy { + PJ_SSL_ENTROPY_NONE = 0, /**< None */ + PJ_SSL_ENTROPY_EGD = 1, /**< EGD */ + PJ_SSL_ENTROPY_RANDOM = 2, /**< Random */ + PJ_SSL_ENTROPY_URANDOM = 3, /**< Urandom */ + PJ_SSL_ENTROPY_FILE = 4, /**< File */ + PJ_SSL_ENTROPY_UNKNOWN = 0x0F /**< Unknown */ +} pj_ssl_entropy_t; + +/** + * This structure contains the callbacks to be called by the secure socket. + */ +typedef struct pj_ssl_sock_cb { + /** + * This callback is called when a data arrives as the result of + * pj_ssl_sock_start_read(). + * + * @param ssock The secure socket. + * @param data The buffer containing the new data, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument may be NULL. + * @param size The length of data in the buffer. + * @param status The status of the read operation. This may contain + * non-PJ_SUCCESS for example when the TCP connection + * has been closed. In this case, the buffer may + * contain left over data from previous callback which + * the application may want to process. + * @param remainder If application wishes to leave some data in the + * buffer (common for TCP applications), it should + * move the remainder data to the front part of the + * buffer and set the remainder length here. The value + * of this parameter will be ignored for datagram + * sockets. + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_read)(pj_ssl_sock_t *ssock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); + /** + * This callback is called when a packet arrives as the result of + * pj_ssl_sock_start_recvfrom(). + * + * @param ssock The secure socket. + * @param data The buffer containing the packet, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + * @param size The length of packet in the buffer. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to zero. + * @param src_addr Source address of the packet. + * @param addr_len Length of the source address. + * @param status This contains + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_recvfrom)(pj_ssl_sock_t *ssock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + + /** + * This callback is called when data has been sent. + * + * @param ssock The secure socket. + * @param send_key Key associated with the send operation. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_sent)(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + + /** + * This callback is called when new connection arrives as the result + * of pj_ssl_sock_start_accept(). If the status of accept operation is + * needed use on_accept_complete2 instead of this callback. + * + * @param ssock The secure socket. + * @param newsock The new incoming secure socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the secure socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete)(pj_ssl_sock_t *ssock, pj_ssl_sock_t *newsock, const pj_sockaddr_t *src_addr, + int src_addr_len); + /** + * This callback is called when new connection arrives as the result + * of pj_ssl_sock_start_accept(). + * + * @param asock The active socket. + * @param newsock The new incoming socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * @param status The status of the accept operation. This may contain + * non-PJ_SUCCESS for example when the TCP listener is in + * bad state for example on iOS platform after the + * application waking up from background. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the active socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete2)(pj_ssl_sock_t *ssock, pj_ssl_sock_t *newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status); + + /** + * This callback is called when pending connect operation has been + * completed. + * + * @param ssock The secure socket. + * @param status The connection result. If connection has been + * successfully established, the status will contain + * PJ_SUCCESS. + * + * @return Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_connect_complete)(pj_ssl_sock_t *ssock, pj_status_t status); + + /** + * This callback is called when certificate verification is being done. + * Certification info can be obtained from #pj_ssl_sock_info. Currently + * it's only implemented for OpenSSL backend. + * + * @param ssock The secure socket. + * @param is_server PJ_TRUE to indicate an incoming connection. + * + * @return Return PJ_TRUE if verification is successful. + * If verification failed, then the connection will be + * dropped immediately. + * + */ + pj_bool_t (*on_verify_cb)(pj_ssl_sock_t *ssock, pj_bool_t is_server); + +} pj_ssl_sock_cb; + +/** + * Enumeration of secure socket protocol types. + * This can be combined using bitwise OR operation. + */ +typedef enum pj_ssl_sock_proto { + /** + * Default protocol of backend. + */ + PJ_SSL_SOCK_PROTO_DEFAULT = 0, + + /** + * SSLv2.0 protocol. + */ + PJ_SSL_SOCK_PROTO_SSL2 = (1 << 0), + + /** + * SSLv3.0 protocol. + */ + PJ_SSL_SOCK_PROTO_SSL3 = (1 << 1), + + /** + * TLSv1.0 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1 = (1 << 2), + + /** + * TLSv1.1 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1_1 = (1 << 3), + + /** + * TLSv1.2 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1_2 = (1 << 4), + + /** + * TLSv1.3 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1_3 = (1 << 5), + + /** + * Certain backend implementation e.g:OpenSSL, has feature to enable all + * protocol. + */ + PJ_SSL_SOCK_PROTO_SSL23 = (1 << 16) - 1, + PJ_SSL_SOCK_PROTO_ALL = PJ_SSL_SOCK_PROTO_SSL23, + + /** + * DTLSv1.0 protocol. + */ + PJ_SSL_SOCK_PROTO_DTLS1 = (1 << 16), + +} pj_ssl_sock_proto; + +/** + * Definition of secure socket info structure. + */ +typedef struct pj_ssl_sock_info { + /** + * Describes whether secure socket connection is established, i.e: TLS/SSL + * handshaking has been done successfully. + */ + pj_bool_t established; + + /** + * Describes secure socket protocol being used, see #pj_ssl_sock_proto. + * Use bitwise OR operation to combine the protocol type. + */ + pj_uint32_t proto; + + /** + * Describes cipher suite being used, this will only be set when connection + * is established. + */ + pj_ssl_cipher cipher; + + /** + * Describes local address. + */ + pj_sockaddr local_addr; + + /** + * Describes remote address. + */ + pj_sockaddr remote_addr; + + /** + * Describes active local certificate info. + */ + pj_ssl_cert_info *local_cert_info; + + /** + * Describes active remote certificate info. + */ + pj_ssl_cert_info *remote_cert_info; + + /** + * Status of peer certificate verification. + */ + pj_uint32_t verify_status; + + /** + * Last native error returned by the backend. + */ + unsigned long last_native_err; + + /** + * Group lock assigned to the ioqueue key. + */ + pj_grp_lock_t *grp_lock; + +} pj_ssl_sock_info; + +/** + * Definition of secure socket creation parameters. + */ +typedef struct pj_ssl_sock_param { + /** + * Optional group lock to be assigned to the ioqueue key. + * + * Note that when a secure socket listener is configured with a group + * lock, any new secure socket of an accepted incoming connection + * will have its own group lock created automatically by the library, + * this group lock can be queried via pj_ssl_sock_get_info() in the info + * field pj_ssl_sock_info::grp_lock. + */ + pj_grp_lock_t *grp_lock; + + /** + * Specifies socket address family, either pj_AF_INET() and pj_AF_INET6(). + * + * Default is pj_AF_INET(). + */ + int sock_af; + + /** + * Specify socket type, either pj_SOCK_DGRAM() or pj_SOCK_STREAM(). + * + * Default is pj_SOCK_STREAM(). + */ + int sock_type; + + /** + * Specify the ioqueue to use. Secure socket uses the ioqueue to perform + * active socket operations, see \ref PJ_ACTIVESOCK for more detail. + */ + pj_ioqueue_t *ioqueue; + + /** + * Specify the timer heap to use. Secure socket uses the timer to provide + * auto cancelation on asynchronous operation when it takes longer time + * than specified timeout period, e.g: security negotiation timeout. + */ + pj_timer_heap_t *timer_heap; + + /** + * Specify secure socket callbacks, see #pj_ssl_sock_cb. + */ + pj_ssl_sock_cb cb; + + /** + * Specify secure socket user data. + */ + void *user_data; + + /** + * Specify security protocol to use, see #pj_ssl_sock_proto. Use bitwise OR + * operation to combine the protocol type. + * + * Default is PJ_SSL_SOCK_PROTO_DEFAULT. + */ + pj_uint32_t proto; + + /** + * Number of concurrent asynchronous operations that is to be supported + * by the secure socket. This value only affects socket receive and + * accept operations -- the secure socket will issue one or more + * asynchronous read and accept operations based on the value of this + * field. Setting this field to more than one will allow more than one + * incoming data or incoming connections to be processed simultaneously + * on multiprocessor systems, when the ioqueue is polled by more than + * one threads. + * + * The default value is 1. + */ + unsigned async_cnt; + + /** + * The ioqueue concurrency to be forced on the socket when it is + * registered to the ioqueue. See #pj_ioqueue_set_concurrency() for more + * info about ioqueue concurrency. + * + * When this value is -1, the concurrency setting will not be forced for + * this socket, and the socket will inherit the concurrency setting of + * the ioqueue. When this value is zero, the secure socket will disable + * concurrency for the socket. When this value is +1, the secure socket + * will enable concurrency for the socket. + * + * The default value is -1. + */ + int concurrency; + + /** + * If this option is specified, the secure socket will make sure that + * asynchronous send operation with stream oriented socket will only + * call the callback after all data has been sent. This means that the + * secure socket will automatically resend the remaining data until + * all data has been sent. + * + * Please note that when this option is specified, it is possible that + * error is reported after partial data has been sent. Also setting + * this will disable the ioqueue concurrency for the socket. + * + * Default value is 1. + */ + pj_bool_t whole_data; + + /** + * Specify buffer size for sending operation. Buffering sending data + * is used for allowing application to perform multiple outstanding + * send operations. Whenever application specifies this setting too + * small, sending operation may return PJ_ENOMEM. + * + * Default value is 8192 bytes. + */ + pj_size_t send_buffer_size; + + /** + * Specify buffer size for receiving encrypted (and perhaps compressed) + * data on underlying socket. This setting is unused on Symbian, since + * SSL/TLS Symbian backend, CSecureSocket, can use application buffer + * directly. + * + * Default value is 1500. + */ + pj_size_t read_buffer_size; + + /** + * Number of ciphers contained in the specified cipher preference. + * If this is set to zero, then the cipher list used will be determined + * by the backend default (for OpenSSL backend, setting + * PJ_SSL_SOCK_OSSL_CIPHERS will be used). + */ + unsigned ciphers_num; + + /** + * Ciphers and order preference. If empty, then default cipher list and + * its default order of the backend will be used. + */ + pj_ssl_cipher *ciphers; + + /** + * Number of curves contained in the specified curve preference. + * If this is set to zero, then default curve list of the backend + * will be used. + * + * Default: 0 (zero). + */ + unsigned curves_num; + + /** + * Curves and order preference. The #pj_ssl_curve_get_availables() + * can be used to check the available curves supported by backend. + */ + pj_ssl_curve *curves; + + /** + * The supported signature algorithms. Set the sigalgs string + * using this form: + * "+:+" + * Digests are: "RSA", "DSA" or "ECDSA" + * Algorithms are: "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512" + * Example: "ECDSA+SHA256:RSA+SHA256" + */ + pj_str_t sigalgs; + + /** + * Reseed random number generator. + * For type #PJ_SSL_ENTROPY_FILE, parameter \a entropy_path + * must be set to a file. + * For type #PJ_SSL_ENTROPY_EGD, parameter \a entropy_path + * must be set to a socket. + * + * Default value is PJ_SSL_ENTROPY_NONE. + */ + pj_ssl_entropy_t entropy_type; + + /** + * When using a file/socket for entropy #PJ_SSL_ENTROPY_EGD or + * #PJ_SSL_ENTROPY_FILE, \a entropy_path must contain the path + * to entropy socket/file. + * + * Default value is an empty string. + */ + pj_str_t entropy_path; + + /** + * Security negotiation timeout. If this is set to zero (both sec and + * msec), the negotiation doesn't have a timeout. + * + * Default value is zero. + */ + pj_time_val timeout; + + /** + * Specify whether endpoint should verify peer certificate. + * + * Default value is PJ_FALSE. + */ + pj_bool_t verify_peer; + + /** + * When secure socket is acting as server (handles incoming connection), + * it will require the client to provide certificate. + * + * Default value is PJ_FALSE. + */ + pj_bool_t require_client_cert; + + /** + * Server name indication. When secure socket is acting as client + * (perform outgoing connection) and the server may host multiple + * 'virtual' servers at a single underlying network address, setting + * this will allow client to tell the server a name of the server + * it is contacting. This must be set to hostname and literal IP addresses + * are not allowed. + * + * Default value is zero/not-set. + */ + pj_str_t server_name; + + /** + * Specify if SO_REUSEADDR should be used for listening socket. This + * option will only be used with accept() operation. + * + * Default is PJ_FALSE. + */ + pj_bool_t reuse_addr; + + /** + * QoS traffic type to be set on this transport. When application wants + * to apply QoS tagging to the transport, it's preferable to set this + * field rather than \a qos_param fields since this is more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a lower + * level operation than setting the \a qos_type field and may not be + * supported on all platforms. + * + * By default all settings in this structure are disabled. + */ + pj_qos_params qos_params; + + /** + * Specify if the transport should ignore any errors when setting the QoS + * traffic type/parameters. + * + * Default: PJ_TRUE + */ + pj_bool_t qos_ignore_error; + + /** + * Specify options to be set on the transport. + * + * By default there is no options. + * + */ + pj_sockopt_params sockopt_params; + + /** + * Specify if the transport should ignore any errors when setting the + * sockopt parameters. + * + * Default: PJ_TRUE + * + */ + pj_bool_t sockopt_ignore_error; + +} pj_ssl_sock_param; + +/** + * The parameter for pj_ssl_sock_start_connect2(). + */ +typedef struct pj_ssl_start_connect_param { + /** + * The pool to allocate some internal data for the operation. + */ + pj_pool_t *pool; + + /** + * Local address. + */ + const pj_sockaddr_t *localaddr; + + /** + * Port range for socket binding, relative to the start port number + * specified in \a localaddr. This is only applicable when the start port + * number is non zero. + */ + pj_uint16_t local_port_range; + + /** + * Remote address. + */ + const pj_sockaddr_t *remaddr; + + /** + * Length of buffer containing above addresses. + */ + int addr_len; + +} pj_ssl_start_connect_param; + +/** + * Initialize the secure socket parameters for its creation with + * the default values. + * + * @param param The parameter to be initialized. + */ +PJ_DECL(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param); + +/** + * Duplicate pj_ssl_sock_param. + * + * @param pool Pool to allocate memory. + * @param dst Destination parameter. + * @param src Source parameter. + */ +PJ_DECL(void) pj_ssl_sock_param_copy(pj_pool_t *pool, pj_ssl_sock_param *dst, const pj_ssl_sock_param *src); + +/** + * Create secure socket instance. + * + * @param pool The pool for allocating secure socket instance. + * @param param The secure socket parameter, see #pj_ssl_sock_param. + * @param p_ssock Pointer to secure socket instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool, const pj_ssl_sock_param *param, pj_ssl_sock_t **p_ssock); + +/** + * Set secure socket certificate or credentials. Credentials may include + * certificate, private key and trusted Certification Authorities list. + * Normally, server socket must provide certificate (and private key). + * Socket client may also need to provide certificate in case requested + * by the server. + * + * @param ssock The secure socket instance. + * @param pool The pool. + * @param cert The endpoint certificate/credentials, see + * #pj_ssl_cert_t. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_ssl_cert_t *cert); + +/** + * Close and destroy the secure socket. + * + * @param ssock The secure socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock); + +/** + * Associate arbitrary data with the secure socket. Application may + * inspect this data in the callbacks and associate it with higher + * level processing. + * + * @param ssock The secure socket. + * @param user_data The user data to be associated with the secure + * socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock, void *user_data); + +/** + * Retrieve the user data previously associated with this secure + * socket. + * + * @param ssock The secure socket. + * + * @return The user data. + */ +PJ_DECL(void *) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock); + +/** + * Retrieve the local address and port used by specified secure socket. + * + * @param ssock The secure socket. + * @param info The info buffer to be set, see #pj_ssl_sock_info. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_get_info(pj_ssl_sock_t *ssock, pj_ssl_sock_info *info); + +/** + * Starts read operation on this secure socket. This function will create + * \a async_cnt number of buffers (the \a async_cnt parameter was given + * in \a pj_ssl_sock_create() function) where each buffer is \a buff_size + * long. The buffers are allocated from the specified \a pool. Once the + * buffers are created, it then issues \a async_cnt number of asynchronous + * \a recv() operations to the socket and returns back to caller. Incoming + * data on the socket will be reported back to application via the + * \a on_data_read() callback. + * + * Application only needs to call this function once to initiate read + * operations. Further read operations will be done automatically by the + * secure socket when \a on_data_read() callback returns non-zero. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_read(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_ssl_sock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_read2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], pj_uint32_t flags); + +/** + * Same as pj_ssl_sock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_recvfrom(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_recvfrom2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); + +/** + * Send data using the socket. + * + * @param ssock The secure socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately or + * PJ_ENOMEM when sending buffer could not handle all + * queued data, see \a send_buffer_size. The callback + * \a on_data_sent() will be called when data is actually + * sent. Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags); + +/** + * Send datagram using the socket. + * + * @param ssock The secure socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * @param addr The destination address. + * @param addr_len Length of buffer containing destination address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_sendto(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len); + +/** + * Starts asynchronous socket accept() operations on this secure socket. + * This function will issue \a async_cnt number of asynchronous \a accept() + * operations to the socket and returns back to caller. Incoming + * connection on the socket will be reported back to application via the + * \a on_accept_complete() callback. + * + * Application only needs to call this function once to initiate accept() + * operations. Further accept() operations will be done automatically by + * the secure socket when \a on_accept_complete() callback returns non-zero. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * @param local_addr Local address to bind on. + * @param addr_len Length of buffer containing local address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_accept(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *local_addr, int addr_len); + +/** + * Same as #pj_ssl_sock_start_accept(), but application can provide + * a secure socket parameter, which will be used to create a new secure + * socket reported in \a on_accept_complete() callback when there is + * an incoming connection. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * @param local_addr Local address to bind on. + * @param addr_len Length of buffer containing local address. + * @param newsock_param Secure socket parameter for new accepted sockets. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_accept2(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *local_addr, int addr_len, + const pj_ssl_sock_param *newsock_param); + +/** + * Starts asynchronous socket connect() operation and SSL/TLS handshaking + * for this socket. Once the connection is done (either successfully or not), + * the \a on_connect_complete() callback will be called. + * + * @param ssock The secure socket. + * @param pool The pool to allocate some internal data for the + * operation. + * @param localaddr Local address. + * @param remaddr Remote address. + * @param addr_len Length of buffer containing above addresses. + * + * @return PJ_SUCCESS if connection can be established immediately + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, + const pj_sockaddr_t *remaddr, int addr_len); + +/** + * Same as #pj_ssl_sock_start_connect(), but application can provide a + * \a port_range parameter, which will be used to bind the socket to + * random port. + * + * @param ssock The secure socket. + * + * @param connect_param The parameter, refer to \a pj_ssl_start_connect_param. + * + * @return PJ_SUCCESS if connection can be established immediately + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_connect2(pj_ssl_sock_t *ssock, pj_ssl_start_connect_param *connect_param); + +/** + * Starts SSL/TLS renegotiation over an already established SSL connection + * for this socket. This operation is performed transparently, no callback + * will be called once the renegotiation completed successfully. However, + * when the renegotiation fails, the connection will be closed and callback + * \a on_data_read() will be invoked with non-PJ_SUCCESS status code. + * + * @param ssock The secure socket. + * + * @return PJ_SUCCESS if renegotiation is completed immediately, + * or PJ_EPENDING if renegotiation has been started and + * waiting for completion, or the appropriate error code + * on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SSL_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/string.h b/src/tuya_p2p/pjproject/pjlib/include/pj/string.h new file mode 100755 index 000000000..4c3af555f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/string.h @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_STRING_H__ +#define __PJ_STRING_H__ + +/** + * @file string.h + * @brief PJLIB String Operations. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_PSTR String Operations + * @ingroup PJ_DS + * @{ + * This module provides string manipulation API. + * + * \section pj_pstr_not_null_sec PJLIB String is NOT Null Terminated! + * + * That is the first information that developers need to know. Instead + * of using normal C string, strings in PJLIB are represented as + * pj_str_t structure below: + * + *
+ *   typedef struct pj_str_t
+ *   {
+ *       char      *ptr;
+ *       pj_ssize_t  slen;
+ *   } pj_str_t;
+ * 
+ * + * There are some advantages of using this approach: + * - the string can point to arbitrary location in memory even + * if the string in that location is not null terminated. This is + * most usefull for text parsing, where the parsed text can just + * point to the original text in the input. If we use C string, + * then we will have to copy the text portion from the input + * to a string variable. + * - because the length of the string is known, string copy operation + * can be made more efficient. + * + * Most of APIs in PJLIB that expect or return string will represent + * the string as pj_str_t instead of normal C string. + * + * \section pj_pstr_examples_sec Examples + * + * For some examples, please see: + * - @ref page_pjlib_string_test + */ + +/** + * Check if a string is truncated and if yes, put a suffix of ".." + * to indicate the truncation. + * This macro is used to check the result of pj_ansi_snprintf(). + * + * @param ret The return value of pj_ansi_snprintf(). + * @param str The string. + * @param len The length of the string buffer. + */ +#define PJ_CHECK_TRUNC_STR(ret, str, len) \ + if ((ret) >= (len) || (ret) < 0) \ + pj_ansi_strcpy((str) + (len)-3, "..") + +/** + * Create string initializer from a normal C string. + * + * @param str Null terminated string to be stored. + * + * @return pj_str_t. + */ +PJ_IDECL(pj_str_t) pj_str(char *str); + +/** + * Create constant string from normal C string. + * + * @param str The string to be initialized. + * @param s Null terminated string. + * + * @return pj_str_t. + */ +PJ_INLINE(const pj_str_t *) pj_cstr(pj_str_t *str, const char *s) +{ + str->ptr = (char *)s; + str->slen = s ? (pj_ssize_t)strlen(s) : 0; + return str; +} + +/** + * Set the pointer and length to the specified value. + * + * @param str the string. + * @param ptr pointer to set. + * @param length length to set. + * + * @return the string. + */ +PJ_INLINE(pj_str_t *) pj_strset(pj_str_t *str, char *ptr, pj_size_t length) +{ + str->ptr = ptr; + str->slen = (pj_ssize_t)length; + return str; +} + +/** + * Set the pointer and length of the string to the source string, which + * must be NULL terminated. + * + * @param str the string. + * @param src pointer to set. + * + * @return the string. + */ +PJ_INLINE(pj_str_t *) pj_strset2(pj_str_t *str, char *src) +{ + str->ptr = src; + str->slen = src ? (pj_ssize_t)strlen(src) : 0; + return str; +} + +/** + * Set the pointer and the length of the string. + * + * @param str The target string. + * @param begin The start of the string. + * @param end The end of the string. + * + * @return the target string. + */ +PJ_INLINE(pj_str_t *) pj_strset3(pj_str_t *str, char *begin, char *end) +{ + str->ptr = begin; + str->slen = (pj_ssize_t)(end - begin); + return str; +} + +/** + * Assign string. + * + * @param dst The target string. + * @param src The source string. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strassign(pj_str_t *dst, pj_str_t *src); + +/** + * Copy string contents. + * + * @param dst The target string. + * @param src The source string. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strcpy(pj_str_t *dst, const pj_str_t *src); + +/** + * Copy string contents. + * + * @param dst The target string. + * @param src The source string. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strcpy2(pj_str_t *dst, const char *src); + +/** + * Copy source string to destination up to the specified max length. + * + * @param dst The target string. + * @param src The source string. + * @param max Maximum characters to copy. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strncpy(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max); + +/** + * Copy source string to destination up to the specified max length, + * and NULL terminate the destination. If source string length is + * greater than or equal to max, then max-1 will be copied. + * + * @param dst The target string. + * @param src The source string. + * @param max Maximum characters to copy. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strncpy_with_null(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max); + +/** + * Duplicate string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return the string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src); + +/** + * Duplicate string and NULL terminate the destination string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return The string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup_with_null(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src); + +/** + * Duplicate string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return the string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup2(pj_pool_t *pool, pj_str_t *dst, const char *src); + +/** + * Duplicate string and NULL terminate the destination string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return The string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup2_with_null(pj_pool_t *pool, pj_str_t *dst, const char *src); + +/** + * Duplicate string. + * + * @param pool The pool. + * @param src The string to duplicate. + * + * @return the string result. + */ +PJ_IDECL(pj_str_t) pj_strdup3(pj_pool_t *pool, const char *src); + +/** + * Return the length of the string. + * + * @param str The string. + * + * @return the length of the string. + */ +PJ_INLINE(pj_size_t) pj_strlen(const pj_str_t *str) +{ + return str->slen; +} + +/** + * Return the pointer to the string data. + * + * @param str The string. + * + * @return the pointer to the string buffer. + */ +PJ_INLINE(const char *) pj_strbuf(const pj_str_t *str) +{ + return str->ptr; +} + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strcmp(const pj_str_t *str1, const pj_str_t *str2); + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strcmp2(const pj_str_t *str1, const char *str2); + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strncmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len); + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strncmp2(const pj_str_t *str1, const char *str2, pj_size_t len); + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is equal to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_stricmp(const pj_str_t *str1, const pj_str_t *str2); + +/** + * Perform lowercase comparison to the strings which consists of only + * alnum characters. More over, it will only return non-zero if both + * strings are not equal, not the usual negative or positive value. + * + * If non-alnum inputs are given, then the function may mistakenly + * treat two strings as equal. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The length to compare. + * + * @return + * - 0 if str1 is equal to str2 + * - (-1) if not equal. + */ +#if defined(PJ_HAS_STRICMP_ALNUM) && PJ_HAS_STRICMP_ALNUM != 0 +PJ_IDECL(int) strnicmp_alnum(const char *str1, const char *str2, int len); +#else +#define strnicmp_alnum pj_ansi_strnicmp +#endif + +/** + * Perform lowercase comparison to the strings which consists of only + * alnum characters. More over, it will only return non-zero if both + * strings are not equal, not the usual negative or positive value. + * + * If non-alnum inputs are given, then the function may mistakenly + * treat two strings as equal. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - 0 if str1 is equal to str2 + * - (-1) if not equal. + */ +#if defined(PJ_HAS_STRICMP_ALNUM) && PJ_HAS_STRICMP_ALNUM != 0 +PJ_IDECL(int) pj_stricmp_alnum(const pj_str_t *str1, const pj_str_t *str2); +#else +#define pj_stricmp_alnum pj_stricmp +#endif + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_stricmp2(const pj_str_t *str1, const char *str2); + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strnicmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len); + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strnicmp2(const pj_str_t *str1, const char *str2, pj_size_t len); + +/** + * Concatenate strings. + * + * @param dst The destination string. + * @param src The source string. + */ +PJ_IDECL(void) pj_strcat(pj_str_t *dst, const pj_str_t *src); + +/** + * Concatenate strings. + * + * @param dst The destination string. + * @param src The source string. + */ +PJ_IDECL(void) pj_strcat2(pj_str_t *dst, const char *src); + +/** + * Finds a character in a string. + * + * @param str The string. + * @param chr The character to find. + * + * @return the pointer to first character found, or NULL. + */ +PJ_INLINE(char *) pj_strchr(const pj_str_t *str, int chr) +{ + if (str->slen == 0) + return NULL; + return (char *)memchr((char *)str->ptr, chr, str->slen); +} + +/** + * Find the first index of character, in a string, that does not belong to a + * set of characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that doesn't belong to + * set_char. If str starts with a character not in set_char, return 0. + */ +PJ_DECL(pj_ssize_t) pj_strspn(const pj_str_t *str, const pj_str_t *set_char); + +/** + * Find the first index of character, in a string, that does not belong to a + * set of characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that doesn't belong to + * set_char. If str starts with a character not in set_char, return 0. + */ +PJ_DECL(pj_ssize_t) pj_strspn2(const pj_str_t *str, const char *set_char); + +/** + * Find the first index of character, in a string, that belong to a set of + * characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that belong to + * set_char. If no match is found, return the length of str. + */ +PJ_DECL(pj_ssize_t) pj_strcspn(const pj_str_t *str, const pj_str_t *set_char); + +/** + * Find the first index of character, in a string, that belong to a set of + * characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that belong to + * set_char. If no match is found, return the length of str. + */ +PJ_DECL(pj_ssize_t) pj_strcspn2(const pj_str_t *str, const char *set_char); + +/** + * Find tokens from a string using the delimiter. + * + * @param str The string. + * @param delim The string containing the delimiter. It might contain + * multiple character treated as unique set. If same character + * was found on the set, it will be skipped. + * @param tok The string containing the token. + * @param start_idx The search will start from this index. + * + * @return the index of token from the str, or the length of the str + * if the token is not found. + */ +PJ_DECL(pj_ssize_t) pj_strtok(const pj_str_t *str, const pj_str_t *delim, pj_str_t *tok, pj_size_t start_idx); + +/** + * Find tokens from a string using the delimiter. + * + * @param str The string. + * @param delim The string containing the delimiter. It might contain + * multiple character treated as unique set. If same character + * was found on the set, it will be skipped. + * @param tok The string containing the token. + * @param start_idx The search will start from this index. + * + * @return the index of token from the str, or the length of the str + * if the token is not found. + */ +PJ_DECL(pj_ssize_t) pj_strtok2(const pj_str_t *str, const char *delim, pj_str_t *tok, pj_size_t start_idx); + +/** + * Find the occurence of a substring substr in string str. + * + * @param str The string to search. + * @param substr The string to search fo. + * + * @return the pointer to the position of substr in str, or NULL. Note + * that if str is not NULL terminated, the returned pointer + * is pointing to non-NULL terminated string. + */ +PJ_DECL(char *) pj_strstr(const pj_str_t *str, const pj_str_t *substr); + +/** + * Performs substring lookup like pj_strstr() but ignores the case of + * both strings. + * + * @param str The string to search. + * @param substr The string to search fo. + * + * @return the pointer to the position of substr in str, or NULL. Note + * that if str is not NULL terminated, the returned pointer + * is pointing to non-NULL terminated string. + */ +PJ_DECL(char *) pj_stristr(const pj_str_t *str, const pj_str_t *substr); + +/** + * Remove (trim) leading whitespaces from the string. + * + * @param str The string. + * + * @return the string. + */ +PJ_DECL(pj_str_t *) pj_strltrim(pj_str_t *str); + +/** + * Remove (trim) the trailing whitespaces from the string. + * + * @param str The string. + * + * @return the string. + */ +PJ_DECL(pj_str_t *) pj_strrtrim(pj_str_t *str); + +/** + * Remove (trim) leading and trailing whitespaces from the string. + * + * @param str The string. + * + * @return the string. + */ +PJ_IDECL(pj_str_t *) pj_strtrim(pj_str_t *str); + +/** + * Initialize the buffer with some random string. Note that the + * generated string is not NULL terminated. + * + * @param str the string to store the result. + * @param length the length of the random string to generate. + * + * @return the string. + */ +PJ_DECL(char *) pj_create_random_string(char *str, pj_size_t length); + +/** + * Convert string to signed integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str the string. + * + * @return the integer. + */ +PJ_DECL(long) pj_strtol(const pj_str_t *str); + +/** + * Convert string to signed long integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str the string. + * @param value Pointer to a long to receive the value. + * + * @return PJ_SUCCESS if successful. Otherwise: + * PJ_ETOOSMALL if the value was an impossibly long negative number. + * In this case *value will be set to LONG_MIN. + * \n + * PJ_ETOOBIG if the value was an impossibly long positive number. + * In this case, *value will be set to LONG_MAX. + * \n + * PJ_EINVAL if the input string was NULL, the value pointer was NULL + * or the input string could not be parsed at all such as starting with + * a character other than a '+', '-' or not in the '0' - '9' range. + * In this case, *value will be left untouched. + */ +PJ_DECL(pj_status_t) pj_strtol2(const pj_str_t *str, long *value); + +/** + * Convert string to unsigned integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str the string. + * + * @return the unsigned integer. + */ +PJ_DECL(unsigned long) pj_strtoul(const pj_str_t *str); + +/** + * Convert strings to an unsigned long-integer value. + * This function stops reading the string input either when the number + * of characters has exceeded the length of the input or it has read + * the first character it cannot recognize as part of a number, that is + * a character greater than or equal to base. + * + * @param str The input string. + * @param endptr Optional pointer to receive the remainder/unparsed + * portion of the input. + * @param base Number base to use. + * + * @return the unsigned integer number. + */ +PJ_DECL(unsigned long) pj_strtoul2(const pj_str_t *str, pj_str_t *endptr, unsigned base); + +/** + * Convert string to unsigned long integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str The input string. + * @param value Pointer to an unsigned long to receive the value. + * @param base Number base to use. + * + * @return PJ_SUCCESS if successful. Otherwise: + * PJ_ETOOBIG if the value was an impossibly long positive number. + * In this case, *value will be set to ULONG_MAX. + * \n + * PJ_EINVAL if the input string was NULL, the value pointer was NULL + * or the input string could not be parsed at all such as starting + * with a character outside the base character range. In this case, + * *value will be left untouched. + */ +PJ_DECL(pj_status_t) pj_strtoul3(const pj_str_t *str, unsigned long *value, unsigned base); + +/** + * Convert string to float. + * + * @param str the string. + * + * @return the value. + */ +PJ_DECL(float) pj_strtof(const pj_str_t *str); + +/** + * Utility to convert unsigned integer to string. Note that the + * string will be NULL terminated. + * + * @param val the unsigned integer value. + * @param buf the buffer + * + * @return the number of characters written + */ +PJ_DECL(int) pj_utoa(unsigned long val, char *buf); + +/** + * Convert unsigned integer to string with minimum digits. Note that the + * string will be NULL terminated. + * + * @param val The unsigned integer value. + * @param buf The buffer. + * @param min_dig Minimum digits to be printed, or zero to specify no + * minimum digit. + * @param pad The padding character to be put in front of the string + * when the digits is less than minimum. + * + * @return the number of characters written. + */ +PJ_DECL(int) pj_utoa_pad(unsigned long val, char *buf, int min_dig, int pad); + +/** + * Fill the memory location with zero. + * + * @param dst The destination buffer. + * @param size The number of bytes. + */ +PJ_INLINE(void) pj_bzero(void *dst, pj_size_t size) +{ +#if defined(PJ_HAS_BZERO) && PJ_HAS_BZERO != 0 + bzero(dst, size); +#else + memset(dst, 0, size); +#endif +} + +/** + * Fill the memory location with value. + * + * @param dst The destination buffer. + * @param c Character to set. + * @param size The number of characters. + * + * @return the value of dst. + */ +PJ_INLINE(void *) pj_memset(void *dst, int c, pj_size_t size) +{ + return memset(dst, c, size); +} + +/** + * Copy buffer. + * + * @param dst The destination buffer. + * @param src The source buffer. + * @param size The size to copy. + * + * @return the destination buffer. + */ +PJ_INLINE(void *) pj_memcpy(void *dst, const void *src, pj_size_t size) +{ + return memcpy(dst, src, size); +} + +/** + * Move memory. + * + * @param dst The destination buffer. + * @param src The source buffer. + * @param size The size to copy. + * + * @return the destination buffer. + */ +PJ_INLINE(void *) pj_memmove(void *dst, const void *src, pj_size_t size) +{ + return memmove(dst, src, size); +} + +/** + * Compare buffers. + * + * @param buf1 The first buffer. + * @param buf2 The second buffer. + * @param size The size to compare. + * + * @return negative, zero, or positive value. + */ +PJ_INLINE(int) pj_memcmp(const void *buf1, const void *buf2, pj_size_t size) +{ + return memcmp(buf1, buf2, size); +} + +/** + * Find character in the buffer. + * + * @param buf The buffer. + * @param c The character to find. + * @param size The size to check. + * + * @return the pointer to location where the character is found, or NULL if + * not found. + */ +PJ_INLINE(void *) pj_memchr(const void *buf, int c, pj_size_t size) +{ + return (void *)memchr((void *)buf, c, size); +} + +/** + * @} + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#include +#endif + +PJ_END_DECL + +#endif /* __PJ_STRING_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h b/src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h new file mode 100755 index 000000000..ad2ebcbc1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +PJ_IDEF(pj_str_t) pj_str(char *str) +{ + pj_str_t dst; + dst.ptr = str; + dst.slen = str ? pj_ansi_strlen(str) : 0; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) +{ + pj_assert(src->slen >= 0); + + /* Without this, destination will be corrupted */ + if (dst == src) + return dst; + + if (src->slen > 0) { + dst->ptr = (char *)pj_pool_alloc(pool, src->slen); + pj_memcpy(dst->ptr, src->ptr, src->slen); + } + dst->slen = (src->slen < 0) ? 0 : src->slen; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup_with_null(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) +{ + pj_size_t src_slen = src->slen; + + pj_assert(src->slen >= 0); + + /* Check if the source's length is invalid */ + if (src_slen < 0) + src_slen = 0; + + dst->ptr = (char *)pj_pool_alloc(pool, src_slen + 1); + if (src_slen) { + pj_memcpy(dst->ptr, src->ptr, src_slen); + } + dst->slen = src_slen; + dst->ptr[dst->slen] = '\0'; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup2(pj_pool_t *pool, pj_str_t *dst, const char *src) +{ + dst->slen = src ? pj_ansi_strlen(src) : 0; + if (dst->slen) { + dst->ptr = (char *)pj_pool_alloc(pool, dst->slen); + pj_memcpy(dst->ptr, src, dst->slen); + } else { + dst->ptr = NULL; + } + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup2_with_null(pj_pool_t *pool, pj_str_t *dst, const char *src) +{ + dst->slen = src ? pj_ansi_strlen(src) : 0; + dst->ptr = (char *)pj_pool_alloc(pool, dst->slen + 1); + if (dst->slen) { + pj_memcpy(dst->ptr, src, dst->slen); + } + dst->ptr[dst->slen] = '\0'; + return dst; +} + +PJ_IDEF(pj_str_t) pj_strdup3(pj_pool_t *pool, const char *src) +{ + pj_str_t temp; + pj_strdup2(pool, &temp, src); + return temp; +} + +PJ_IDEF(pj_str_t *) pj_strassign(pj_str_t *dst, pj_str_t *src) +{ + dst->ptr = src->ptr; + dst->slen = src->slen; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strcpy(pj_str_t *dst, const pj_str_t *src) +{ + pj_assert(src->slen >= 0); + + dst->slen = (src->slen < 0) ? 0 : src->slen; + if (src->slen > 0) + pj_memcpy(dst->ptr, src->ptr, src->slen); + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strcpy2(pj_str_t *dst, const char *src) +{ + dst->slen = src ? pj_ansi_strlen(src) : 0; + if (dst->slen > 0) + pj_memcpy(dst->ptr, src, dst->slen); + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strncpy(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max) +{ + pj_assert(src->slen >= 0); + pj_assert(max >= 0); + + if (max > src->slen) + max = src->slen; + if (max > 0) + pj_memcpy(dst->ptr, src->ptr, max); + dst->slen = (max < 0) ? 0 : max; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strncpy_with_null(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max) +{ + pj_assert(src->slen >= 0); + pj_assert(max > 0); + + if (max <= src->slen) + max = (max > 0) ? max - 1 : 0; + else + max = (src->slen < 0) ? 0 : src->slen; + + if (max > 0) + pj_memcpy(dst->ptr, src->ptr, max); + dst->ptr[max] = '\0'; + dst->slen = max; + return dst; +} + +PJ_IDEF(int) pj_strcmp(const pj_str_t *str1, const pj_str_t *str2) +{ + pj_assert(str1->slen >= 0); + pj_assert(str2->slen >= 0); + + if (str1->slen <= 0) { + return str2->slen <= 0 ? 0 : -1; + } else if (str2->slen <= 0) { + return 1; + } else { + pj_size_t min = (str1->slen < str2->slen) ? str1->slen : str2->slen; + int res = pj_memcmp(str1->ptr, str2->ptr, min); + if (res == 0) { + return (str1->slen < str2->slen) ? -1 : (str1->slen == str2->slen ? 0 : 1); + } else { + return res; + } + } +} + +PJ_IDEF(int) pj_strncmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len) +{ + pj_str_t copy1, copy2; + + pj_assert(str1->slen >= 0); + pj_assert(str2->slen >= 0); + + if (len < (unsigned)str1->slen && str1->slen > 0) { + copy1.ptr = str1->ptr; + copy1.slen = len; + str1 = ©1; + } + + if (len < (unsigned)str2->slen && str2->slen > 0) { + copy2.ptr = str2->ptr; + copy2.slen = len; + str2 = ©2; + } + + return pj_strcmp(str1, str2); +} + +PJ_IDEF(int) pj_strncmp2(const pj_str_t *str1, const char *str2, pj_size_t len) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.slen = 0; + } + + return pj_strncmp(str1, ©2, len); +} + +PJ_IDEF(int) pj_strcmp2(const pj_str_t *str1, const char *str2) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.ptr = NULL; + copy2.slen = 0; + } + + return pj_strcmp(str1, ©2); +} + +PJ_IDEF(int) pj_stricmp(const pj_str_t *str1, const pj_str_t *str2) +{ + pj_assert(str1->slen >= 0); + pj_assert(str2->slen >= 0); + + if (str1->slen <= 0) { + return str2->slen <= 0 ? 0 : -1; + } else if (str2->slen <= 0) { + return 1; + } else { + pj_size_t min = (str1->slen < str2->slen) ? str1->slen : str2->slen; + int res = pj_ansi_strnicmp(str1->ptr, str2->ptr, min); + if (res == 0) { + return (str1->slen < str2->slen) ? -1 : (str1->slen == str2->slen ? 0 : 1); + } else { + return res; + } + } +} + +#if defined(PJ_HAS_STRICMP_ALNUM) && PJ_HAS_STRICMP_ALNUM != 0 +PJ_IDEF(int) strnicmp_alnum(const char *str1, const char *str2, int len) +{ + if (len == 0) + return 0; + else { + register const pj_uint32_t *p1 = (pj_uint32_t *)str1, *p2 = (pj_uint32_t *)str2; + while (len > 3 && (*p1 & 0x5F5F5F5F) == (*p2 & 0x5F5F5F5F)) + ++p1, ++p2, len -= 4; + + if (len > 3) + return -1; +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + else if (len == 3) + return ((*p1 & 0x005F5F5F) == (*p2 & 0x005F5F5F)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x00005F5F) == (*p2 & 0x00005F5F)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x0000005F) == (*p2 & 0x0000005F)) ? 0 : -1; +#else + else if (len == 3) + return ((*p1 & 0x5F5F5F00) == (*p2 & 0x5F5F5F00)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x5F5F0000) == (*p2 & 0x5F5F0000)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x5F000000) == (*p2 & 0x5F000000)) ? 0 : -1; +#endif + else + return 0; + } +} + +PJ_IDEF(int) pj_stricmp_alnum(const pj_str_t *str1, const pj_str_t *str2) +{ + register int len = str1->slen; + + if (len != str2->slen) { + return (len < str2->slen) ? -1 : 1; + } else if (len == 0) { + return 0; + } else { + register const pj_uint32_t *p1 = (pj_uint32_t *)str1->ptr, *p2 = (pj_uint32_t *)str2->ptr; + while (len > 3 && (*p1 & 0x5F5F5F5F) == (*p2 & 0x5F5F5F5F)) + ++p1, ++p2, len -= 4; + + if (len > 3) + return -1; +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + else if (len == 3) + return ((*p1 & 0x005F5F5F) == (*p2 & 0x005F5F5F)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x00005F5F) == (*p2 & 0x00005F5F)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x0000005F) == (*p2 & 0x0000005F)) ? 0 : -1; +#else + else if (len == 3) + return ((*p1 & 0x5F5F5F00) == (*p2 & 0x5F5F5F00)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x5F5F0000) == (*p2 & 0x5F5F0000)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x5F000000) == (*p2 & 0x5F000000)) ? 0 : -1; +#endif + else + return 0; + } +} +#endif /* PJ_HAS_STRICMP_ALNUM */ + +PJ_IDEF(int) pj_stricmp2(const pj_str_t *str1, const char *str2) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.ptr = NULL; + copy2.slen = 0; + } + + return pj_stricmp(str1, ©2); +} + +PJ_IDEF(int) pj_strnicmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len) +{ + pj_str_t copy1, copy2; + + if (len < (unsigned)str1->slen && str1->slen > 0) { + copy1.ptr = str1->ptr; + copy1.slen = len; + str1 = ©1; + } + + if (len < (unsigned)str2->slen && str2->slen > 0) { + copy2.ptr = str2->ptr; + copy2.slen = len; + str2 = ©2; + } + + return pj_stricmp(str1, str2); +} + +PJ_IDEF(int) pj_strnicmp2(const pj_str_t *str1, const char *str2, pj_size_t len) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.slen = 0; + } + + return pj_strnicmp(str1, ©2, len); +} + +PJ_IDEF(void) pj_strcat(pj_str_t *dst, const pj_str_t *src) +{ + pj_assert(src->slen >= 0); + pj_assert(dst->slen >= 0); + + if (src->slen > 0 && dst->slen >= 0) { + pj_memcpy(dst->ptr + dst->slen, src->ptr, src->slen); + dst->slen += src->slen; + } +} + +PJ_IDEF(void) pj_strcat2(pj_str_t *dst, const char *str) +{ + pj_size_t len = str ? pj_ansi_strlen(str) : 0; + + pj_assert(dst->slen >= 0); + + if (len && dst->slen >= 0) { + pj_memcpy(dst->ptr + dst->slen, str, len); + dst->slen += len; + } +} + +PJ_IDEF(pj_str_t *) pj_strtrim(pj_str_t *str) +{ + pj_strltrim(str); + pj_strrtrim(str); + return str; +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/timer.h b/src/tuya_p2p/pjproject/pjlib/include/pj/timer.h new file mode 100755 index 000000000..9afd722f6 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/timer.h @@ -0,0 +1,353 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJ_TIMER_H__ +#define __PJ_TIMER_H__ + +/** + * @file timer.h + * @brief Timer Heap + */ + +#include +#include + +#if PJ_TIMER_USE_LINKED_LIST +#include +#endif + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_TIMER Timer Heap Management. + * @ingroup PJ_MISC + * @brief + * The timer scheduling implementation here is based on ACE library's + * ACE_Timer_Heap, with only little modification to suit our library's style + * (I even left most of the comments in the original source). + * + * To quote the original quote in ACE_Timer_Heap_T class: + * + * This implementation uses a heap-based callout queue of + * absolute times. Therefore, in the average and worst case, + * scheduling, canceling, and expiring timers is O(log N) (where + * N is the total number of timers). In addition, we can also + * preallocate as many \a ACE_Timer_Nodes as there are slots in + * the heap. This allows us to completely remove the need for + * dynamic memory allocation, which is important for real-time + * systems. + * + * You can find the fine ACE library at: + * http://www.cs.wustl.edu/~schmidt/ACE.html + * + * ACE is Copyright (C)1993-2006 Douglas C. Schmidt + * + * @{ + * + * \section pj_timer_examples_sec Examples + * + * For some examples on how to use the timer heap, please see the link below. + * + * - \ref page_pjlib_timer_test + */ + +/** + * The type for internal timer ID. + */ +typedef int pj_timer_id_t; + +/** + * Forward declaration for pj_timer_entry. + */ +struct pj_timer_entry; + +/** + * The type of callback function to be called by timer scheduler when a timer + * has expired. + * + * @param timer_heap The timer heap. + * @param entry Timer entry which timer's has expired. + */ +typedef void pj_timer_heap_callback(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry); + +/** + * This structure represents an entry to the timer. + */ +typedef struct pj_timer_entry { +#if !PJ_TIMER_USE_COPY && PJ_TIMER_USE_LINKED_LIST + /** + * Standard list members. + */ + PJ_DECL_LIST_MEMBER(struct pj_timer_entry); +#endif + + /** + * User data to be associated with this entry. + * Applications normally will put the instance of object that + * owns the timer entry in this field. + */ + void *user_data; + + /** + * Arbitrary ID assigned by the user/owner of this entry. + * Applications can use this ID to distinguish multiple + * timer entries that share the same callback and user_data. + */ + int id; + + /** + * Callback to be called when the timer expires. + */ + pj_timer_heap_callback *cb; + + /** + * Internal unique timer ID, which is assigned by the timer heap. + * Positive values indicate that the timer entry is running, + * while -1 means that it's not. Any other value may indicate that it + * hasn't been properly initialised or is in a bad state. + * Application should not touch this ID. + */ + pj_timer_id_t _timer_id; + +#if !PJ_TIMER_USE_COPY + /** + * The future time when the timer expires, which the value is updated + * by timer heap when the timer is scheduled. + */ + pj_time_val _timer_value; + + /** + * Internal: the group lock used by this entry, set when + * pj_timer_heap_schedule_w_lock() is used. + */ + pj_grp_lock_t *_grp_lock; + +#if PJ_TIMER_DEBUG + const char *src_file; + int src_line; +#endif + +#endif +} pj_timer_entry; + +/** + * Calculate memory size required to create a timer heap. + * + * @param count Number of timer entries to be supported. + * @return Memory size requirement in bytes. + */ +PJ_DECL(pj_size_t) pj_timer_heap_mem_size(pj_size_t count); + +/** + * Create a timer heap. + * + * @param pool The pool where allocations in the timer heap will be + * allocated. The timer heap will dynamicly allocate + * more storate from the pool if the number of timer + * entries registered is more than the size originally + * requested when calling this function. + * @param count The maximum number of timer entries to be supported + * initially. If the application registers more entries + * during runtime, then the timer heap will resize. + * @param ht Pointer to receive the created timer heap. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_timer_heap_create(pj_pool_t *pool, pj_size_t count, pj_timer_heap_t **ht); + +/** + * Destroy the timer heap. + * + * @param ht The timer heap. + */ +PJ_DECL(void) pj_timer_heap_destroy(pj_timer_heap_t *ht); + +/** + * Set lock object to be used by the timer heap. By default, the timer heap + * uses dummy synchronization. + * + * @param ht The timer heap. + * @param lock The lock object to be used for synchronization. + * @param auto_del If nonzero, the lock object will be destroyed when + * the timer heap is destroyed. + */ +PJ_DECL(void) pj_timer_heap_set_lock(pj_timer_heap_t *ht, pj_lock_t *lock, pj_bool_t auto_del); + +/** + * Set maximum number of timed out entries to process in a single poll. + * + * @param ht The timer heap. + * @param count Number of entries. + * + * @return The old number. + */ +PJ_DECL(unsigned) pj_timer_heap_set_max_timed_out_per_poll(pj_timer_heap_t *ht, unsigned count); + +/** + * Initialize a timer entry. Application should call this function at least + * once before scheduling the entry to the timer heap, to properly initialize + * the timer entry. + * + * @param entry The timer entry to be initialized. + * @param id Arbitrary ID assigned by the user/owner of this entry. + * Applications can use this ID to distinguish multiple + * timer entries that share the same callback and user_data. + * @param user_data User data to be associated with this entry. + * Applications normally will put the instance of object that + * owns the timer entry in this field. + * @param cb Callback function to be called when the timer elapses. + * + * @return The timer entry itself. + */ +PJ_DECL(pj_timer_entry *) +pj_timer_entry_init(pj_timer_entry *entry, int id, void *user_data, pj_timer_heap_callback *cb); + +/** + * Queries whether a timer entry is currently running. + * + * @param entry The timer entry to query. + * + * @return PJ_TRUE if the timer is running. PJ_FALSE if not. + */ +PJ_DECL(pj_bool_t) pj_timer_entry_running(pj_timer_entry *entry); + +/** + * Schedule a timer entry which will expire AFTER the specified delay. + * + * @param ht The timer heap. + * @param entry The entry to be registered. + * @param delay The interval to expire. + * @return PJ_SUCCESS, or the appropriate error code. + */ +#if PJ_TIMER_DEBUG +#define pj_timer_heap_schedule(ht, e, d) pj_timer_heap_schedule_dbg(ht, e, d, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) +pj_timer_heap_schedule_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, const char *src_file, + int src_line); +#else +PJ_DECL(pj_status_t) pj_timer_heap_schedule(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay); +#endif /* PJ_TIMER_DEBUG */ + +/** + * Schedule a timer entry which will expire AFTER the specified delay, and + * increment the reference counter of the group lock while the timer entry + * is active. The group lock reference counter will automatically be released + * after the timer callback is called or when the timer is cancelled. + * + * @param ht The timer heap. + * @param entry The entry to be registered. + * @param delay The interval to expire. + * @param id_val The value to be set to the "id" field of the timer entry + * once the timer is scheduled. + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +#if PJ_TIMER_DEBUG +#define pj_timer_heap_schedule_w_grp_lock(ht, e, d, id, g) \ + pj_timer_heap_schedule_w_grp_lock_dbg(ht, e, d, id, g, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) +pj_timer_heap_schedule_w_grp_lock_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock, const char *src_file, int src_line); +#else +PJ_DECL(pj_status_t) +pj_timer_heap_schedule_w_grp_lock(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock); +#endif /* PJ_TIMER_DEBUG */ + +/** + * Cancel a previously registered timer. This will also decrement the + * reference counter of the group lock associated with the timer entry, + * if the entry was scheduled with one. + * + * @param ht The timer heap. + * @param entry The entry to be cancelled. + * @return The number of timer cancelled, which should be one if the + * entry has really been registered, or zero if no timer was + * cancelled. + */ +PJ_DECL(int) pj_timer_heap_cancel(pj_timer_heap_t *ht, pj_timer_entry *entry); + +/** + * Cancel only if the previously registered timer is active. This will + * also decrement the reference counter of the group lock associated + * with the timer entry, if the entry was scheduled with one. In any + * case, set the "id" to the specified value. + * + * @param ht The timer heap. + * @param entry The entry to be cancelled. + * @param id_val Value to be set to "id" + * + * @return The number of timer cancelled, which should be one if the + * entry has really been registered, or zero if no timer was + * cancelled. + */ +PJ_DECL(int) pj_timer_heap_cancel_if_active(pj_timer_heap_t *ht, pj_timer_entry *entry, int id_val); + +/** + * Get the number of timer entries. + * + * @param ht The timer heap. + * @return The number of timer entries. + */ +PJ_DECL(pj_size_t) pj_timer_heap_count(pj_timer_heap_t *ht); + +/** + * Get the earliest time registered in the timer heap. The timer heap + * MUST have at least one timer being scheduled (application should use + * #pj_timer_heap_count() before calling this function). + * + * @param ht The timer heap. + * @param timeval The time deadline of the earliest timer entry. + * + * @return PJ_SUCCESS, or PJ_ENOTFOUND if no entry is scheduled. + */ +PJ_DECL(pj_status_t) pj_timer_heap_earliest_time(pj_timer_heap_t *ht, pj_time_val *timeval); + +/** + * Poll the timer heap, check for expired timers and call the callback for + * each of the expired timers. + * + * Note: polling the timer heap is not necessary in Symbian. Please see + * @ref PJ_SYMBIAN_OS for more info. + * + * @param ht The timer heap. + * @param next_delay If this parameter is not NULL, it will be filled up with + * the time delay until the next timer elapsed, or + * PJ_MAXINT32 in the sec part if no entry exist. + * + * @return The number of timers expired. + */ +PJ_DECL(unsigned) pj_timer_heap_poll(pj_timer_heap_t *ht, pj_time_val *next_delay); + +#if PJ_TIMER_DEBUG +/** + * Dump timer heap entries. + * + * @param ht The timer heap. + */ +PJ_DECL(void) pj_timer_heap_dump(pj_timer_heap_t *ht); +#endif + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_TIMER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/types.h b/src/tuya_p2p/pjproject/pjlib/include/pj/types.h new file mode 100755 index 000000000..495dd71bf --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/types.h @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_TYPES_H__ +#define __PJ_TYPES_H__ + +/** + * @file types.h + * @brief Declaration of basic types and utility. + */ +/** + * @defgroup PJ_BASIC Basic Data Types and Library Functionality. + * @ingroup PJ_DS + * @{ + */ +#include +#include + +PJ_BEGIN_DECL + +/* ************************************************************************* */ + +/** Signed 32bit integer. */ +typedef int pj_int32_t; + +/** Unsigned 32bit integer. */ +typedef unsigned int pj_uint32_t; + +/** Signed 16bit integer. */ +typedef short pj_int16_t; + +/** Unsigned 16bit integer. */ +typedef unsigned short pj_uint16_t; + +/** Signed 8bit integer. */ +typedef signed char pj_int8_t; + +/** Unsigned 8bit integer. */ +typedef unsigned char pj_uint8_t; + +/** Large unsigned integer. */ +typedef size_t pj_size_t; + +/** Large signed integer. */ +#if defined(PJ_WIN64) && PJ_WIN64 != 0 +typedef pj_int64_t pj_ssize_t; +#else +typedef long pj_ssize_t; +#endif + +/** Status code. */ +typedef int pj_status_t; + +/** Boolean. */ +typedef int pj_bool_t; + +/** Native char type, which will be equal to wchar_t for Unicode + * and char for ANSI. */ +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +typedef wchar_t pj_char_t; +#else +typedef char pj_char_t; +#endif + +/** This macro creates Unicode or ANSI literal string depending whether + * native platform string is Unicode or ANSI. */ +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +#define PJ_T(literal_str) L##literal_str +#else +#define PJ_T(literal_str) literal_str +#endif + +/** Some constants */ +enum pj_constants_ { + /** Status is OK. */ + PJ_SUCCESS = 0, + + /** True value. */ + PJ_TRUE = 1, + + /** False value. */ + PJ_FALSE = 0 +}; + +/** + * File offset type. + */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 +typedef pj_int64_t pj_off_t; +#else +typedef pj_ssize_t pj_off_t; +#endif + +/* ************************************************************************* */ +/* + * Data structure types. + */ +/** + * This type is used as replacement to legacy C string, and used throughout + * the library. By convention, the string is NOT null terminated. + */ +struct pj_str_t { + /** Buffer pointer, which is by convention NOT null terminated. */ + char *ptr; + + /** The length of the string. */ + pj_ssize_t slen; +}; + +/** + * This structure represents high resolution (64bit) time value. The time + * values represent time in cycles, which is retrieved by calling + * #pj_get_timestamp(). + */ +typedef union pj_timestamp { + struct { +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + pj_uint32_t lo; /**< Low 32-bit value of the 64-bit value. */ + pj_uint32_t hi; /**< high 32-bit value of the 64-bit value. */ +#else + pj_uint32_t hi; /**< high 32-bit value of the 64-bit value. */ + pj_uint32_t lo; /**< Low 32-bit value of the 64-bit value. */ +#endif + } u32; /**< The 64-bit value as two 32-bit values. */ + +#if PJ_HAS_INT64 + pj_uint64_t u64; /**< The whole 64-bit value, where available. */ +#endif +} pj_timestamp; + +/** + * The opaque data type for linked list, which is used as arguments throughout + * the linked list operations. + */ +typedef void pj_list_type; + +/** + * List. + */ +typedef struct pj_list pj_list; + +/** + * Opaque data type for hash tables. + */ +typedef struct pj_hash_table_t pj_hash_table_t; + +/** + * Opaque data type for hash entry (only used internally by hash table). + */ +typedef struct pj_hash_entry pj_hash_entry; + +/** + * Data type for hash search iterator. + * This structure should be opaque, however applications need to declare + * concrete variable of this type, that's why the declaration is visible here. + */ +typedef struct pj_hash_iterator_t { + pj_uint32_t index; /**< Internal index. */ + pj_hash_entry *entry; /**< Internal entry. */ +} pj_hash_iterator_t; + +/** + * Forward declaration for memory pool factory. + */ +typedef struct pj_pool_factory pj_pool_factory; + +/** + * Opaque data type for memory pool. + */ +typedef struct pj_pool_t pj_pool_t; + +/** + * Forward declaration for caching pool, a pool factory implementation. + */ +typedef struct pj_caching_pool pj_caching_pool; + +/** + * This type is used as replacement to legacy C string, and used throughout + * the library. + */ +typedef struct pj_str_t pj_str_t; + +/** + * Opaque data type for I/O Queue structure. + */ +typedef struct pj_ioqueue_t pj_ioqueue_t; + +/** + * Opaque data type for key that identifies a handle registered to the + * I/O queue framework. + */ +typedef struct pj_ioqueue_key_t pj_ioqueue_key_t; + +/** + * Opaque data to identify timer heap. + */ +typedef struct pj_timer_heap_t pj_timer_heap_t; + +/** + * Opaque data type for atomic operations. + */ +typedef struct pj_atomic_t pj_atomic_t; + +/** + * Value type of an atomic variable. + */ +typedef PJ_ATOMIC_VALUE_TYPE pj_atomic_value_t; + +/* ************************************************************************* */ + +/** Thread handle. */ +typedef struct pj_thread_t pj_thread_t; + +/** Lock object. */ +typedef struct pj_lock_t pj_lock_t; + +/** Group lock */ +typedef struct pj_grp_lock_t pj_grp_lock_t; + +/** Mutex handle. */ +typedef struct pj_mutex_t pj_mutex_t; + +/** Semaphore handle. */ +typedef struct pj_sem_t pj_sem_t; + +/** Event object. */ +typedef struct pj_event_t pj_event_t; + +/** Unidirectional stream pipe object. */ +typedef struct pj_pipe_t pj_pipe_t; + +/** Operating system handle. */ +typedef void *pj_oshandle_t; + +/** Socket handle. */ +#if defined(PJ_WIN64) && PJ_WIN64 != 0 +typedef pj_int64_t pj_sock_t; +#else +typedef long pj_sock_t; +#endif + +/** Generic socket address. */ +typedef void pj_sockaddr_t; + +/** Forward declaration. */ +typedef struct pj_sockaddr_in pj_sockaddr_in; + +/** Color type. */ +typedef unsigned int pj_color_t; + +/** Exception id. */ +typedef int pj_exception_id_t; + +/* ************************************************************************* */ + +/** Utility macro to compute the number of elements in static array. */ +#define PJ_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +/** + * Length of object names. + */ +#define PJ_MAX_OBJ_NAME 32 + +/* ************************************************************************* */ +/* + * General. + */ +/** + * Initialize the PJ Library. + * This function must be called before using the library. The purpose of this + * function is to initialize static library data, such as character table used + * in random string generation, and to initialize operating system dependent + * functionality (such as WSAStartup() in Windows). + * + * Apart from calling pj_init(), application typically should also initialize + * the random seed by calling pj_srand(). + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_init(void); + +/** + * Shutdown PJLIB. + */ +PJ_DECL(void) pj_shutdown(void); + +/** + * Type of callback to register to pj_atexit(). + */ +typedef void (*pj_exit_callback)(void); + +/** + * Register cleanup function to be called by PJLIB when pj_shutdown() is + * called. + * + * @param func The function to be registered. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_atexit(pj_exit_callback func); + +/** + * Swap the byte order of an 16bit data. + * + * @param val16 The 16bit data. + * + * @return An 16bit data with swapped byte order. + */ +PJ_INLINE(pj_int16_t) pj_swap16(pj_int16_t val16) +{ + pj_uint8_t *p = (pj_uint8_t *)&val16; + pj_uint8_t tmp = *p; + *p = *(p + 1); + *(p + 1) = tmp; + return val16; +} + +/** + * Swap the byte order of an 32bit data. + * + * @param val32 The 32bit data. + * + * @return An 32bit data with swapped byte order. + */ +PJ_INLINE(pj_int32_t) pj_swap32(pj_int32_t val32) +{ + pj_uint8_t *p = (pj_uint8_t *)&val32; + pj_uint8_t tmp = *p; + *p = *(p + 3); + *(p + 3) = tmp; + tmp = *(p + 1); + *(p + 1) = *(p + 2); + *(p + 2) = tmp; + return val32; +} + +/** + * Utility macro to check if uint32 var will overflow if converted to + * signed long. + */ +#if (PJ_MAXLONG <= 2147483647L) +#define PJ_CHECK_OVERFLOW_UINT32_TO_LONG(uint32_var, exec_on_overflow) \ + do { \ + if (uint32_var > PJ_MAXLONG) { \ + exec_on_overflow; \ + } \ + } while (0) +#else +/* 'long' is longer than 32bit, uint32_var should never overflow */ +#define PJ_CHECK_OVERFLOW_UINT32_TO_LONG(uint32_var, exec_on_overflow) +#endif + +/** + * @} + */ +/** + * @addtogroup PJ_TIME Time Data Type and Manipulation. + * @ingroup PJ_MISC + * @{ + */ + +/** + * Representation of time value in this library. + * This type can be used to represent either an interval or a specific time + * or date. + */ +typedef struct pj_time_val { + /** The seconds part of the time. */ + long sec; + + /** The miliseconds fraction of the time. */ + long msec; + +} pj_time_val; + +/** + * Normalize the value in time value. + * @param t Time value to be normalized. + */ +PJ_DECL(void) pj_time_val_normalize(pj_time_val *t); + +/** + * Get the total time value in miliseconds. This is the same as + * multiplying the second part with 1000 and then add the miliseconds + * part to the result. + * + * @param t The time value. + * @return Total time in miliseconds. + * @hideinitializer + */ +#define PJ_TIME_VAL_MSEC(t) ((t).sec * 1000 + (t).msec) + +/** + * This macro will check if \a t1 is equal to \a t2. + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if both time values are equal. + * @hideinitializer + */ +#define PJ_TIME_VAL_EQ(t1, t2) ((t1).sec == (t2).sec && (t1).msec == (t2).msec) + +/** + * This macro will check if \a t1 is greater than \a t2 + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is greater than t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_GT(t1, t2) ((t1).sec > (t2).sec || ((t1).sec == (t2).sec && (t1).msec > (t2).msec)) + +/** + * This macro will check if \a t1 is greater than or equal to \a t2 + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is greater than or equal to t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_GTE(t1, t2) (PJ_TIME_VAL_GT(t1, t2) || PJ_TIME_VAL_EQ(t1, t2)) + +/** + * This macro will check if \a t1 is less than \a t2 + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is less than t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_LT(t1, t2) (!(PJ_TIME_VAL_GTE(t1, t2))) + +/** + * This macro will check if \a t1 is less than or equal to \a t2. + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is less than or equal to t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_LTE(t1, t2) (!PJ_TIME_VAL_GT(t1, t2)) + +/** + * Add \a t2 to \a t1 and store the result in \a t1. Effectively + * + * this macro will expand as: (\a t1 += \a t2). + * @param t1 The time value to add. + * @param t2 The time value to be added to \a t1. + * @hideinitializer + */ +#define PJ_TIME_VAL_ADD(t1, t2) \ + do { \ + (t1).sec += (t2).sec; \ + (t1).msec += (t2).msec; \ + pj_time_val_normalize(&(t1)); \ + } while (0) + +/** + * Substract \a t2 from \a t1 and store the result in \a t1. Effectively + * this macro will expand as (\a t1 -= \a t2). + * + * @param t1 The time value to subsctract. + * @param t2 The time value to be substracted from \a t1. + * @hideinitializer + */ +#define PJ_TIME_VAL_SUB(t1, t2) \ + do { \ + (t1).sec -= (t2).sec; \ + (t1).msec -= (t2).msec; \ + pj_time_val_normalize(&(t1)); \ + } while (0) + +/** + * This structure represent the parsed representation of time. + * It is acquired by calling #pj_time_decode(). + */ +typedef struct pj_parsed_time { + /** This represents day of week where value zero means Sunday */ + int wday; + + /* This represents day of the year, 0-365, where zero means + * 1st of January. + */ + /*int yday; */ + + /** This represents day of month: 1-31 */ + int day; + + /** This represents month, with the value is 0 - 11 (zero is January) */ + int mon; + + /** This represent the actual year (unlike in ANSI libc where + * the value must be added by 1900). + */ + int year; + + /** This represents the second part, with the value is 0-59 */ + int sec; + + /** This represents the minute part, with the value is: 0-59 */ + int min; + + /** This represents the hour part, with the value is 0-23 */ + int hour; + + /** This represents the milisecond part, with the value is 0-999 */ + int msec; + +} pj_parsed_time; + +/** + * @} // Time Management + */ + +/* ************************************************************************* */ +/* + * Terminal. + */ +/** + * Color code combination. + */ +enum { + PJ_TERM_COLOR_R = 2, /**< Red */ + PJ_TERM_COLOR_G = 4, /**< Green */ + PJ_TERM_COLOR_B = 1, /**< Blue. */ + PJ_TERM_COLOR_BRIGHT = 8 /**< Bright mask. */ +}; + +PJ_END_DECL + +#endif /* __PJ_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h b/src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h new file mode 100755 index 000000000..fc87f6053 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_UNICODE_H__ +#define __PJ_UNICODE_H__ + +#include + +/** + * @defgroup PJ_UNICODE Unicode Support + * @ingroup PJ_MISC + * @{ + */ + +PJ_BEGIN_DECL + +/** + * @file unicode.h + * @brief Provides Unicode conversion for Unicode OSes + */ + +/** + * Convert ANSI strings to Unicode strings. + * + * @param str The ANSI string to be converted. + * @param len The length of the input string. + * @param wbuf Buffer to hold the Unicode string output. + * @param wbuf_count Buffer size, in number of elements (not bytes). + * + * @return The Unicode string, NULL terminated. + */ +PJ_DECL(wchar_t *) pj_ansi_to_unicode(const char *str, int len, wchar_t *wbuf, int wbuf_count); + +/** + * Convert Unicode string to ANSI string. + * + * @param wstr The Unicode string to be converted. + * @param len The length of the input string. + * @param buf Buffer to hold the ANSI string output. + * @param buf_size Size of the output buffer. + * + * @return The ANSI string, NULL terminated. + */ +PJ_DECL(char *) pj_unicode_to_ansi(const wchar_t *wstr, pj_ssize_t len, char *buf, int buf_size); + +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 + +/** + * This macro is used to declare temporary Unicode buffer for ANSI to + * Unicode conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_UNICODE_TEMP_BUF(buf, size) wchar_t buf[size]; + +/** + * This macro will convert ANSI string to native, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_STRING_TO_NATIVE(s, buf, max) pj_ansi_to_unicode(s, strlen(s), buf, max) + +/** + * This macro is used to declare temporary ANSI buffer for Unicode to + * ANSI conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_ANSI_TEMP_BUF(buf, size) char buf[size]; + +/** + * This macro will convert Unicode string to ANSI, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_NATIVE_TO_STRING(cs, buf, max) pj_unicode_to_ansi(cs, wcslen(cs), buf, max) + +#else + +/** + * This macro is used to declare temporary Unicode buffer for ANSI to + * Unicode conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_UNICODE_TEMP_BUF(var, size) +/** + * This macro will convert ANSI string to native, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_STRING_TO_NATIVE(s, buf, max) ((char *)s) +/** + * This macro is used to declare temporary ANSI buffer for Unicode to + * ANSI conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_ANSI_TEMP_BUF(buf, size) +/** + * This macro will convert Unicode string to ANSI, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_NATIVE_TO_STRING(cs, buf, max) ((char *)(const char *)cs) + +#endif + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJ_UNICODE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pjlib.h b/src/tuya_p2p/pjproject/pjlib/include/pjlib.h new file mode 100755 index 000000000..2231e22c8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pjlib.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJLIB_H__ +#define __PJLIB_H__ + +/** + * @file pjlib.h + * @brief Include all PJLIB header files. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif /* __PJLIB_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c b/src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c new file mode 100755 index 000000000..7c16c8d0e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +#include + +static pj_bool_t ios_bg_support = PJ_TRUE; +#endif + +#define PJ_ACTIVESOCK_MAX_LOOP 50 + +enum read_type { TYPE_NONE, TYPE_RECV, TYPE_RECV_FROM }; + +enum shutdown_dir { SHUT_NONE = 0, SHUT_RX = 1, SHUT_TX = 2 }; + +struct read_op { + pj_ioqueue_op_key_t op_key; + pj_uint8_t *pkt; + unsigned max_size; + pj_size_t size; + pj_sockaddr src_addr; + int src_addr_len; +}; + +struct accept_op { + pj_ioqueue_op_key_t op_key; + pj_sock_t new_sock; + pj_sockaddr rem_addr; + int rem_addr_len; +}; + +struct send_data { + pj_uint8_t *data; + pj_ssize_t len; + pj_ssize_t sent; + unsigned flags; +}; + +struct pj_activesock_t { + pj_ioqueue_key_t *key; + pj_bool_t stream_oriented; + pj_bool_t whole_data; + pj_ioqueue_t *ioqueue; + void *user_data; + unsigned async_count; + unsigned shutdown; + unsigned max_loop; + pj_activesock_cb cb; +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + int bg_setting; + pj_sock_t sock; + CFReadStreamRef readStream; +#endif + + unsigned err_counter; + pj_status_t last_err; + + struct send_data send_data; + + struct read_op *read_op; + pj_uint32_t read_flags; + enum read_type read_type; + + struct accept_op *accept_op; +}; + +static void ioqueue_on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); +static void ioqueue_on_write_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent); +#if PJ_HAS_TCP +static void ioqueue_on_accept_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t sock, + pj_status_t status); +static void ioqueue_on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status); +#endif + +PJ_DEF(void) pj_activesock_cfg_default(pj_activesock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->async_cnt = 1; + cfg->concurrency = -1; + cfg->whole_data = PJ_TRUE; +} + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +static void activesock_destroy_iphone_os_stream(pj_activesock_t *asock) +{ + if (asock->readStream) { + CFReadStreamClose(asock->readStream); + CFRelease(asock->readStream); + asock->readStream = NULL; + } +} + +static void activesock_create_iphone_os_stream(pj_activesock_t *asock) +{ + if (ios_bg_support && asock->bg_setting && asock->stream_oriented) { + activesock_destroy_iphone_os_stream(asock); + + CFStreamCreatePairWithSocket(kCFAllocatorDefault, asock->sock, &asock->readStream, NULL); + + if (!asock->readStream || + CFReadStreamSetProperty(asock->readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP) != + TRUE || + CFReadStreamOpen(asock->readStream) != TRUE) { + PJ_LOG(2, ("", "Failed to configure TCP transport for VoIP " + "usage. Usage of THIS particular TCP transport in " + "background mode will not be supported.")); + + activesock_destroy_iphone_os_stream(asock); + } + } +} + +PJ_DEF(void) pj_activesock_set_iphone_os_bg(pj_activesock_t *asock, int val) +{ + asock->bg_setting = val; + if (asock->bg_setting) + activesock_create_iphone_os_stream(asock); + else + activesock_destroy_iphone_os_stream(asock); +} + +PJ_DEF(void) pj_activesock_enable_iphone_os_bg(pj_bool_t val) +{ + ios_bg_support = val; +} +#endif + +PJ_DEF(pj_status_t) +pj_activesock_create(pj_pool_t *pool, pj_sock_t sock, int sock_type, const pj_activesock_cfg *opt, + pj_ioqueue_t *ioqueue, const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock) +{ + pj_activesock_t *asock; + pj_ioqueue_callback ioq_cb; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && ioqueue && cb && p_asock, PJ_EINVAL); + PJ_ASSERT_RETURN(sock != 0 && sock != PJ_INVALID_SOCKET, PJ_EINVAL); + PJ_ASSERT_RETURN(sock_type == pj_SOCK_STREAM() || sock_type == pj_SOCK_DGRAM(), PJ_EINVAL); + PJ_ASSERT_RETURN(!opt || opt->async_cnt >= 1, PJ_EINVAL); + + asock = PJ_POOL_ZALLOC_T(pool, pj_activesock_t); + asock->ioqueue = ioqueue; + asock->stream_oriented = (sock_type == pj_SOCK_STREAM()); + asock->async_count = (opt ? opt->async_cnt : 1); + asock->whole_data = (opt ? opt->whole_data : 1); + asock->max_loop = PJ_ACTIVESOCK_MAX_LOOP; + asock->user_data = user_data; + pj_memcpy(&asock->cb, cb, sizeof(*cb)); + + pj_bzero(&ioq_cb, sizeof(ioq_cb)); + ioq_cb.on_read_complete = &ioqueue_on_read_complete; + ioq_cb.on_write_complete = &ioqueue_on_write_complete; +#if PJ_HAS_TCP + ioq_cb.on_connect_complete = &ioqueue_on_connect_complete; + ioq_cb.on_accept_complete = &ioqueue_on_accept_complete; +#endif + + status = pj_ioqueue_register_sock2(pool, ioqueue, sock, (opt ? opt->grp_lock : NULL), asock, &ioq_cb, &asock->key); + if (status != PJ_SUCCESS) { + pj_activesock_close(asock); + return status; + } + + if (asock->whole_data) { + /* Must disable concurrency otherwise there is a race condition */ + pj_ioqueue_set_concurrency(asock->key, 0); + } else if (opt && opt->concurrency >= 0) { + pj_ioqueue_set_concurrency(asock->key, opt->concurrency); + } + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + asock->sock = sock; + asock->bg_setting = PJ_ACTIVESOCK_TCP_IPHONE_OS_BG; +#endif + + *p_asock = asock; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_activesock_create_udp(pj_pool_t *pool, const pj_sockaddr *addr, const pj_activesock_cfg *opt, pj_ioqueue_t *ioqueue, + const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock, + pj_sockaddr *bound_addr) +{ + pj_sock_t sock_fd; + pj_sockaddr default_addr; + pj_status_t status; + + if (addr == NULL) { + pj_sockaddr_init(pj_AF_INET(), &default_addr, NULL, 0); + addr = &default_addr; + } + + status = pj_sock_socket(addr->addr.sa_family, pj_SOCK_DGRAM(), 0, &sock_fd); + if (status != PJ_SUCCESS) { + return status; + } + + status = pj_sock_bind(sock_fd, addr, pj_sockaddr_get_len(addr)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock_fd); + return status; + } + + status = pj_activesock_create(pool, sock_fd, pj_SOCK_DGRAM(), opt, ioqueue, cb, user_data, p_asock); + if (status != PJ_SUCCESS) { + pj_sock_close(sock_fd); + return status; + } + + if (bound_addr) { + int addr_len = sizeof(*bound_addr); + status = pj_sock_getsockname(sock_fd, bound_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_activesock_close(*p_asock); + return status; + } + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_activesock_close(pj_activesock_t *asock) +{ + pj_ioqueue_key_t *key; + pj_bool_t unregister = PJ_FALSE; + + PJ_ASSERT_RETURN(asock, PJ_EINVAL); + asock->shutdown = SHUT_RX | SHUT_TX; + + /* Avoid double unregistration on the key */ + key = asock->key; + if (key) { + pj_ioqueue_lock_key(key); + unregister = (asock->key != NULL); + asock->key = NULL; + pj_ioqueue_unlock_key(key); + } + + if (unregister) { + pj_ioqueue_unregister(key); + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + activesock_destroy_iphone_os_stream(asock); +#endif + } + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_activesock_set_user_data(pj_activesock_t *asock, void *user_data) +{ + PJ_ASSERT_RETURN(asock, PJ_EINVAL); + asock->user_data = user_data; + return PJ_SUCCESS; +} + +PJ_DEF(void *) pj_activesock_get_user_data(pj_activesock_t *asock) +{ + PJ_ASSERT_RETURN(asock, NULL); + return asock->user_data; +} + +PJ_DEF(pj_status_t) +pj_activesock_start_read(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + void **readbuf; + unsigned i; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + + readbuf = (void **)pj_pool_calloc(pool, asock->async_count, sizeof(void *)); + + for (i = 0; i < asock->async_count; ++i) { + readbuf[i] = pj_pool_alloc(pool, buff_size); + } + + return pj_activesock_start_read2(asock, pool, buff_size, readbuf, flags); +} + +PJ_DEF(pj_status_t) +pj_activesock_start_read2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + PJ_ASSERT_RETURN(asock->read_type == TYPE_NONE, PJ_EINVALIDOP); + PJ_ASSERT_RETURN(asock->read_op == NULL, PJ_EINVALIDOP); + + asock->read_op = (struct read_op *)pj_pool_calloc(pool, asock->async_count, sizeof(struct read_op)); + asock->read_type = TYPE_RECV; + asock->read_flags = flags; + + for (i = 0; i < asock->async_count; ++i) { + struct read_op *r = &asock->read_op[i]; + pj_ssize_t size_to_read; + + r->pkt = (pj_uint8_t *)readbuf[i]; + size_to_read = r->max_size = buff_size; + + status = pj_ioqueue_recv(asock->key, &r->op_key, r->pkt, &size_to_read, PJ_IOQUEUE_ALWAYS_ASYNC | flags); + PJ_ASSERT_RETURN(status != PJ_SUCCESS, PJ_EBUG); + + if (status != PJ_EPENDING) + return status; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_activesock_start_recvfrom(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + void **readbuf; + unsigned i; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + + readbuf = (void **)pj_pool_calloc(pool, asock->async_count, sizeof(void *)); + + for (i = 0; i < asock->async_count; ++i) { + readbuf[i] = pj_pool_alloc(pool, buff_size); + } + + return pj_activesock_start_recvfrom2(asock, pool, buff_size, readbuf, flags); +} + +PJ_DEF(pj_status_t) +pj_activesock_start_recvfrom2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + PJ_ASSERT_RETURN(asock->read_type == TYPE_NONE, PJ_EINVALIDOP); + + asock->read_op = (struct read_op *)pj_pool_calloc(pool, asock->async_count, sizeof(struct read_op)); + asock->read_type = TYPE_RECV_FROM; + asock->read_flags = flags; + + for (i = 0; i < asock->async_count; ++i) { + struct read_op *r = &asock->read_op[i]; + pj_ssize_t size_to_read; + + r->pkt = (pj_uint8_t *)readbuf[i]; + size_to_read = r->max_size = buff_size; + r->src_addr_len = sizeof(r->src_addr); + + status = pj_ioqueue_recvfrom(asock->key, &r->op_key, r->pkt, &size_to_read, PJ_IOQUEUE_ALWAYS_ASYNC | flags, + &r->src_addr, &r->src_addr_len); + PJ_ASSERT_RETURN(status != PJ_SUCCESS, PJ_EBUG); + + if (status != PJ_EPENDING) + return status; + } + + return PJ_SUCCESS; +} + +static void ioqueue_on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) +{ + pj_activesock_t *asock; + struct read_op *r = (struct read_op *)op_key; + unsigned loop = 0; + pj_status_t status; + + asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + + /* Ignore if we've been shutdown */ + if (asock->shutdown & SHUT_RX) + return; + + do { + unsigned flags; + + if (bytes_read > 0) { + /* + * We've got new data. + */ + pj_size_t remainder; + pj_bool_t ret; + + /* Append this new data to existing data. If socket is stream + * oriented, user might have left some data in the buffer. + * Otherwise if socket is datagram there will be nothing in + * existing packet hence the packet will contain only the new + * packet. + */ + r->size += bytes_read; + + /* Set default remainder to zero */ + remainder = 0; + + /* And return value to TRUE */ + ret = PJ_TRUE; + + /* Notify callback */ + if (asock->read_type == TYPE_RECV && asock->cb.on_data_read) { + ret = (*asock->cb.on_data_read)(asock, r->pkt, r->size, PJ_SUCCESS, &remainder); + } else if (asock->read_type == TYPE_RECV_FROM && asock->cb.on_data_recvfrom) { + ret = (*asock->cb.on_data_recvfrom)(asock, r->pkt, r->size, &r->src_addr, r->src_addr_len, PJ_SUCCESS); + } + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + + /* Only stream oriented socket may leave data in the packet */ + if (asock->stream_oriented) { + r->size = remainder; + } else { + r->size = 0; + } + + } else if (bytes_read <= 0 && -bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && + (asock->stream_oriented || -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))) { + pj_size_t remainder; + pj_bool_t ret; + + if (bytes_read == 0) { + /* For stream/connection oriented socket, this means the + * connection has been closed. For datagram sockets, it means + * we've received datagram with zero length. + */ + if (asock->stream_oriented) + status = PJ_EEOF; + else + status = PJ_SUCCESS; + } else { + /* This means we've got an error. If this is stream/connection + * oriented, it means connection has been closed. For datagram + * sockets, it means we've got some error (e.g. EWOULDBLOCK). + */ + status = (pj_status_t)-bytes_read; + } + + /* Set default remainder to zero */ + remainder = 0; + + /* And return value to TRUE */ + ret = PJ_TRUE; + + /* Notify callback */ + if (asock->read_type == TYPE_RECV && asock->cb.on_data_read) { + /* For connection oriented socket, we still need to report + * the remainder data (if any) to the user to let user do + * processing with the remainder data before it closes the + * connection. + * If there is no remainder data, set the packet to NULL. + */ + + /* Shouldn't set the packet to NULL, as there may be active + * socket user, such as SSL socket, that needs to have access + * to the read buffer packet. + */ + // ret = (*asock->cb.on_data_read)(asock, (r->size? r->pkt:NULL), + // r->size, status, &remainder); + ret = (*asock->cb.on_data_read)(asock, r->pkt, r->size, status, &remainder); + + } else if (asock->read_type == TYPE_RECV_FROM && asock->cb.on_data_recvfrom) { + /* This would always be datagram oriented hence there's + * nothing in the packet. We can't be sure if there will be + * anything useful in the source_addr, so just put NULL + * there too. + */ + /* In some scenarios, status may be PJ_SUCCESS. The upper + * layer application may not expect the callback to be called + * with successful status and NULL data, so lets not call the + * callback if the status is PJ_SUCCESS. + */ + if (status != PJ_SUCCESS) { + ret = (*asock->cb.on_data_recvfrom)(asock, NULL, 0, NULL, 0, status); + } + } + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + + /* Also stop further read if we've been shutdown */ + if (asock->shutdown & SHUT_RX) + return; + + /* Only stream oriented socket may leave data in the packet */ + if (asock->stream_oriented) { + r->size = remainder; + } else { + r->size = 0; + } + } + + /* Read next data. We limit ourselves to processing max_loop immediate + * data, so when the loop counter has exceeded this value, force the + * read()/recvfrom() to return pending operation to allow the program + * to do other jobs. + */ + bytes_read = r->max_size - r->size; + flags = asock->read_flags; + if (++loop >= asock->max_loop) + flags |= PJ_IOQUEUE_ALWAYS_ASYNC; + + if (asock->read_type == TYPE_RECV) { + status = pj_ioqueue_recv(key, op_key, r->pkt + r->size, &bytes_read, flags); + } else { + r->src_addr_len = sizeof(r->src_addr); + status = + pj_ioqueue_recvfrom(key, op_key, r->pkt + r->size, &bytes_read, flags, &r->src_addr, &r->src_addr_len); + } + + if (status == PJ_SUCCESS) { + /* Immediate data */ + ; + } else if (status != PJ_EPENDING && status != PJ_ECANCELLED) { + /* Error */ + bytes_read = -status; + } else { + break; + } + } while (1); +} + +static pj_status_t send_remaining(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key) +{ + struct send_data *sd = (struct send_data *)send_key->activesock_data; + pj_status_t status; + + do { + pj_ssize_t size; + + size = sd->len - sd->sent; + status = pj_ioqueue_send(asock->key, send_key, sd->data + sd->sent, &size, sd->flags); + if (status != PJ_SUCCESS) { + /* Pending or error */ + break; + } + + sd->sent += size; + if (sd->sent == sd->len) { + /* The whole data has been sent. */ + return PJ_SUCCESS; + } + + } while (sd->sent < sd->len); + + return status; +} + +PJ_DEF(pj_status_t) +pj_activesock_send(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags) +{ + PJ_ASSERT_RETURN(asock && send_key && data && size, PJ_EINVAL); + + if (asock->shutdown & SHUT_TX) + return PJ_EINVALIDOP; + + send_key->activesock_data = NULL; + + if (asock->whole_data) { + pj_ssize_t whole; + pj_status_t status; + + whole = *size; + + status = pj_ioqueue_send(asock->key, send_key, data, size, flags); + if (status != PJ_SUCCESS) { + /* Pending or error */ + return status; + } + + if (*size == whole) { + /* The whole data has been sent. */ + return PJ_SUCCESS; + } + + /* Data was partially sent */ + asock->send_data.data = (pj_uint8_t *)data; + asock->send_data.len = whole; + asock->send_data.sent = *size; + asock->send_data.flags = flags; + send_key->activesock_data = &asock->send_data; + + /* Try again */ + status = send_remaining(asock, send_key); + if (status == PJ_SUCCESS) { + *size = whole; + } + return status; + + } else { + return pj_ioqueue_send(asock->key, send_key, data, size, flags); + } +} + +PJ_DEF(pj_status_t) +pj_activesock_sendto(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len) +{ + PJ_ASSERT_RETURN(asock && send_key && data && size && addr && addr_len, PJ_EINVAL); + + if (asock->shutdown & SHUT_TX) + return PJ_EINVALIDOP; + + return pj_ioqueue_sendto(asock->key, send_key, data, size, flags, addr, addr_len); +} + +static void ioqueue_on_write_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent) +{ + pj_activesock_t *asock; + + asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + + /* Ignore if we've been shutdown. This may cause data to be partially + * sent even when 'wholedata' was requested if the OS only sent partial + * buffer. + */ + if (asock->shutdown & SHUT_TX) + return; + + if (bytes_sent > 0 && op_key->activesock_data) { + /* whole_data is requested. Make sure we send all the data */ + struct send_data *sd = (struct send_data *)op_key->activesock_data; + + sd->sent += bytes_sent; + if (sd->sent == sd->len) { + /* all has been sent */ + bytes_sent = sd->sent; + op_key->activesock_data = NULL; + } else { + /* send remaining data */ + pj_status_t status; + + status = send_remaining(asock, op_key); + if (status == PJ_EPENDING) + return; + else if (status == PJ_SUCCESS) + bytes_sent = sd->sent; + else + bytes_sent = -status; + + op_key->activesock_data = NULL; + } + } + + if (asock->cb.on_data_sent) { + pj_bool_t ret; + + ret = (*asock->cb.on_data_sent)(asock, op_key, bytes_sent); + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + } +} + +#if PJ_HAS_TCP +PJ_DEF(pj_status_t) pj_activesock_start_accept(pj_activesock_t *asock, pj_pool_t *pool) +{ + unsigned i; + + PJ_ASSERT_RETURN(asock, PJ_EINVAL); + PJ_ASSERT_RETURN(asock->accept_op == NULL, PJ_EINVALIDOP); + + /* Ignore if we've been shutdown */ + if (asock->shutdown) + return PJ_EINVALIDOP; + + asock->accept_op = (struct accept_op *)pj_pool_calloc(pool, asock->async_count, sizeof(struct accept_op)); + for (i = 0; i < asock->async_count; ++i) { + struct accept_op *a = &asock->accept_op[i]; + pj_status_t status; + + do { + a->new_sock = PJ_INVALID_SOCKET; + a->rem_addr_len = sizeof(a->rem_addr); + + status = pj_ioqueue_accept(asock->key, &a->op_key, &a->new_sock, NULL, &a->rem_addr, &a->rem_addr_len); + if (status == PJ_SUCCESS) { + /* We've got immediate connection. Not sure if it's a good + * idea to call the callback now (probably application will + * not be prepared to process it), so lets just silently + * close the socket. + */ + pj_sock_close(a->new_sock); + } + } while (status == PJ_SUCCESS); + + if (status != PJ_EPENDING) { + return status; + } + } + + return PJ_SUCCESS; +} + +static void ioqueue_on_accept_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t new_sock, + pj_status_t status) +{ + pj_activesock_t *asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + struct accept_op *accept_op = (struct accept_op *)op_key; + + PJ_UNUSED_ARG(new_sock); + + /* Ignore if we've been shutdown */ + if (asock->shutdown) + return; + + do { + if (status == asock->last_err && status != PJ_SUCCESS) { + asock->err_counter++; + if (asock->err_counter >= PJ_ACTIVESOCK_MAX_CONSECUTIVE_ACCEPT_ERROR) { + PJ_LOG(3, ("", + "Received %d consecutive errors: %d for the accept()" + " operation, stopping further ioqueue accepts.", + asock->err_counter, asock->last_err)); + + if ((status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) && (asock->cb.on_accept_complete2)) { + (*asock->cb.on_accept_complete2)(asock, accept_op->new_sock, &accept_op->rem_addr, + accept_op->rem_addr_len, PJ_ESOCKETSTOP); + } + return; + } + } else { + asock->err_counter = 0; + asock->last_err = status; + } + + if (status == PJ_SUCCESS && (asock->cb.on_accept_complete2 || asock->cb.on_accept_complete)) { + pj_bool_t ret; + + /* Notify callback */ + if (asock->cb.on_accept_complete2) { + ret = (*asock->cb.on_accept_complete2)(asock, accept_op->new_sock, &accept_op->rem_addr, + accept_op->rem_addr_len, status); + } else { + ret = (*asock->cb.on_accept_complete)(asock, accept_op->new_sock, &accept_op->rem_addr, + accept_op->rem_addr_len); + } + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + activesock_create_iphone_os_stream(asock); +#endif + } else if (status == PJ_SUCCESS) { + /* Application doesn't handle the new socket, we need to + * close it to avoid resource leak. + */ + pj_sock_close(accept_op->new_sock); + } + + /* Don't start another accept() if we've been shutdown */ + if (asock->shutdown) + return; + + /* Prepare next accept() */ + accept_op->new_sock = PJ_INVALID_SOCKET; + accept_op->rem_addr_len = sizeof(accept_op->rem_addr); + + status = pj_ioqueue_accept(asock->key, op_key, &accept_op->new_sock, NULL, &accept_op->rem_addr, + &accept_op->rem_addr_len); + + } while (status != PJ_EPENDING && status != PJ_ECANCELLED); +} + +PJ_DEF(pj_status_t) +pj_activesock_start_connect(pj_activesock_t *asock, pj_pool_t *pool, const pj_sockaddr_t *remaddr, int addr_len) +{ + PJ_UNUSED_ARG(pool); + + if (asock->shutdown) + return PJ_EINVALIDOP; + + return pj_ioqueue_connect(asock->key, remaddr, addr_len); +} + +static void ioqueue_on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status) +{ + pj_activesock_t *asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + + /* Ignore if we've been shutdown */ + if (asock->shutdown) + return; + + if (asock->cb.on_connect_complete) { + pj_bool_t ret; + + ret = (*asock->cb.on_connect_complete)(asock, status); + + if (!ret) { + /* We've been destroyed */ + return; + } + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + activesock_create_iphone_os_stream(asock); +#endif + } +} +#endif /* PJ_HAS_TCP */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c b/src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c new file mode 100755 index 000000000..c8ec65b95 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +#if defined(PJ_GETADDRINFO_USE_CFHOST) && PJ_GETADDRINFO_USE_CFHOST != 0 +#include +#include +#endif + +PJ_DEF(pj_status_t) pj_gethostbyname(const pj_str_t *hostname, pj_hostent *phe) +{ + struct hostent *he; + char copy[PJ_MAX_HOSTNAME]; + + pj_assert(hostname && hostname->slen < PJ_MAX_HOSTNAME); + + if (hostname->slen >= PJ_MAX_HOSTNAME) + return PJ_ENAMETOOLONG; + + pj_memcpy(copy, hostname->ptr, hostname->slen); + copy[hostname->slen] = '\0'; + + he = gethostbyname(copy); + if (!he) { + return PJ_ERESOLVE; + /* DO NOT use pj_get_netos_error() since host resolution error + * is reported in h_errno instead of errno! + return pj_get_netos_error(); + */ + } + + phe->h_name = he->h_name; + phe->h_aliases = he->h_aliases; + phe->h_addrtype = he->h_addrtype; + phe->h_length = he->h_length; + phe->h_addr_list = he->h_addr_list; + + return PJ_SUCCESS; +} + +/* Resolve IPv4/IPv6 address */ +PJ_DEF(pj_status_t) pj_getaddrinfo(int af, const pj_str_t *nodename, unsigned *count, pj_addrinfo ai[]) +{ +#if defined(PJ_SOCK_HAS_GETADDRINFO) && PJ_SOCK_HAS_GETADDRINFO != 0 + char nodecopy[PJ_MAX_HOSTNAME]; + pj_bool_t has_addr = PJ_FALSE; + unsigned i; +#if defined(PJ_GETADDRINFO_USE_CFHOST) && PJ_GETADDRINFO_USE_CFHOST != 0 + CFStringRef hostname; + CFHostRef hostRef; + pj_status_t status = PJ_SUCCESS; +#else + int rc; + struct addrinfo hint, *res, *orig_res; +#endif + + PJ_ASSERT_RETURN(nodename && count && *count && ai, PJ_EINVAL); + PJ_ASSERT_RETURN(nodename->ptr && nodename->slen, PJ_EINVAL); + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6 || af == PJ_AF_UNSPEC, PJ_EINVAL); + +#if PJ_WIN32_WINCE + + /* Check if nodename is IP address */ + pj_bzero(&ai[0], sizeof(ai[0])); + if ((af == PJ_AF_INET || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET, nodename, &ai[0].ai_addr.ipv4.sin_addr) == PJ_SUCCESS) { + af = PJ_AF_INET; + has_addr = PJ_TRUE; + } else if ((af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET6, nodename, &ai[0].ai_addr.ipv6.sin6_addr) == PJ_SUCCESS) { + af = PJ_AF_INET6; + has_addr = PJ_TRUE; + } + + if (has_addr) { + pj_str_t tmp; + + tmp.ptr = ai[0].ai_canonname; + pj_strncpy_with_null(&tmp, nodename, PJ_MAX_HOSTNAME); + ai[0].ai_addr.addr.sa_family = (pj_uint16_t)af; + *count = 1; + + return PJ_SUCCESS; + } + +#else /* PJ_WIN32_WINCE */ + PJ_UNUSED_ARG(has_addr); +#endif + + /* Copy node name to null terminated string. */ + if (nodename->slen >= PJ_MAX_HOSTNAME) + return PJ_ENAMETOOLONG; + pj_memcpy(nodecopy, nodename->ptr, nodename->slen); + nodecopy[nodename->slen] = '\0'; + +#if defined(PJ_GETADDRINFO_USE_CFHOST) && PJ_GETADDRINFO_USE_CFHOST != 0 + hostname = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, nodecopy, kCFStringEncodingASCII, kCFAllocatorNull); + hostRef = CFHostCreateWithName(kCFAllocatorDefault, hostname); + if (CFHostStartInfoResolution(hostRef, kCFHostAddresses, nil)) { + CFArrayRef addrRef = CFHostGetAddressing(hostRef, nil); + i = 0; + if (addrRef != nil) { + CFIndex idx, naddr; + + naddr = CFArrayGetCount(addrRef); + for (idx = 0; idx < naddr && i < *count; idx++) { + struct sockaddr *addr; + size_t addr_size; + + addr = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addrRef, idx)); + /* This should not happen. */ + pj_assert(addr); + + /* Ignore unwanted address families */ + if (af != PJ_AF_UNSPEC && addr->sa_family != af) + continue; + + /* Store canonical name */ + pj_ansi_strcpy(ai[i].ai_canonname, nodecopy); + + /* Store address */ + addr_size = sizeof(*addr); + if (addr->sa_family == PJ_AF_INET6) { + addr_size = addr->sa_len; + } + PJ_ASSERT_ON_FAIL(addr_size <= sizeof(pj_sockaddr), continue); + pj_memcpy(&ai[i].ai_addr, addr, addr_size); + PJ_SOCKADDR_RESET_LEN(&ai[i].ai_addr); + + i++; + } + } + + *count = i; + if (*count == 0) + status = PJ_ERESOLVE; + + } else { + status = PJ_ERESOLVE; + } + + CFRelease(hostRef); + CFRelease(hostname); + + return status; +#else + /* Call getaddrinfo() */ + pj_bzero(&hint, sizeof(hint)); + hint.ai_family = af; + /* Zero value of ai_socktype means the implementation shall attempt + * to resolve the service name for all supported socket types */ + hint.ai_socktype = 0; + + rc = getaddrinfo(nodecopy, NULL, &hint, &res); + if (rc != 0) + return PJ_ERESOLVE; + + orig_res = res; + + /* Enumerate each item in the result */ + for (i = 0; i < *count && res; res = res->ai_next) { + unsigned j; + pj_bool_t duplicate_found = PJ_FALSE; + + /* Ignore unwanted address families */ + if (af != PJ_AF_UNSPEC && res->ai_family != af) + continue; + + if (res->ai_socktype != pj_SOCK_DGRAM() && res->ai_socktype != pj_SOCK_STREAM() && + /* It is possible that the result's sock type + * is unspecified. + */ + res->ai_socktype != 0) { + continue; + } + + /* Add current address in the resulting list if there + * is no duplicates only. */ + for (j = 0; j < i; j++) { + if (!pj_sockaddr_cmp(&ai[j].ai_addr, res->ai_addr)) { + duplicate_found = PJ_TRUE; + break; + } + } + if (duplicate_found) { + continue; + } + + /* Store canonical name (possibly truncating the name) */ + if (res->ai_canonname) { + pj_ansi_strncpy(ai[i].ai_canonname, res->ai_canonname, sizeof(ai[i].ai_canonname)); + ai[i].ai_canonname[sizeof(ai[i].ai_canonname) - 1] = '\0'; + } else { + pj_ansi_strcpy(ai[i].ai_canonname, nodecopy); + } + + /* Store address */ + PJ_ASSERT_ON_FAIL(res->ai_addrlen <= sizeof(pj_sockaddr), continue); + pj_memcpy(&ai[i].ai_addr, res->ai_addr, res->ai_addrlen); + PJ_SOCKADDR_RESET_LEN(&ai[i].ai_addr); + + /* Next slot */ + ++i; + } + + *count = i; + + freeaddrinfo(orig_res); + + /* Done */ + return (*count > 0 ? PJ_SUCCESS : PJ_ERESOLVE); +#endif + +#else /* PJ_SOCK_HAS_GETADDRINFO */ + pj_bool_t has_addr = PJ_FALSE; + + PJ_ASSERT_RETURN(count && *count, PJ_EINVAL); + +#if PJ_WIN32_WINCE + + /* Check if nodename is IP address */ + pj_bzero(&ai[0], sizeof(ai[0])); + if ((af == PJ_AF_INET || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET, nodename, &ai[0].ai_addr.ipv4.sin_addr) == PJ_SUCCESS) { + af = PJ_AF_INET; + has_addr = PJ_TRUE; + } else if ((af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET6, nodename, &ai[0].ai_addr.ipv6.sin6_addr) == PJ_SUCCESS) { + af = PJ_AF_INET6; + has_addr = PJ_TRUE; + } + + if (has_addr) { + pj_str_t tmp; + + tmp.ptr = ai[0].ai_canonname; + pj_strncpy_with_null(&tmp, nodename, PJ_MAX_HOSTNAME); + ai[0].ai_addr.addr.sa_family = (pj_uint16_t)af; + *count = 1; + + return PJ_SUCCESS; + } + +#else /* PJ_WIN32_WINCE */ + PJ_UNUSED_ARG(has_addr); +#endif + + if (af == PJ_AF_INET || af == PJ_AF_UNSPEC) { + pj_hostent he; + unsigned i, max_count; + pj_status_t status; + +/* VC6 complains that "he" is uninitialized */ +#ifdef _MSC_VER + pj_bzero(&he, sizeof(he)); +#endif + + status = pj_gethostbyname(nodename, &he); + if (status != PJ_SUCCESS) + return status; + + max_count = *count; + *count = 0; + + pj_bzero(ai, max_count * sizeof(pj_addrinfo)); + + for (i = 0; he.h_addr_list[i] && *count < max_count; ++i) { + pj_ansi_strncpy(ai[*count].ai_canonname, he.h_name, sizeof(ai[*count].ai_canonname)); + ai[*count].ai_canonname[sizeof(ai[*count].ai_canonname) - 1] = '\0'; + + ai[*count].ai_addr.ipv4.sin_family = PJ_AF_INET; + pj_memcpy(&ai[*count].ai_addr.ipv4.sin_addr, he.h_addr_list[i], he.h_length); + PJ_SOCKADDR_RESET_LEN(&ai[*count].ai_addr); + + (*count)++; + } + + return (*count > 0 ? PJ_SUCCESS : PJ_ERESOLVE); + + } else { + /* IPv6 is not supported */ + *count = 0; + + return PJ_EIPV6NOTSUP; + } +#endif /* PJ_SOCK_HAS_GETADDRINFO */ +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/array.c b/src/tuya_p2p/pjproject/pjlib/src/pj/array.c new file mode 100755 index 000000000..4a9ef0024 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/array.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +PJ_DEF(void) pj_array_insert(void *array, unsigned elem_size, unsigned count, unsigned pos, const void *value) +{ + if (count && pos < count) { + pj_memmove((char *)array + (pos + 1) * elem_size, (char *)array + pos * elem_size, + (count - pos) * (pj_size_t)elem_size); + } + pj_memmove((char *)array + pos * elem_size, value, elem_size); +} + +PJ_DEF(void) pj_array_erase(void *array, unsigned elem_size, unsigned count, unsigned pos) +{ + pj_assert(count != 0); + if (pos < count - 1) { + pj_memmove((char *)array + pos * elem_size, (char *)array + (pos + 1) * elem_size, + (count - pos - 1) * (pj_size_t)elem_size); + } +} + +PJ_DEF(pj_status_t) +pj_array_find(const void *array, unsigned elem_size, unsigned count, pj_status_t (*matching)(const void *value), + void **result) +{ + unsigned i; + const char *char_array = (const char *)array; + for (i = 0; i < count; ++i) { + if ((*matching)(char_array) == PJ_SUCCESS) { + if (result) { + *result = (void *)char_array; + } + return PJ_SUCCESS; + } + char_array += elem_size; + } + return PJ_ENOTFOUND; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old new file mode 100755 index 000000000..8c72672be --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +int __sigjmp_save(sigjmp_buf env, int savemask) +{ + return 0; +} + +extern int __sigsetjmp(pj_jmp_buf env, int savemask); +extern void __longjmp(pj_jmp_buf env, int val) __attribute__((noreturn)); + +PJ_DEF(int) pj_setjmp(pj_jmp_buf env) +{ + return __sigsetjmp(env, 0); +} + +PJ_DEF(void) pj_longjmp(pj_jmp_buf env, int val) +{ + __longjmp(env, val); +} + diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old new file mode 100755 index 000000000..61b9a1218 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(int) strcasecmp(const char *s1, const char *s2) +{ + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} + +PJ_DEF(int) strncasecmp(const char *s1, const char *s2, int len) +{ + if (!len) return 0; + + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++ || --len <= 0) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} + diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old new file mode 100755 index 000000000..58a07c978 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + + +#if defined(PJ_HAS_STRING_H) && PJ_HAS_STRING_H != 0 +/* Nothing to do */ +#else +PJ_DEF(int) strcasecmp(const char *s1, const char *s2) +{ + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} + +PJ_DEF(int) strncasecmp(const char *s1, const char *s2, int len) +{ + if (!len) return 0; + + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++ || --len <= 0) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} +#endif + +#if defined(PJ_HAS_NO_SNPRINTF) && PJ_HAS_NO_SNPRINTF != 0 + +PJ_DEF(int) snprintf(char *s1, pj_size_t len, const char *s2, ...) +{ + int ret; + va_list arg; + + PJ_UNUSED_ARG(len); + + va_start(arg, s2); + ret = vsprintf(s1, s2, arg); + va_end(arg); + + return ret; +} + +PJ_DEF(int) vsnprintf(char *s1, pj_size_t len, const char *s2, va_list arg) +{ +#define MARK_CHAR ((char)255) + int rc; + + s1[len-1] = MARK_CHAR; + + rc = vsprintf(s1,s2,arg); + + pj_assert(s1[len-1] == MARK_CHAR || s1[len-1] == '\0'); + + return rc; +} + +#endif + diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/config.c b/src/tuya_p2p/pjproject/pjlib/src/pj/config.c new file mode 100755 index 000000000..a2613759a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/config.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +static const char *id = "config.c"; + +#define PJ_MAKE_VERSION3_1(a, b, d) #a "." #b d +#define PJ_MAKE_VERSION3_2(a, b, d) PJ_MAKE_VERSION3_1(a, b, d) + +#define PJ_MAKE_VERSION4_1(a, b, c, d) #a "." #b "." #c d +#define PJ_MAKE_VERSION4_2(a, b, c, d) PJ_MAKE_VERSION4_1(a, b, c, d) + +#if PJ_VERSION_NUM_REV +PJ_DEF_DATA(const char *) +PJ_VERSION = PJ_MAKE_VERSION4_2(PJ_VERSION_NUM_MAJOR, PJ_VERSION_NUM_MINOR, PJ_VERSION_NUM_REV, PJ_VERSION_NUM_EXTRA); +#else +PJ_DEF_DATA(const char *) +PJ_VERSION = PJ_MAKE_VERSION3_2(PJ_VERSION_NUM_MAJOR, PJ_VERSION_NUM_MINOR, PJ_VERSION_NUM_EXTRA); +#endif + +/* + * Get PJLIB version string. + */ +PJ_DEF(const char *) pj_get_version(void) +{ + return PJ_VERSION; +} + +PJ_DEF(void) pj_dump_config(void) +{ + PJ_LOG(3, (id, "PJLIB (c)2008-2016 Teluu Inc.")); + PJ_LOG(3, (id, "Dumping configurations:")); + PJ_LOG(3, (id, " PJ_VERSION : %s", PJ_VERSION)); + PJ_LOG(3, (id, " PJ_M_NAME : %s", PJ_M_NAME)); + PJ_LOG(3, (id, " PJ_HAS_PENTIUM : %d", PJ_HAS_PENTIUM)); + PJ_LOG(3, (id, " PJ_OS_NAME : %s", PJ_OS_NAME)); + PJ_LOG(3, (id, " PJ_CC_NAME/VER_(1,2,3) : %s-%d.%d.%d", PJ_CC_NAME, PJ_CC_VER_1, PJ_CC_VER_2, PJ_CC_VER_3)); + PJ_LOG(3, (id, " PJ_IS_(BIG/LITTLE)_ENDIAN : %s", (PJ_IS_BIG_ENDIAN ? "big-endian" : "little-endian"))); + PJ_LOG(3, (id, " PJ_HAS_INT64 : %d", PJ_HAS_INT64)); + PJ_LOG(3, (id, " PJ_HAS_FLOATING_POINT : %d", PJ_HAS_FLOATING_POINT)); + PJ_LOG(3, (id, " PJ_DEBUG : %d", PJ_DEBUG)); + PJ_LOG(3, (id, " PJ_FUNCTIONS_ARE_INLINED : %d", PJ_FUNCTIONS_ARE_INLINED)); + PJ_LOG(3, (id, " PJ_LOG_MAX_LEVEL : %d", PJ_LOG_MAX_LEVEL)); + PJ_LOG(3, (id, " PJ_LOG_MAX_SIZE : %d", PJ_LOG_MAX_SIZE)); + PJ_LOG(3, (id, " PJ_LOG_USE_STACK_BUFFER : %d", PJ_LOG_USE_STACK_BUFFER)); + PJ_LOG(3, (id, " PJ_POOL_DEBUG : %d", PJ_POOL_DEBUG)); + PJ_LOG(3, (id, " PJ_HAS_POOL_ALT_API : %d", PJ_HAS_POOL_ALT_API)); + PJ_LOG(3, (id, " PJ_HAS_TCP : %d", PJ_HAS_TCP)); + PJ_LOG(3, (id, " PJ_MAX_HOSTNAME : %d", PJ_MAX_HOSTNAME)); + PJ_LOG(3, (id, " ioqueue type : %s", pj_ioqueue_name())); + PJ_LOG(3, (id, " PJ_IOQUEUE_MAX_HANDLES : %d", PJ_IOQUEUE_MAX_HANDLES)); + PJ_LOG(3, (id, " PJ_IOQUEUE_HAS_SAFE_UNREG : %d", PJ_IOQUEUE_HAS_SAFE_UNREG)); + PJ_LOG(3, (id, " PJ_HAS_THREADS : %d", PJ_HAS_THREADS)); + PJ_LOG(3, (id, " PJ_LOG_USE_STACK_BUFFER : %d", PJ_LOG_USE_STACK_BUFFER)); + PJ_LOG(3, (id, " PJ_HAS_SEMAPHORE : %d", PJ_HAS_SEMAPHORE)); + PJ_LOG(3, (id, " PJ_HAS_EVENT_OBJ : %d", PJ_HAS_EVENT_OBJ)); + PJ_LOG(3, (id, " PJ_HAS_EXCEPTION_NAMES : %d", PJ_HAS_EXCEPTION_NAMES)); + PJ_LOG(3, (id, " PJ_MAX_EXCEPTION_ID : %d", PJ_MAX_EXCEPTION_ID)); + PJ_LOG(3, (id, " PJ_EXCEPTION_USE_WIN32_SEH: %d", PJ_EXCEPTION_USE_WIN32_SEH)); + PJ_LOG(3, (id, " PJ_TIMESTAMP_USE_RDTSC: : %d", PJ_TIMESTAMP_USE_RDTSC)); + PJ_LOG(3, (id, " PJ_OS_HAS_CHECK_STACK : %d", PJ_OS_HAS_CHECK_STACK)); + PJ_LOG(3, (id, " PJ_HAS_HIGH_RES_TIMER : %d", PJ_HAS_HIGH_RES_TIMER)); + PJ_LOG(3, (id, " PJ_HAS_IPV6 : %d", PJ_HAS_IPV6)); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c new file mode 100755 index 000000000..cb7585fc8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +/* +char pj_hex_digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +*/ + +int pjlib_ctype_c_dummy_symbol; diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/errno.c b/src/tuya_p2p/pjproject/pjlib/src/pj/errno.c new file mode 100755 index 000000000..2802bf467 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/errno.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +/* Prototype for platform specific error message, which will be defined + * in separate file. + */ +PJ_BEGIN_DECL + +PJ_DECL(int) platform_strerror(pj_os_err_type code, char *buf, pj_size_t bufsize); +PJ_END_DECL + +#ifndef PJLIB_MAX_ERR_MSG_HANDLER +#define PJLIB_MAX_ERR_MSG_HANDLER 10 +#endif + +/* Error message handler. */ +static unsigned err_msg_hnd_cnt; +static struct err_msg_hnd { + pj_status_t begin; + pj_status_t end; + pj_str_t (*strerror)(pj_status_t, char *, pj_size_t); + +} err_msg_hnd[PJLIB_MAX_ERR_MSG_HANDLER]; + +/* PJLIB's own error codes/messages */ +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 + +static const struct { + int code; + const char *msg; +} err_str[] = {PJ_BUILD_ERR(PJ_EUNKNOWN, "Unknown Error"), + PJ_BUILD_ERR(PJ_EPENDING, "Pending operation"), + PJ_BUILD_ERR(PJ_ETOOMANYCONN, "Too many connecting sockets"), + PJ_BUILD_ERR(PJ_EINVAL, "Invalid value or argument"), + PJ_BUILD_ERR(PJ_ENAMETOOLONG, "Name too long"), + PJ_BUILD_ERR(PJ_ENOTFOUND, "Not found"), + PJ_BUILD_ERR(PJ_ENOMEM, "Not enough memory"), + PJ_BUILD_ERR(PJ_EBUG, "BUG DETECTED!"), + PJ_BUILD_ERR(PJ_ETIMEDOUT, "Operation timed out"), + PJ_BUILD_ERR(PJ_ETOOMANY, "Too many objects of the specified type"), + PJ_BUILD_ERR(PJ_EBUSY, "Object is busy"), + PJ_BUILD_ERR(PJ_ENOTSUP, "Option/operation is not supported"), + PJ_BUILD_ERR(PJ_EINVALIDOP, "Invalid operation"), + PJ_BUILD_ERR(PJ_ECANCELLED, "Operation cancelled"), + PJ_BUILD_ERR(PJ_EEXISTS, "Object already exists"), + PJ_BUILD_ERR(PJ_EEOF, "End of file"), + PJ_BUILD_ERR(PJ_ETOOBIG, "Size is too big"), + PJ_BUILD_ERR(PJ_ERESOLVE, "gethostbyname() has returned error"), + PJ_BUILD_ERR(PJ_ETOOSMALL, "Size is too short"), + PJ_BUILD_ERR(PJ_EIGNORED, "Ignored"), + PJ_BUILD_ERR(PJ_EIPV6NOTSUP, "IPv6 is not supported"), + PJ_BUILD_ERR(PJ_EAFNOTSUP, "Unsupported address family"), + PJ_BUILD_ERR(PJ_EGONE, "Object no longer exists"), + PJ_BUILD_ERR(PJ_ESOCKETSTOP, "Socket is in bad state")}; +#endif /* PJ_HAS_ERROR_STRING */ + +/* + * pjlib_error() + * + * Retrieve message string for PJLIB's own error code. + */ +static int pjlib_error(pj_status_t code, char *buf, pj_size_t size) +{ + int len; + +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 + unsigned i; + + for (i = 0; i < sizeof(err_str) / sizeof(err_str[0]); ++i) { + if (err_str[i].code == code) { + pj_size_t len2 = pj_ansi_strlen(err_str[i].msg); + if (len2 >= size) + len2 = size - 1; + pj_memcpy(buf, err_str[i].msg, len2); + buf[len2] = '\0'; + return (int)len2; + } + } +#endif + + len = pj_ansi_snprintf(buf, size, "Unknown pjlib error %d", code); + if (len < 1 || len >= (int)size) + len = (int)(size - 1); + return len; +} + +#define IN_RANGE(val, start, end) ((val) >= (start) && (val) < (end)) + +/* Register strerror handle. */ +PJ_DEF(pj_status_t) pj_register_strerror(pj_status_t start, pj_status_t space, pj_error_callback f) +{ + unsigned i; + + /* Check arguments. */ + PJ_ASSERT_RETURN(start && space && f, PJ_EINVAL); + + /* Check if there aren't too many handlers registered. */ + PJ_ASSERT_RETURN(err_msg_hnd_cnt < PJ_ARRAY_SIZE(err_msg_hnd), PJ_ETOOMANY); + + /* Start error must be greater than PJ_ERRNO_START_USER */ + PJ_ASSERT_RETURN(start >= PJ_ERRNO_START_USER, PJ_EEXISTS); + + /* Check that no existing handler has covered the specified range. */ + for (i = 0; i < err_msg_hnd_cnt; ++i) { + if (IN_RANGE(start, err_msg_hnd[i].begin, err_msg_hnd[i].end) || + IN_RANGE(start + space - 1, err_msg_hnd[i].begin, err_msg_hnd[i].end)) { + if (err_msg_hnd[i].begin == start && err_msg_hnd[i].end == (start + space) && + err_msg_hnd[i].strerror == f) { + /* The same range and handler has already been registered */ + return PJ_SUCCESS; + } + + return PJ_EEXISTS; + } + } + + /* Register the handler. */ + err_msg_hnd[err_msg_hnd_cnt].begin = start; + err_msg_hnd[err_msg_hnd_cnt].end = start + space; + err_msg_hnd[err_msg_hnd_cnt].strerror = f; + + ++err_msg_hnd_cnt; + + return PJ_SUCCESS; +} + +/* Internal PJLIB function called by pj_shutdown() to clear error handlers */ +void pj_errno_clear_handlers(void) +{ + err_msg_hnd_cnt = 0; + pj_bzero(err_msg_hnd, sizeof(err_msg_hnd)); +} + +/* + * pj_strerror() + */ +PJ_DEF(pj_str_t) pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + int len = -1; + pj_str_t errstr; + + pj_assert(buf && bufsize); + + if (statcode == PJ_SUCCESS) { + len = pj_ansi_snprintf(buf, bufsize, "Success"); + + } else if (statcode < PJ_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + len = pj_ansi_snprintf(buf, bufsize, "Unknown error %d", statcode); + + } else if (statcode < PJ_ERRNO_START_STATUS + PJ_ERRNO_SPACE_SIZE) { + len = pjlib_error(statcode, buf, bufsize); + + } else if (statcode < PJ_ERRNO_START_SYS + PJ_ERRNO_SPACE_SIZE) { + len = platform_strerror(PJ_STATUS_TO_OS(statcode), buf, bufsize); + + } else { + unsigned i; + + /* Find user handler to get the error message. */ + for (i = 0; i < err_msg_hnd_cnt; ++i) { + if (IN_RANGE(statcode, err_msg_hnd[i].begin, err_msg_hnd[i].end)) { + return (*err_msg_hnd[i].strerror)(statcode, buf, bufsize); + } + } + + /* Handler not found! */ + len = pj_ansi_snprintf(buf, bufsize, "Unknown error %d", statcode); + } + + if (len < 1 || len >= (int)bufsize) { + len = (int)(bufsize - 1); + buf[len] = '\0'; + } + + errstr.ptr = buf; + errstr.slen = len; + + return errstr; +} + +#if PJ_LOG_MAX_LEVEL >= 1 +static void invoke_log(const char *sender, int level, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(sender, level, format, arg); + va_end(arg); +} + +static void pj_perror_imp(int log_level, const char *sender, pj_status_t status, const char *title_fmt, va_list marker) +{ + char titlebuf[PJ_PERROR_TITLE_BUF_SIZE]; + char errmsg[PJ_ERR_MSG_SIZE]; + int len; + + /* Build the title */ + len = pj_ansi_vsnprintf(titlebuf, sizeof(titlebuf), title_fmt, marker); + if (len < 0 || len >= (int)sizeof(titlebuf)) + pj_ansi_strcpy(titlebuf, "Error"); + + /* Get the error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + + /* Send to log */ + invoke_log(sender, log_level, "%s: %s", titlebuf, errmsg); +} + +PJ_DEF(void) pj_perror(int log_level, const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(log_level, sender, status, title_fmt, marker); + va_end(marker); +} + +PJ_DEF(void) pj_perror_1(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(1, sender, status, title_fmt, marker); + va_end(marker); +} + +#else /* #if PJ_LOG_MAX_LEVEL >= 1 */ +PJ_DEF(void) pj_perror(int log_level, const char *sender, pj_status_t status, const char *title_fmt, ...) {} +#endif /* #if PJ_LOG_MAX_LEVEL >= 1 */ + +#if PJ_LOG_MAX_LEVEL >= 2 +PJ_DEF(void) pj_perror_2(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(2, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 3 +PJ_DEF(void) pj_perror_3(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(3, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 4 +PJ_DEF(void) pj_perror_4(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(4, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 5 +PJ_DEF(void) pj_perror_5(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(5, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 6 +PJ_DEF(void) pj_perror_6(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(6, sender, status, title_fmt, marker); + va_end(marker); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/except.c b/src/tuya_p2p/pjproject/pjlib/src/pj/except.c new file mode 100755 index 000000000..519337dee --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/except.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +static long thread_local_id = -1; + +#if defined(PJ_HAS_EXCEPTION_NAMES) && PJ_HAS_EXCEPTION_NAMES != 0 +static const char *exception_id_names[PJ_MAX_EXCEPTION_ID]; +#else +/* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ +static int last_exception_id = 1; +#endif /* PJ_HAS_EXCEPTION_NAMES */ + +#if !defined(PJ_EXCEPTION_USE_WIN32_SEH) || PJ_EXCEPTION_USE_WIN32_SEH == 0 +PJ_DEF(void) pj_throw_exception_(int exception_id) +{ + struct pj_exception_state_t *handler; + + handler = (struct pj_exception_state_t *)pj_thread_local_get(thread_local_id); + if (handler == NULL) { + PJ_LOG(1, ("except.c", "!!!FATAL: unhandled exception %s!\n", pj_exception_id_name(exception_id))); + pj_assert(handler != NULL); + /* This will crash the system! */ + } + pj_pop_exception_handler_(handler); + pj_longjmp(handler->state, exception_id); +} + +static void exception_cleanup(void) +{ + if (thread_local_id != -1) { + pj_thread_local_free(thread_local_id); + thread_local_id = -1; + } + +#if defined(PJ_HAS_EXCEPTION_NAMES) && PJ_HAS_EXCEPTION_NAMES != 0 + { + unsigned i; + for (i = 0; i < PJ_MAX_EXCEPTION_ID; ++i) + exception_id_names[i] = NULL; + } +#else + last_exception_id = 1; +#endif +} + +PJ_DEF(void) pj_push_exception_handler_(struct pj_exception_state_t *rec) +{ + struct pj_exception_state_t *parent_handler = NULL; + + if (thread_local_id == -1) { + pj_thread_local_alloc(&thread_local_id); + pj_assert(thread_local_id != -1); + pj_atexit(&exception_cleanup); + } + parent_handler = (struct pj_exception_state_t *)pj_thread_local_get(thread_local_id); + rec->prev = parent_handler; + pj_thread_local_set(thread_local_id, rec); +} + +PJ_DEF(void) pj_pop_exception_handler_(struct pj_exception_state_t *rec) +{ + struct pj_exception_state_t *handler; + + handler = (struct pj_exception_state_t *)pj_thread_local_get(thread_local_id); + if (handler && handler == rec) { + pj_thread_local_set(thread_local_id, handler->prev); + } +} +#endif + +#if defined(PJ_HAS_EXCEPTION_NAMES) && PJ_HAS_EXCEPTION_NAMES != 0 +PJ_DEF(pj_status_t) pj_exception_id_alloc(const char *name, pj_exception_id_t *id) +{ + unsigned i; + + pj_enter_critical_section(); + + /* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ + for (i = 1; i < PJ_MAX_EXCEPTION_ID; ++i) { + if (exception_id_names[i] == NULL) { + exception_id_names[i] = name; + *id = i; + pj_leave_critical_section(); + return PJ_SUCCESS; + } + } + + pj_leave_critical_section(); + return PJ_ETOOMANY; +} + +PJ_DEF(pj_status_t) pj_exception_id_free(pj_exception_id_t id) +{ + /* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ + PJ_ASSERT_RETURN(id > 0 && id < PJ_MAX_EXCEPTION_ID, PJ_EINVAL); + + pj_enter_critical_section(); + exception_id_names[id] = NULL; + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(const char *) pj_exception_id_name(pj_exception_id_t id) +{ + static char unknown_name[32]; + + /* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ + PJ_ASSERT_RETURN(id > 0 && id < PJ_MAX_EXCEPTION_ID, ""); + + if (exception_id_names[id] == NULL) { + pj_ansi_snprintf(unknown_name, sizeof(unknown_name), "exception %d", id); + return unknown_name; + } + + return exception_id_names[id]; +} + +#else /* PJ_HAS_EXCEPTION_NAMES */ +PJ_DEF(pj_status_t) pj_exception_id_alloc(const char *name, pj_exception_id_t *id) +{ + PJ_ASSERT_RETURN(last_exception_id < PJ_MAX_EXCEPTION_ID - 1, PJ_ETOOMANY); + + *id = last_exception_id++; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_exception_id_free(pj_exception_id_t id) +{ + return PJ_SUCCESS; +} + +PJ_DEF(const char *) pj_exception_id_name(pj_exception_id_t id) +{ + return ""; +} + +#endif /* PJ_HAS_EXCEPTION_NAMES */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c b/src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c new file mode 100755 index 000000000..03bef7c3d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#define THIS_FILE "fifobuf" + +#define SZ sizeof(unsigned) + +PJ_DEF(void) pj_fifobuf_init(pj_fifobuf_t *fifobuf, void *buffer, unsigned size) +{ + PJ_CHECK_STACK(); + + PJ_LOG(6, (THIS_FILE, "fifobuf_init fifobuf=%p buffer=%p, size=%d", fifobuf, buffer, size)); + + fifobuf->first = (char *)buffer; + fifobuf->last = fifobuf->first + size; + fifobuf->ubegin = fifobuf->uend = fifobuf->first; + fifobuf->full = 0; +} + +PJ_DEF(unsigned) pj_fifobuf_max_size(pj_fifobuf_t *fifobuf) +{ + unsigned s1, s2; + + PJ_CHECK_STACK(); + + if (fifobuf->uend >= fifobuf->ubegin) { + s1 = (unsigned)(fifobuf->last - fifobuf->uend); + s2 = (unsigned)(fifobuf->ubegin - fifobuf->first); + } else { + s1 = s2 = (unsigned)(fifobuf->ubegin - fifobuf->uend); + } + + return s1 < s2 ? s2 : s1; +} + +PJ_DEF(void *) pj_fifobuf_alloc(pj_fifobuf_t *fifobuf, unsigned size) +{ + unsigned available; + char *start; + + PJ_CHECK_STACK(); + + if (fifobuf->full) { + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: full!", fifobuf, size)); + return NULL; + } + + /* try to allocate from the end part of the fifo */ + if (fifobuf->uend >= fifobuf->ubegin) { + available = (unsigned)(fifobuf->last - fifobuf->uend); + if (available >= size + SZ) { + char *ptr = fifobuf->uend; + fifobuf->uend += (size + SZ); + if (fifobuf->uend == fifobuf->last) + fifobuf->uend = fifobuf->first; + if (fifobuf->uend == fifobuf->ubegin) + fifobuf->full = 1; + *(unsigned *)ptr = size + SZ; + ptr += SZ; + + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: returning %p, p1=%p, p2=%p", fifobuf, size, ptr, + fifobuf->ubegin, fifobuf->uend)); + return ptr; + } + } + + /* try to allocate from the start part of the fifo */ + start = (fifobuf->uend <= fifobuf->ubegin) ? fifobuf->uend : fifobuf->first; + available = (unsigned)(fifobuf->ubegin - start); + if (available >= size + SZ) { + char *ptr = start; + fifobuf->uend = start + size + SZ; + if (fifobuf->uend == fifobuf->ubegin) + fifobuf->full = 1; + *(unsigned *)ptr = size + SZ; + ptr += SZ; + + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: returning %p, p1=%p, p2=%p", fifobuf, size, ptr, + fifobuf->ubegin, fifobuf->uend)); + return ptr; + } + + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: no space left! p1=%p, p2=%p", fifobuf, size, + fifobuf->ubegin, fifobuf->uend)); + return NULL; +} + +PJ_DEF(pj_status_t) pj_fifobuf_unalloc(pj_fifobuf_t *fifobuf, void *buf) +{ + char *ptr = (char *)buf; + char *endptr; + unsigned sz; + + PJ_CHECK_STACK(); + + ptr -= SZ; + sz = *(unsigned *)ptr; + + endptr = fifobuf->uend; + if (endptr == fifobuf->first) + endptr = fifobuf->last; + + if (ptr + sz != endptr) { + pj_assert(!"Invalid pointer to undo alloc"); + return -1; + } + + fifobuf->uend = ptr; + fifobuf->full = 0; + + PJ_LOG(6, (THIS_FILE, "fifobuf_unalloc fifobuf=%p, ptr=%p, size=%d, p1=%p, p2=%p", fifobuf, buf, sz, + fifobuf->ubegin, fifobuf->uend)); + + return 0; +} + +PJ_DEF(pj_status_t) pj_fifobuf_free(pj_fifobuf_t *fifobuf, void *buf) +{ + char *ptr = (char *)buf; + char *end; + unsigned sz; + + PJ_CHECK_STACK(); + + ptr -= SZ; + if (ptr < fifobuf->first || ptr >= fifobuf->last) { + pj_assert(!"Invalid pointer to free"); + return -1; + } + + if (ptr != fifobuf->ubegin && ptr != fifobuf->first) { + pj_assert(!"Invalid free() sequence!"); + return -1; + } + + end = (fifobuf->uend > fifobuf->ubegin) ? fifobuf->uend : fifobuf->last; + sz = *(unsigned *)ptr; + if (ptr + sz > end) { + pj_assert(!"Invalid size!"); + return -1; + } + + fifobuf->ubegin = ptr + sz; + + /* Rollover */ + if (fifobuf->ubegin == fifobuf->last) + fifobuf->ubegin = fifobuf->first; + + /* Reset if fifobuf is empty */ + if (fifobuf->ubegin == fifobuf->uend) + fifobuf->ubegin = fifobuf->uend = fifobuf->first; + + fifobuf->full = 0; + + PJ_LOG(6, (THIS_FILE, "fifobuf_free fifobuf=%p, ptr=%p, size=%d, p1=%p, p2=%p", fifobuf, buf, sz, fifobuf->ubegin, + fifobuf->uend)); + + return 0; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c new file mode 100755 index 000000000..65f626686 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#include +#include +#include +#include /* rename() */ +#include + +/* + * pj_file_exists() + */ +PJ_DEF(pj_bool_t) pj_file_exists(const char *filename) +{ + struct stat buf; + + PJ_ASSERT_RETURN(filename, 0); + + if (stat(filename, &buf) != 0) + return 0; + + return PJ_TRUE; +} + +/* + * pj_file_size() + */ +PJ_DEF(pj_off_t) pj_file_size(const char *filename) +{ + struct stat buf; + + PJ_ASSERT_RETURN(filename, -1); + + if (stat(filename, &buf) != 0) + return -1; + + return buf.st_size; +} + +/* + * pj_file_delete() + */ +PJ_DEF(pj_status_t) pj_file_delete(const char *filename) +{ + PJ_ASSERT_RETURN(filename, PJ_EINVAL); + + if (unlink(filename) != 0) { + return PJ_RETURN_OS_ERROR(errno); + } + return PJ_SUCCESS; +} + +/* + * pj_file_move() + */ +PJ_DEF(pj_status_t) pj_file_move(const char *oldname, const char *newname) +{ + PJ_ASSERT_RETURN(oldname && newname, PJ_EINVAL); + + if (rename(oldname, newname) != 0) { + return PJ_RETURN_OS_ERROR(errno); + } + return PJ_SUCCESS; +} + +/* + * pj_file_getstat() + */ +PJ_DEF(pj_status_t) pj_file_getstat(const char *filename, pj_file_stat *statbuf) +{ + struct stat buf; + + PJ_ASSERT_RETURN(filename && statbuf, PJ_EINVAL); + + if (stat(filename, &buf) != 0) { + return PJ_RETURN_OS_ERROR(errno); + } + + statbuf->size = buf.st_size; + statbuf->ctime.sec = buf.st_ctime; + statbuf->ctime.msec = 0; + statbuf->mtime.sec = buf.st_mtime; + statbuf->mtime.msec = 0; + statbuf->atime.sec = buf.st_atime; + statbuf->atime.msec = 0; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c b/src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c new file mode 100755 index 000000000..0ce7cf28e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +PJ_DEF(pj_status_t) pj_file_open(pj_pool_t *pool, const char *pathname, unsigned flags, pj_oshandle_t *fd) +{ + char mode[8]; + char *p = mode; + + PJ_ASSERT_RETURN(pathname && fd, PJ_EINVAL); + PJ_UNUSED_ARG(pool); + + if ((flags & PJ_O_APPEND) == PJ_O_APPEND) { + if ((flags & PJ_O_WRONLY) == PJ_O_WRONLY) { + *p++ = 'a'; + if ((flags & PJ_O_RDONLY) == PJ_O_RDONLY) + *p++ = '+'; + } else { + /* This is invalid. + * Can not specify PJ_O_RDONLY with PJ_O_APPEND! + */ + } + } else { + if ((flags & PJ_O_RDONLY) == PJ_O_RDONLY) { + *p++ = 'r'; + if ((flags & PJ_O_WRONLY) == PJ_O_WRONLY) + *p++ = '+'; + } else { + *p++ = 'w'; + } + } + + if (p == mode) + return PJ_EINVAL; + + *p++ = 'b'; + *p++ = '\0'; + + *fd = fopen(pathname, mode); + if (*fd == NULL) + return PJ_RETURN_OS_ERROR(errno); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_close(pj_oshandle_t fd) +{ + PJ_ASSERT_RETURN(fd, PJ_EINVAL); + if (fclose((FILE *)fd) != 0) + return PJ_RETURN_OS_ERROR(errno); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_write(pj_oshandle_t fd, const void *data, pj_ssize_t *size) +{ + size_t written; + + clearerr((FILE *)fd); + written = fwrite(data, 1, *size, (FILE *)fd); + if (ferror((FILE *)fd)) { + *size = -1; + return PJ_RETURN_OS_ERROR(errno); + } + + *size = written; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_read(pj_oshandle_t fd, void *data, pj_ssize_t *size) +{ + size_t bytes; + + clearerr((FILE *)fd); + bytes = fread(data, 1, *size, (FILE *)fd); + if (ferror((FILE *)fd)) { + *size = -1; + return PJ_RETURN_OS_ERROR(errno); + } + + *size = bytes; + return PJ_SUCCESS; +} + +/* +PJ_DEF(pj_bool_t) pj_file_eof(pj_oshandle_t fd, enum pj_file_access access) +{ + PJ_UNUSED_ARG(access); + return feof((FILE*)fd) ? PJ_TRUE : 0; +} +*/ + +PJ_DEF(pj_status_t) pj_file_setpos(pj_oshandle_t fd, pj_off_t offset, enum pj_file_seek_type whence) +{ + int mode; + + if ((sizeof(pj_off_t) > sizeof(long)) && (offset > PJ_MAXLONG || offset < PJ_MINLONG)) { + return PJ_ENOTSUP; + } + + switch (whence) { + case PJ_SEEK_SET: + mode = SEEK_SET; + break; + case PJ_SEEK_CUR: + mode = SEEK_CUR; + break; + case PJ_SEEK_END: + mode = SEEK_END; + break; + default: + pj_assert(!"Invalid whence in file_setpos"); + return PJ_EINVAL; + } + + if (fseek((FILE *)fd, (long)offset, mode) != 0) + return PJ_RETURN_OS_ERROR(errno); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_getpos(pj_oshandle_t fd, pj_off_t *pos) +{ + long offset; + + offset = ftell((FILE *)fd); + if (offset == -1) { + *pos = -1; + return PJ_RETURN_OS_ERROR(errno); + } + + *pos = offset; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_flush(pj_oshandle_t fd) +{ + int rc; + + rc = fflush((FILE *)fd); + if (rc == EOF) { + return PJ_RETURN_OS_ERROR(errno); + } + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/guid.c b/src/tuya_p2p/pjproject/pjlib/src/pj/guid.c new file mode 100755 index 000000000..f7b2fad4a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/guid.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(pj_str_t *) pj_generate_unique_string_lower(pj_str_t *str) +{ + int i; + + pj_generate_unique_string(str); + for (i = 0; i < str->slen; i++) + str->ptr[i] = (char)pj_tolower(str->ptr[i]); + + return str; +} + +PJ_DEF(void) pj_create_unique_string(pj_pool_t *pool, pj_str_t *str) +{ + str->ptr = (char *)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH); + pj_generate_unique_string(str); +} + +PJ_DEF(void) pj_create_unique_string_lower(pj_pool_t *pool, pj_str_t *str) +{ + int i; + + pj_create_unique_string(pool, str); + for (i = 0; i < str->slen; i++) + str->ptr[i] = (char)pj_tolower(str->ptr[i]); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c b/src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c new file mode 100755 index 000000000..317de642c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +PJ_DEF_DATA(const unsigned) PJ_GUID_STRING_LENGTH = 32; + +static char guid_chars[64]; + +PJ_DEF(unsigned) pj_GUID_STRING_LENGTH() +{ + return PJ_GUID_STRING_LENGTH; +} + +static void init_guid_chars(void) +{ + char *p = guid_chars; + unsigned i; + + for (i = 0; i < 10; ++i) + *p++ = '0' + i; + + for (i = 0; i < 26; ++i) { + *p++ = 'a' + i; + *p++ = 'A' + i; + } + + *p++ = '-'; + *p++ = '.'; +} + +PJ_DEF(pj_str_t *) pj_generate_unique_string(pj_str_t *str) +{ + char *p, *end; + + PJ_CHECK_STACK(); + + if (guid_chars[0] == '\0') { + pj_enter_critical_section(); + if (guid_chars[0] == '\0') { + init_guid_chars(); + } + pj_leave_critical_section(); + } + + /* This would only work if PJ_GUID_STRING_LENGTH is multiple of 2 bytes */ + pj_assert(PJ_GUID_STRING_LENGTH % 2 == 0); + + for (p = str->ptr, end = p + PJ_GUID_STRING_LENGTH; p < end;) { + pj_uint32_t rand_val = pj_rand(); + pj_uint32_t rand_idx = RAND_MAX; + + for (; rand_idx > 0 && p < end; rand_idx >>= 8, rand_val >>= 8, p++) { + *p = guid_chars[(rand_val & 0xFF) & 63]; + } + } + + str->slen = PJ_GUID_STRING_LENGTH; + return str; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/hash.c b/src/tuya_p2p/pjproject/pjlib/src/pj/hash.c new file mode 100755 index 000000000..e32fd3614 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/hash.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +/** + * The hash multiplier used to calculate hash value. + */ +#define PJ_HASH_MULTIPLIER 33 + +struct pj_hash_entry { + struct pj_hash_entry *next; + void *key; + pj_uint32_t hash; + pj_uint32_t keylen; + void *value; +}; + +struct pj_hash_table_t { + pj_hash_entry **table; + unsigned count, rows; + pj_hash_iterator_t iterator; +}; + +PJ_DEF(pj_uint32_t) pj_hash_calc(pj_uint32_t hash, const void *key, unsigned keylen) +{ + PJ_CHECK_STACK(); + + if (keylen == PJ_HASH_KEY_STRING) { + const pj_uint8_t *p = (const pj_uint8_t *)key; + for (; *p; ++p) { + hash = (hash * PJ_HASH_MULTIPLIER) + *p; + } + } else { + const pj_uint8_t *p = (const pj_uint8_t *)key, *end = p + keylen; + for (; p != end; ++p) { + hash = (hash * PJ_HASH_MULTIPLIER) + *p; + } + } + return hash; +} + +PJ_DEF(pj_uint32_t) pj_hash_calc_tolower(pj_uint32_t hval, char *result, const pj_str_t *key) +{ + long i; + + for (i = 0; i < key->slen; ++i) { + int lower = pj_tolower(key->ptr[i]); + if (result) + result[i] = (char)lower; + + hval = hval * PJ_HASH_MULTIPLIER + lower; + } + + return hval; +} + +PJ_DEF(pj_hash_table_t *) pj_hash_create(pj_pool_t *pool, unsigned size) +{ + pj_hash_table_t *h; + unsigned table_size; + + /* Check that PJ_HASH_ENTRY_BUF_SIZE is correct. */ + PJ_ASSERT_RETURN(sizeof(pj_hash_entry) <= PJ_HASH_ENTRY_BUF_SIZE, NULL); + + h = PJ_POOL_ALLOC_T(pool, pj_hash_table_t); + h->count = 0; + + PJ_LOG(6, ("hashtbl", "hash table %p created from pool %s", h, pj_pool_getobjname(pool))); + + /* size must be 2^n - 1. + round-up the size to this rule, except when size is 2^n, then size + will be round-down to 2^n-1. + */ + table_size = 8; + do { + table_size <<= 1; + } while (table_size < size); + table_size -= 1; + + h->rows = table_size; + h->table = (pj_hash_entry **)pj_pool_calloc(pool, table_size + 1, sizeof(pj_hash_entry *)); + return h; +} + +static pj_hash_entry **find_entry(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, void *val, + pj_uint32_t *hval, void *entry_buf, pj_bool_t lower) +{ + pj_uint32_t hash; + pj_hash_entry **p_entry, *entry; + + if (hval && *hval != 0) { + hash = *hval; + if (keylen == PJ_HASH_KEY_STRING) { + keylen = (unsigned)pj_ansi_strlen((const char *)key); + } + } else { + /* This slightly differs with pj_hash_calc() because we need + * to get the keylen when keylen is PJ_HASH_KEY_STRING. + */ + hash = 0; + if (keylen == PJ_HASH_KEY_STRING) { + const pj_uint8_t *p = (const pj_uint8_t *)key; + for (; *p; ++p) { + if (lower) + hash = hash * PJ_HASH_MULTIPLIER + pj_tolower(*p); + else + hash = hash * PJ_HASH_MULTIPLIER + *p; + } + keylen = (unsigned)(p - (const unsigned char *)key); + } else { + const pj_uint8_t *p = (const pj_uint8_t *)key, *end = p + keylen; + for (; p != end; ++p) { + if (lower) + hash = hash * PJ_HASH_MULTIPLIER + pj_tolower(*p); + else + hash = hash * PJ_HASH_MULTIPLIER + *p; + } + } + + /* Report back the computed hash. */ + if (hval) + *hval = hash; + } + + /* scan the linked list */ + for (p_entry = &ht->table[hash & ht->rows], entry = *p_entry; entry; p_entry = &entry->next, entry = *p_entry) { + if (entry->hash == hash && entry->keylen == keylen && + ((lower && pj_ansi_strnicmp((const char *)entry->key, (const char *)key, keylen) == 0) || + (!lower && pj_memcmp(entry->key, key, keylen) == 0))) { + break; + } + } + + if (entry || val == NULL) + return p_entry; + + /* Entry not found, create a new one. + * If entry_buf is specified, use it. Otherwise allocate from pool. + */ + if (entry_buf) { + entry = (pj_hash_entry *)entry_buf; + } else { + /* Pool must be specified! */ + PJ_ASSERT_RETURN(pool != NULL, NULL); + + entry = PJ_POOL_ALLOC_T(pool, pj_hash_entry); + PJ_LOG(6, ("hashtbl", "%p: New p_entry %p created, pool used=%u, cap=%u", ht, entry, + pj_pool_get_used_size(pool), pj_pool_get_capacity(pool))); + } + entry->next = NULL; + entry->hash = hash; + if (pool) { + entry->key = pj_pool_alloc(pool, keylen); + pj_memcpy(entry->key, key, keylen); + } else { + entry->key = (void *)key; + } + entry->keylen = keylen; + entry->value = val; + *p_entry = entry; + + ++ht->count; + + return p_entry; +} + +PJ_DEF(void *) pj_hash_get(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval) +{ + pj_hash_entry *entry; + entry = *find_entry(NULL, ht, key, keylen, NULL, hval, NULL, PJ_FALSE); + return entry ? entry->value : NULL; +} + +PJ_DEF(void *) pj_hash_get_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval) +{ + pj_hash_entry *entry; + entry = *find_entry(NULL, ht, key, keylen, NULL, hval, NULL, PJ_TRUE); + return entry ? entry->value : NULL; +} + +static void hash_set(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + void *value, void *entry_buf, pj_bool_t lower) +{ + pj_hash_entry **p_entry; + + p_entry = find_entry(pool, ht, key, keylen, value, &hval, entry_buf, lower); + if (*p_entry) { + if (value == NULL) { + /* delete entry */ + PJ_LOG(6, ("hashtbl", "%p: p_entry %p deleted", ht, *p_entry)); + *p_entry = (*p_entry)->next; + --ht->count; + + } else { + /* overwrite */ + (*p_entry)->value = value; + PJ_LOG(6, ("hashtbl", "%p: p_entry %p value set to %p", ht, *p_entry, value)); + } + } +} + +PJ_DEF(void) +pj_hash_set(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, void *value) +{ + hash_set(pool, ht, key, keylen, hval, value, NULL, PJ_FALSE); +} + +PJ_DEF(void) +pj_hash_set_lower(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, void *value) +{ + hash_set(pool, ht, key, keylen, hval, value, NULL, PJ_TRUE); +} + +PJ_DEF(void) +pj_hash_set_np(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, pj_hash_entry_buf entry_buf, + void *value) +{ + hash_set(NULL, ht, key, keylen, hval, value, (void *)entry_buf, PJ_FALSE); +} + +PJ_DEF(void) +pj_hash_set_np_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + pj_hash_entry_buf entry_buf, void *value) +{ + hash_set(NULL, ht, key, keylen, hval, value, (void *)entry_buf, PJ_TRUE); +} + +PJ_DEF(unsigned) pj_hash_count(pj_hash_table_t *ht) +{ + return ht->count; +} + +PJ_DEF(pj_hash_iterator_t *) pj_hash_first(pj_hash_table_t *ht, pj_hash_iterator_t *it) +{ + it->index = 0; + it->entry = NULL; + + for (; it->index <= ht->rows; ++it->index) { + it->entry = ht->table[it->index]; + if (it->entry) { + break; + } + } + + return it->entry ? it : NULL; +} + +PJ_DEF(pj_hash_iterator_t *) pj_hash_next(pj_hash_table_t *ht, pj_hash_iterator_t *it) +{ + it->entry = it->entry->next; + if (it->entry) { + return it; + } + + for (++it->index; it->index <= ht->rows; ++it->index) { + it->entry = ht->table[it->index]; + if (it->entry) { + break; + } + } + + return it->entry ? it : NULL; +} + +PJ_DEF(void *) pj_hash_this(pj_hash_table_t *ht, pj_hash_iterator_t *it) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(ht); + return it->entry->value; +} + +#if 0 +void pj_hash_dump_collision( pj_hash_table_t *ht ) +{ + unsigned min=0xFFFFFFFF, max=0; + unsigned i; + char line[120]; + int len, totlen = 0; + + for (i=0; i<=ht->rows; ++i) { + unsigned count = 0; + pj_hash_entry *entry = ht->table[i]; + while (entry) { + ++count; + entry = entry->next; + } + if (count < min) + min = count; + if (count > max) + max = count; + len = pj_snprintf( line+totlen, sizeof(line)-totlen, "%3d:%3d ", i, count); + if (len < 1) + break; + totlen += len; + + if ((i+1) % 10 == 0) { + line[totlen] = '\0'; + PJ_LOG(4,(__FILE__, line)); + } + } + + PJ_LOG(4,(__FILE__,"Count: %d, min: %d, max: %d\n", ht->count, min, max)); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c new file mode 100755 index 000000000..71215509c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c @@ -0,0 +1,1318 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * ioqueue_common_abs.c + * + * This contains common functionalities to emulate proactor pattern with + * various event dispatching mechanisms (e.g. select, epoll). + * + * This file will be included by the appropriate ioqueue implementation. + * This file is NOT supposed to be compiled as stand-alone source. + */ + +#define PENDING_RETRY 2 + +void pj_ioqueue_cfg_default(pj_ioqueue_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->epoll_flags = PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS; + cfg->default_concurrency = PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY; +} + +static void ioqueue_init(pj_ioqueue_t *ioqueue) +{ + ioqueue->lock = NULL; + ioqueue->auto_delete_lock = 0; +} + +static pj_status_t ioqueue_destroy(pj_ioqueue_t *ioqueue) +{ + if (ioqueue->auto_delete_lock && ioqueue->lock) { + pj_lock_release(ioqueue->lock); + return pj_lock_destroy(ioqueue->lock); + } + + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_set_lock() + */ +PJ_DEF(pj_status_t) pj_ioqueue_set_lock(pj_ioqueue_t *ioqueue, pj_lock_t *lock, pj_bool_t auto_delete) +{ + PJ_ASSERT_RETURN(ioqueue && lock, PJ_EINVAL); + + if (ioqueue->auto_delete_lock && ioqueue->lock) { + pj_lock_destroy(ioqueue->lock); + } + + ioqueue->lock = lock; + ioqueue->auto_delete_lock = auto_delete; + + return PJ_SUCCESS; +} + +static pj_status_t ioqueue_init_key(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, pj_sock_t sock, + pj_grp_lock_t *grp_lock, void *user_data, const pj_ioqueue_callback *cb) +{ + pj_status_t rc; + int optlen; + + PJ_UNUSED_ARG(pool); + + key->ioqueue = ioqueue; + key->fd = sock; + key->user_data = user_data; + pj_list_init(&key->read_list); + pj_list_init(&key->write_list); +#if PJ_HAS_TCP + pj_list_init(&key->accept_list); + key->connecting = 0; +#endif + + /* Save callback. */ + pj_memcpy(&key->cb, cb, sizeof(pj_ioqueue_callback)); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Set initial reference count to 1 */ + pj_assert(key->ref_count == 0); + ++key->ref_count; + + key->closing = 0; +#endif + + rc = pj_ioqueue_set_concurrency(key, ioqueue->cfg.default_concurrency); + if (rc != PJ_SUCCESS) + return rc; + + /* Get socket type. When socket type is datagram, some optimization + * will be performed during send to allow parallel send operations. + */ + optlen = sizeof(key->fd_type); + rc = pj_sock_getsockopt(sock, pj_SOL_SOCKET(), pj_SO_TYPE(), &key->fd_type, &optlen); + if (rc != PJ_SUCCESS) + key->fd_type = pj_SOCK_STREAM(); + + /* Create mutex for the key. */ +#if !PJ_IOQUEUE_HAS_SAFE_UNREG + rc = pj_lock_create_simple_mutex(pool, NULL, &key->lock); + if (rc != PJ_SUCCESS) + return rc; +#endif + + /* Group lock */ + key->grp_lock = grp_lock; + if (key->grp_lock) { + pj_grp_lock_add_ref_dbg(key->grp_lock, "ioqueue", 0); + } + + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_get_user_data() + * + * Obtain value associated with a key. + */ +PJ_DEF(void *) pj_ioqueue_get_user_data(pj_ioqueue_key_t *key) +{ + PJ_ASSERT_RETURN(key != NULL, NULL); + return key->user_data; +} + +/* + * pj_ioqueue_set_user_data() + */ +PJ_DEF(pj_status_t) pj_ioqueue_set_user_data(pj_ioqueue_key_t *key, void *user_data, void **old_data) +{ + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + if (old_data) + *old_data = key->user_data; + key->user_data = user_data; + + return PJ_SUCCESS; +} + +PJ_INLINE(int) key_has_pending_write(pj_ioqueue_key_t *key) +{ + return !pj_list_empty(&key->write_list); +} + +PJ_INLINE(int) key_has_pending_read(pj_ioqueue_key_t *key) +{ + return !pj_list_empty(&key->read_list); +} + +PJ_INLINE(int) key_has_pending_accept(pj_ioqueue_key_t *key) +{ +#if PJ_HAS_TCP + return !pj_list_empty(&key->accept_list); +#else + PJ_UNUSED_ARG(key); + return 0; +#endif +} + +PJ_INLINE(int) key_has_pending_connect(pj_ioqueue_key_t *key) +{ + return key->connecting; +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +#define IS_CLOSING(key) (key->closing) +#else +#define IS_CLOSING(key) (0) +#endif + +/* + * ioqueue_dispatch_event() + * + * Report occurence of an event in the key to be processed by the + * framework. + */ +pj_bool_t ioqueue_dispatch_write_event(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *h) +{ + pj_status_t rc; + + /* Try lock the key. */ + rc = pj_ioqueue_trylock_key(h); + if (rc != PJ_SUCCESS) { + return PJ_FALSE; + } + + if (IS_CLOSING(h)) { + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (h->connecting) { + /* Completion of connect() operation */ + pj_status_t status; + pj_bool_t has_lock; + + /* Clear operation. */ + h->connecting = 0; + + ioqueue_remove_from_set2(ioqueue, h, WRITEABLE_EVENT | EXCEPTION_EVENT); + +#if (defined(PJ_HAS_SO_ERROR) && PJ_HAS_SO_ERROR != 0) + /* from connect(2): + * On Linux, use getsockopt to read the SO_ERROR option at + * level SOL_SOCKET to determine whether connect() completed + * successfully (if SO_ERROR is zero). + */ + { + int value; + int vallen = sizeof(value); + int gs_rc = pj_sock_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &value, &vallen); + if (gs_rc != 0) { + /* Argh!! What to do now??? + * Just indicate that the socket is connected. The + * application will get error as soon as it tries to use + * the socket to send/receive. + */ + status = PJ_SUCCESS; + } else { + status = PJ_STATUS_FROM_OS(value); + } + } +#elif (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + status = PJ_SUCCESS; /* success */ +#else + /* Excellent information in D.J. Bernstein page: + * http://cr.yp.to/docs/connect.html + * + * Seems like the most portable way of detecting connect() + * failure is to call getpeername(). If socket is connected, + * getpeername() will return 0. If the socket is not connected, + * it will return ENOTCONN, and read(fd, &ch, 1) will produce + * the right errno through error slippage. This is a combination + * of suggestions from Douglas C. Schmidt and Ken Keys. + */ + { + struct sockaddr_in addr; + int addrlen = sizeof(addr); + + status = pj_sock_getpeername(h->fd, (struct sockaddr *)&addr, &addrlen); + } +#endif + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_connect_complete && !IS_CLOSING(h)) + (*h->cb.on_connect_complete)(h, status); + + /* Unlock if we still hold the lock */ + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + /* Done. */ + + } else +#endif /* PJ_HAS_TCP */ + if (key_has_pending_write(h)) { + /* Socket is writable. */ + struct write_operation *write_op; + pj_ssize_t sent; + pj_status_t send_rc = PJ_SUCCESS; + + /* Get the first in the queue. */ + write_op = h->write_list.next; + + /* For datagrams, we can remove the write_op from the list + * so that send() can work in parallel. + */ + if (h->fd_type == pj_SOCK_DGRAM()) { + pj_list_erase(write_op); + + if (pj_list_empty(&h->write_list)) + ioqueue_remove_from_set(ioqueue, h, WRITEABLE_EVENT); + } + + /* Send the data. + * Unfortunately we must do this while holding key's mutex, thus + * preventing parallel write on a single key.. :-(( + */ + sent = write_op->size - write_op->written; + if (write_op->op == PJ_IOQUEUE_OP_SEND) { + send_rc = pj_sock_send(h->fd, write_op->buf + write_op->written, &sent, write_op->flags); + /* Can't do this. We only clear "op" after we're finished sending + * the whole buffer. + */ + // write_op->op = 0; + } else if (write_op->op == PJ_IOQUEUE_OP_SEND_TO) { + int retry = 2; + while (--retry >= 0) { + send_rc = pj_sock_sendto(h->fd, write_op->buf + write_op->written, &sent, write_op->flags, + &write_op->rmt_addr, write_op->rmt_addrlen); +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + /* Special treatment for dead UDP sockets here, see ticket #1107 */ + if (send_rc == PJ_STATUS_FROM_OS(EPIPE) && !IS_CLOSING(h) && h->fd_type == pj_SOCK_DGRAM()) { + PJ_PERROR(4, (THIS_FILE, send_rc, "Send error for socket %d, retrying", h->fd)); + send_rc = replace_udp_sock(h); + continue; + } +#endif + break; + } + + /* Can't do this. We only clear "op" after we're finished sending + * the whole buffer. + */ + // write_op->op = 0; + } else { + pj_assert(!"Invalid operation type!"); + write_op->op = PJ_IOQUEUE_OP_NONE; + send_rc = PJ_EBUG; + } + + if (send_rc == PJ_SUCCESS) { + write_op->written += sent; + } else { + pj_assert(send_rc > 0); + write_op->written = -send_rc; + } + + /* Are we finished with this buffer? */ + if (send_rc != PJ_SUCCESS || write_op->written == (pj_ssize_t)write_op->size || + h->fd_type == pj_SOCK_DGRAM()) { + pj_bool_t has_lock; + + write_op->op = PJ_IOQUEUE_OP_NONE; + + if (h->fd_type != pj_SOCK_DGRAM()) { + /* Write completion of the whole stream. */ + pj_list_erase(write_op); + + /* Clear operation if there's no more data to send. */ + if (pj_list_empty(&h->write_list)) + ioqueue_remove_from_set(ioqueue, h, WRITEABLE_EVENT); + } + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_write_complete && !IS_CLOSING(h)) { + (*h->cb.on_write_complete)(h, (pj_ioqueue_op_key_t *)write_op, write_op->written); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + } else { + pj_ioqueue_unlock_key(h); + } + + /* Done. */ + } else { + /* + * This is normal; execution may fall here when multiple threads + * are signalled for the same event, but only one thread eventually + * able to process the event. + */ + pj_ioqueue_unlock_key(h); + + return PJ_FALSE; + } + + return PJ_TRUE; +} + +pj_bool_t ioqueue_dispatch_read_event(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *h) +{ + pj_status_t rc; + + /* Try lock the key. */ + rc = pj_ioqueue_trylock_key(h); + if (rc != PJ_SUCCESS) { + return PJ_FALSE; + } + + if (IS_CLOSING(h)) { + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + +#if PJ_HAS_TCP + if (!pj_list_empty(&h->accept_list)) { + + struct accept_operation *accept_op; + pj_bool_t has_lock; + + /* Get one accept operation from the list. */ + accept_op = h->accept_list.next; + pj_list_erase(accept_op); + accept_op->op = PJ_IOQUEUE_OP_NONE; + + /* Clear bit in fdset if there is no more pending accept */ + if (pj_list_empty(&h->accept_list)) + ioqueue_remove_from_set(ioqueue, h, READABLE_EVENT); + + rc = pj_sock_accept(h->fd, accept_op->accept_fd, accept_op->rmt_addr, accept_op->addrlen); + if (rc == PJ_SUCCESS && accept_op->local_addr) { + rc = pj_sock_getsockname(*accept_op->accept_fd, accept_op->local_addr, accept_op->addrlen); + } + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_accept_complete && !IS_CLOSING(h)) { + (*h->cb.on_accept_complete)(h, (pj_ioqueue_op_key_t *)accept_op, *accept_op->accept_fd, rc); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + } else +#endif + if (key_has_pending_read(h)) { + struct read_operation *read_op; + pj_ssize_t bytes_read; + pj_bool_t has_lock; + + /* Get one pending read operation from the list. */ + read_op = h->read_list.next; + pj_list_erase(read_op); + + /* Clear fdset if there is no pending read. */ + if (pj_list_empty(&h->read_list)) + ioqueue_remove_from_set(ioqueue, h, READABLE_EVENT); + + bytes_read = read_op->size; + + if (read_op->op == PJ_IOQUEUE_OP_RECV_FROM) { + read_op->op = PJ_IOQUEUE_OP_NONE; + rc = pj_sock_recvfrom(h->fd, read_op->buf, &bytes_read, read_op->flags, read_op->rmt_addr, + read_op->rmt_addrlen); + } else if (read_op->op == PJ_IOQUEUE_OP_RECV) { + read_op->op = PJ_IOQUEUE_OP_NONE; + rc = pj_sock_recv(h->fd, read_op->buf, &bytes_read, read_op->flags); + } else { + pj_assert(read_op->op == PJ_IOQUEUE_OP_READ); + read_op->op = PJ_IOQUEUE_OP_NONE; + /* + * User has specified pj_ioqueue_read(). + * On Win32, we should do ReadFile(). But because we got + * here because of select() anyway, user must have put a + * socket descriptor on h->fd, which in this case we can + * just call pj_sock_recv() instead of ReadFile(). + * On Unix, user may put a file in h->fd, so we'll have + * to call read() here. + * This may not compile on systems which doesn't have + * read(). That's why we only specify PJ_LINUX here so + * that error is easier to catch. + */ +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + rc = pj_sock_recv(h->fd, read_op->buf, &bytes_read, read_op->flags); + // rc = ReadFile((HANDLE)h->fd, read_op->buf, read_op->size, + // &bytes_read, NULL); +#elif (defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0) + bytes_read = read(h->fd, read_op->buf, bytes_read); + rc = (bytes_read >= 0) ? PJ_SUCCESS : pj_get_os_error(); +#else +#error "Implement read() for this platform!" +#endif + } + + if (rc != PJ_SUCCESS) { +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + /* On Win32, for UDP, WSAECONNRESET on the receive side + * indicates that previous sending has triggered ICMP Port + * Unreachable message. + * But we wouldn't know at this point which one of previous + * key that has triggered the error, since UDP socket can + * be shared! + * So we'll just ignore it! + */ + + if (rc == PJ_STATUS_FROM_OS(WSAECONNRESET)) { + // PJ_LOG(4,(THIS_FILE, + // "Ignored ICMP port unreach. on key=%p", h)); + } +#endif + + /* In any case we would report this to caller. */ + bytes_read = -rc; + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + /* Special treatment for dead UDP sockets here, see ticket #1107 */ + if (rc == PJ_STATUS_FROM_OS(ENOTCONN) && !IS_CLOSING(h) && h->fd_type == pj_SOCK_DGRAM()) { + rc = replace_udp_sock(h); + if (rc != PJ_SUCCESS) { + bytes_read = -rc; + } + } +#endif + } + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_read_complete && !IS_CLOSING(h)) { + (*h->cb.on_read_complete)(h, (pj_ioqueue_op_key_t *)read_op, bytes_read); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + } else { + /* + * This is normal; execution may fall here when multiple threads + * are signalled for the same event, but only one thread eventually + * able to process the event. + */ + pj_ioqueue_unlock_key(h); + + return PJ_FALSE; + } + + return PJ_TRUE; +} + +pj_bool_t ioqueue_dispatch_exception_event(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *h) +{ + pj_bool_t has_lock; + pj_status_t rc; + + /* Try lock the key. */ + rc = pj_ioqueue_trylock_key(h); + if (rc != PJ_SUCCESS) { + return PJ_FALSE; + } + + if (!h->connecting) { + /* It is possible that more than one thread was woken up, thus + * the remaining thread will see h->connecting as zero because + * it has been processed by other thread. + */ + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + + if (IS_CLOSING(h)) { + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + + /* Clear operation. */ + h->connecting = 0; + + ioqueue_remove_from_set2(ioqueue, h, WRITEABLE_EVENT | EXCEPTION_EVENT); + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_connect_complete && !IS_CLOSING(h)) { + pj_status_t status = -1; +#if (defined(PJ_HAS_SO_ERROR) && PJ_HAS_SO_ERROR != 0) + int value; + int vallen = sizeof(value); + int gs_rc = pj_sock_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &value, &vallen); + if (gs_rc == 0) { + status = PJ_RETURN_OS_ERROR(value); + } +#endif + + (*h->cb.on_connect_complete)(h, status); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + return PJ_TRUE; +} + +/* + * pj_ioqueue_recv() + * + * Start asynchronous recv() from the socket. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_recv(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, unsigned flags) +{ + struct read_operation *read_op; + + PJ_ASSERT_RETURN(key && op_key && buffer && length, PJ_EINVAL); + PJ_CHECK_STACK(); + + /* Check if key is closing (need to do this first before accessing + * other variables, since they might have been destroyed. See ticket + * #469). + */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + read_op = (struct read_operation *)op_key; + PJ_ASSERT_RETURN(read_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING); + read_op->op = PJ_IOQUEUE_OP_NONE; + + /* Try to see if there's data immediately available. + */ + if ((flags & PJ_IOQUEUE_ALWAYS_ASYNC) == 0) { + pj_status_t status; + pj_ssize_t size; + + size = *length; + status = pj_sock_recv(key->fd, buffer, &size, flags); + if (status == PJ_SUCCESS) { + /* Yes! Data is available! */ + *length = size; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) + return status; + } + } + + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* + * No data is immediately available. + * Must schedule asynchronous operation to the ioqueue. + */ + read_op->op = PJ_IOQUEUE_OP_RECV; + read_op->buf = buffer; + read_op->size = *length; + read_op->flags = flags; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->read_list, read_op); + ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * pj_ioqueue_recvfrom() + * + * Start asynchronous recvfrom() from the socket. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_recvfrom(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, + unsigned flags, pj_sockaddr_t *addr, int *addrlen) +{ + struct read_operation *read_op; + + PJ_ASSERT_RETURN(key && op_key && buffer && length, PJ_EINVAL); + PJ_CHECK_STACK(); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + read_op = (struct read_operation *)op_key; + PJ_ASSERT_RETURN(read_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING); + read_op->op = PJ_IOQUEUE_OP_NONE; + + /* Try to see if there's data immediately available. + */ + if ((flags & PJ_IOQUEUE_ALWAYS_ASYNC) == 0) { + pj_status_t status; + pj_ssize_t size; + + size = *length; + status = pj_sock_recvfrom(key->fd, buffer, &size, flags, addr, addrlen); + if (status == PJ_SUCCESS) { + /* Yes! Data is available! */ + *length = size; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) + return status; + } + } + + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* + * No data is immediately available. + * Must schedule asynchronous operation to the ioqueue. + */ + read_op->op = PJ_IOQUEUE_OP_RECV_FROM; + read_op->buf = buffer; + read_op->size = *length; + read_op->flags = flags; + read_op->rmt_addr = addr; + read_op->rmt_addrlen = addrlen; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->read_list, read_op); + ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * pj_ioqueue_send() + * + * Start asynchronous send() to the descriptor. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_send(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + unsigned flags) +{ + struct write_operation *write_op; + pj_status_t status; + unsigned retry; + pj_ssize_t sent; + + PJ_ASSERT_RETURN(key && op_key && data && length, PJ_EINVAL); + PJ_CHECK_STACK(); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + /* We can not use PJ_IOQUEUE_ALWAYS_ASYNC for socket write. */ + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* Fast track: + * Try to send data immediately, only if there's no pending write! + * Note: + * We are speculating that the list is empty here without properly + * acquiring ioqueue's mutex first. This is intentional, to maximize + * performance via parallelism. + * + * This should be safe, because: + * - by convention, we require caller to make sure that the + * key is not unregistered while other threads are invoking + * an operation on the same key. + * - pj_list_empty() is safe to be invoked by multiple threads, + * even when other threads are modifying the list. + */ + if (pj_list_empty(&key->write_list)) { + /* + * See if data can be sent immediately. + */ + sent = *length; + status = pj_sock_send(key->fd, data, &sent, flags); + if (status == PJ_SUCCESS) { + /* Success! */ + *length = sent; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) { + return status; + } + } + } + + /* + * Schedule asynchronous send. + */ + write_op = (struct write_operation *)op_key; + + /* Spin if write_op has pending operation */ + for (retry = 0; write_op->op != 0 && retry < PENDING_RETRY; ++retry) + pj_thread_sleep(0); + + /* Last chance */ + if (write_op->op) { + /* Unable to send packet because there is already pending write in the + * write_op. We could not put the operation into the write_op + * because write_op already contains a pending operation! And + * we could not send the packet directly with send() either, + * because that will break the order of the packet. So we can + * only return error here. + * + * This could happen for example in multithreads program, + * where polling is done by one thread, while other threads are doing + * the sending only. If the polling thread runs on lower priority + * than the sending thread, then it's possible that the pending + * write flag is not cleared in-time because clearing is only done + * during polling. + * + * Aplication should specify multiple write operation keys on + * situation like this. + */ + // pj_assert(!"ioqueue: there is pending operation on this key!"); + return PJ_EBUSY; + } + + write_op->op = PJ_IOQUEUE_OP_SEND; + write_op->buf = (char *)data; + write_op->size = *length; + write_op->written = 0; + write_op->flags = flags; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->write_list, write_op); + ioqueue_add_to_set(key->ioqueue, key, WRITEABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * pj_ioqueue_sendto() + * + * Start asynchronous write() to the descriptor. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_sendto(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + pj_uint32_t flags, const pj_sockaddr_t *addr, int addrlen) +{ + struct write_operation *write_op; + unsigned retry; + pj_bool_t restart_retry = PJ_FALSE; + pj_status_t status; + pj_ssize_t sent; + + PJ_ASSERT_RETURN(key && op_key && data && length, PJ_EINVAL); + PJ_CHECK_STACK(); + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +retry_on_restart: +#else + PJ_UNUSED_ARG(restart_retry); +#endif + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + /* We can not use PJ_IOQUEUE_ALWAYS_ASYNC for socket write */ + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* Fast track: + * Try to send data immediately, only if there's no pending write! + * Note: + * We are speculating that the list is empty here without properly + * acquiring ioqueue's mutex first. This is intentional, to maximize + * performance via parallelism. + * + * This should be safe, because: + * - by convention, we require caller to make sure that the + * key is not unregistered while other threads are invoking + * an operation on the same key. + * - pj_list_empty() is safe to be invoked by multiple threads, + * even when other threads are modifying the list. + */ + if (pj_list_empty(&key->write_list)) { + /* + * See if data can be sent immediately. + */ + sent = *length; + status = pj_sock_sendto(key->fd, data, &sent, flags, addr, addrlen); + if (status == PJ_SUCCESS) { + /* Success! */ + *length = sent; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) { +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + /* Special treatment for dead UDP sockets here, see ticket #1107 */ + if (status == PJ_STATUS_FROM_OS(EPIPE) && !IS_CLOSING(key) && key->fd_type == pj_SOCK_DGRAM()) { + if (!restart_retry) { + PJ_PERROR(4, (THIS_FILE, status, "Send error for socket %d, retrying", key->fd)); + status = replace_udp_sock(key); + if (status == PJ_SUCCESS) { + restart_retry = PJ_TRUE; + goto retry_on_restart; + } + } + status = PJ_ESOCKETSTOP; + } +#endif + return status; + } + } + } + + /* + * Check that address storage can hold the address parameter. + */ + PJ_ASSERT_RETURN(addrlen <= (int)sizeof(pj_sockaddr_in), PJ_EBUG); + + /* + * Schedule asynchronous send. + */ + write_op = (struct write_operation *)op_key; + + /* Spin if write_op has pending operation */ + for (retry = 0; write_op->op != 0 && retry < PENDING_RETRY; ++retry) + pj_thread_sleep(0); + + /* Last chance */ + if (write_op->op) { + /* Unable to send packet because there is already pending write on the + * write_op. We could not put the operation into the write_op + * because write_op already contains a pending operation! And + * we could not send the packet directly with sendto() either, + * because that will break the order of the packet. So we can + * only return error here. + * + * This could happen for example in multithreads program, + * where polling is done by one thread, while other threads are doing + * the sending only. If the polling thread runs on lower priority + * than the sending thread, then it's possible that the pending + * write flag is not cleared in-time because clearing is only done + * during polling. + * + * Aplication should specify multiple write operation keys on + * situation like this. + */ + // pj_assert(!"ioqueue: there is pending operation on this key!"); + return PJ_EBUSY; + } + + write_op->op = PJ_IOQUEUE_OP_SEND_TO; + write_op->buf = (char *)data; + write_op->size = *length; + write_op->written = 0; + write_op->flags = flags; + pj_memcpy(&write_op->rmt_addr, addr, addrlen); + write_op->rmt_addrlen = addrlen; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->write_list, write_op); + ioqueue_add_to_set(key->ioqueue, key, WRITEABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +#if PJ_HAS_TCP +/* + * Initiate overlapped accept() operation. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_accept(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t *new_sock, pj_sockaddr_t *local, + pj_sockaddr_t *remote, int *addrlen) +{ + struct accept_operation *accept_op; + pj_status_t status; + + /* check parameters. All must be specified! */ + PJ_ASSERT_RETURN(key && op_key && new_sock, PJ_EINVAL); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + accept_op = (struct accept_operation *)op_key; + PJ_ASSERT_RETURN(accept_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING); + accept_op->op = PJ_IOQUEUE_OP_NONE; + + /* Fast track: + * See if there's new connection available immediately. + */ + if (pj_list_empty(&key->accept_list)) { + status = pj_sock_accept(key->fd, new_sock, remote, addrlen); + if (status == PJ_SUCCESS) { + /* Yes! New connection is available! */ + if (local && addrlen) { + status = pj_sock_getsockname(*new_sock, local, addrlen); + if (status != PJ_SUCCESS) { + pj_sock_close(*new_sock); + *new_sock = PJ_INVALID_SOCKET; + return status; + } + } + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) { + return status; + } + } + } + + /* + * No connection is available immediately. + * Schedule accept() operation to be completed when there is incoming + * connection available. + */ + accept_op->op = PJ_IOQUEUE_OP_ACCEPT; + accept_op->accept_fd = new_sock; + accept_op->rmt_addr = remote; + accept_op->addrlen = addrlen; + accept_op->local_addr = local; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->accept_list, accept_op); + ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * Initiate overlapped connect() operation (well, it's non-blocking actually, + * since there's no overlapped version of connect()). + */ +PJ_DEF(pj_status_t) pj_ioqueue_connect(pj_ioqueue_key_t *key, const pj_sockaddr_t *addr, int addrlen) +{ + pj_status_t status; + + /* check parameters. All must be specified! */ + PJ_ASSERT_RETURN(key && addr && addrlen, PJ_EINVAL); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + /* Check if socket has not been marked for connecting */ + if (key->connecting != 0) + return PJ_EPENDING; + + status = pj_sock_connect(key->fd, addr, addrlen); + if (status == PJ_SUCCESS) { + /* Connected! */ + return PJ_SUCCESS; + } else { + if (status == PJ_STATUS_FROM_OS(PJ_BLOCKING_CONNECT_ERROR_VAL)) { + /* Pending! */ + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous + * check in multithreaded app. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + key->connecting = PJ_TRUE; + ioqueue_add_to_set2(key->ioqueue, key, WRITEABLE_EVENT | EXCEPTION_EVENT); + pj_ioqueue_unlock_key(key); + return PJ_EPENDING; + } else { + /* Error! */ + return status; + } + } +} +#endif /* PJ_HAS_TCP */ + +PJ_DEF(void) pj_ioqueue_op_key_init(pj_ioqueue_op_key_t *op_key, pj_size_t size) +{ + pj_bzero(op_key, size); +} + +/* + * pj_ioqueue_is_pending() + */ +PJ_DEF(pj_bool_t) pj_ioqueue_is_pending(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key) +{ + struct generic_operation *op_rec; + + PJ_UNUSED_ARG(key); + + op_rec = (struct generic_operation *)op_key; + return op_rec->op != 0; +} + +/* + * pj_ioqueue_post_completion() + */ +PJ_DEF(pj_status_t) +pj_ioqueue_post_completion(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_status) +{ + struct generic_operation *op_rec; + + /* + * Find the operation key in all pending operation list to + * really make sure that it's still there; then call the callback. + */ + pj_ioqueue_lock_key(key); + + /* Find the operation in the pending read list. */ + op_rec = (struct generic_operation *)key->read_list.next; + while (op_rec != (void *)&key->read_list) { + if (op_rec == (void *)op_key) { + pj_list_erase(op_rec); + op_rec->op = PJ_IOQUEUE_OP_NONE; + ioqueue_remove_from_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + if (key->cb.on_read_complete) + (*key->cb.on_read_complete)(key, op_key, bytes_status); + return PJ_SUCCESS; + } + op_rec = op_rec->next; + } + + /* Find the operation in the pending write list. */ + op_rec = (struct generic_operation *)key->write_list.next; + while (op_rec != (void *)&key->write_list) { + if (op_rec == (void *)op_key) { + pj_list_erase(op_rec); + op_rec->op = PJ_IOQUEUE_OP_NONE; + ioqueue_remove_from_set(key->ioqueue, key, WRITEABLE_EVENT); + pj_ioqueue_unlock_key(key); + + if (key->cb.on_write_complete) + (*key->cb.on_write_complete)(key, op_key, bytes_status); + return PJ_SUCCESS; + } + op_rec = op_rec->next; + } + + /* Find the operation in the pending accept list. */ + op_rec = (struct generic_operation *)key->accept_list.next; + while (op_rec != (void *)&key->accept_list) { + if (op_rec == (void *)op_key) { + pj_list_erase(op_rec); + op_rec->op = PJ_IOQUEUE_OP_NONE; + pj_ioqueue_unlock_key(key); + + if (key->cb.on_accept_complete) { + (*key->cb.on_accept_complete)(key, op_key, PJ_INVALID_SOCKET, (pj_status_t)bytes_status); + } + return PJ_SUCCESS; + } + op_rec = op_rec->next; + } + + /* Clear connecting operation. */ + if (key->connecting) { + key->connecting = 0; + ioqueue_remove_from_set2(key->ioqueue, key, WRITEABLE_EVENT | EXCEPTION_EVENT); + } + + pj_ioqueue_unlock_key(key); + + return PJ_EINVALIDOP; +} + +PJ_DEF(pj_status_t) pj_ioqueue_clear_key(pj_ioqueue_key_t *key) +{ + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + pj_ioqueue_lock_key(key); + + /* Reset pending lists */ + pj_list_init(&key->read_list); + pj_list_init(&key->write_list); + pj_list_init(&key->accept_list); + key->connecting = 0; + + /* Remove key from sets */ + ioqueue_remove_from_set2(key->ioqueue, key, READABLE_EVENT | WRITEABLE_EVENT | EXCEPTION_EVENT); + + pj_ioqueue_unlock_key(key); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ioqueue_set_default_concurrency(pj_ioqueue_t *ioqueue, pj_bool_t allow) +{ + PJ_ASSERT_RETURN(ioqueue != NULL, PJ_EINVAL); + ioqueue->cfg.default_concurrency = allow; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ioqueue_set_concurrency(pj_ioqueue_key_t *key, pj_bool_t allow) +{ + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + /* PJ_IOQUEUE_HAS_SAFE_UNREG must be enabled if concurrency is + * disabled. + */ + PJ_ASSERT_RETURN(allow || PJ_IOQUEUE_HAS_SAFE_UNREG, PJ_EINVAL); + + key->allow_concurrent = allow; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ioqueue_lock_key(pj_ioqueue_key_t *key) +{ + if (key->grp_lock) + return pj_grp_lock_acquire(key->grp_lock); + else + return pj_lock_acquire(key->lock); +} + +PJ_DEF(pj_status_t) pj_ioqueue_trylock_key(pj_ioqueue_key_t *key) +{ + if (key->grp_lock) + return pj_grp_lock_tryacquire(key->grp_lock); + else + return pj_lock_tryacquire(key->lock); +} + +PJ_DEF(pj_status_t) pj_ioqueue_unlock_key(pj_ioqueue_key_t *key) +{ + if (key->grp_lock) + return pj_grp_lock_release(key->grp_lock); + else + return pj_lock_release(key->lock); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h new file mode 100755 index 000000000..26c87698b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h @@ -0,0 +1,128 @@ +/* $Id */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* ioqueue_common_abs.h + * + * This file contains private declarations for abstracting various + * event polling/dispatching mechanisms (e.g. select, poll, epoll) + * to the ioqueue. + */ + +#include + +/* + * The select ioqueue relies on socket functions (pj_sock_xxx()) to return + * the correct error code. + */ +#if PJ_RETURN_OS_ERROR(100) != PJ_STATUS_FROM_OS(100) +#error "Proper error reporting must be enabled for ioqueue to work!" +#endif + +struct generic_operation { + PJ_DECL_LIST_MEMBER(struct generic_operation); + pj_ioqueue_operation_e op; +}; + +struct read_operation { + PJ_DECL_LIST_MEMBER(struct read_operation); + pj_ioqueue_operation_e op; + + void *buf; + pj_size_t size; + unsigned flags; + pj_sockaddr_t *rmt_addr; + int *rmt_addrlen; +}; + +struct write_operation { + PJ_DECL_LIST_MEMBER(struct write_operation); + pj_ioqueue_operation_e op; + + char *buf; + pj_size_t size; + pj_ssize_t written; + unsigned flags; + pj_sockaddr_in rmt_addr; + int rmt_addrlen; +}; + +struct accept_operation { + PJ_DECL_LIST_MEMBER(struct accept_operation); + pj_ioqueue_operation_e op; + + pj_sock_t *accept_fd; + pj_sockaddr_t *local_addr; + pj_sockaddr_t *rmt_addr; + int *addrlen; +}; + +union operation_key { + struct generic_operation generic_op; + struct read_operation read; + struct write_operation write; +#if PJ_HAS_TCP + struct accept_operation accept; +#endif +}; + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +#define UNREG_FIELDS \ + unsigned ref_count; \ + pj_bool_t closing; \ + pj_time_val free_time; + +#else +#define UNREG_FIELDS +#endif + +#define DECLARE_COMMON_KEY \ + PJ_DECL_LIST_MEMBER(struct pj_ioqueue_key_t); \ + pj_ioqueue_t *ioqueue; \ + pj_grp_lock_t *grp_lock; \ + pj_lock_t *lock; \ + pj_bool_t inside_callback; \ + pj_bool_t destroy_requested; \ + pj_bool_t allow_concurrent; \ + pj_sock_t fd; \ + int fd_type; \ + void *user_data; \ + pj_ioqueue_callback cb; \ + int connecting; \ + struct read_operation read_list; \ + struct write_operation write_list; \ + struct accept_operation accept_list; \ + UNREG_FIELDS + +#define DECLARE_COMMON_IOQUEUE \ + pj_lock_t *lock; \ + pj_bool_t auto_delete_lock; \ + pj_ioqueue_cfg cfg; + +enum ioqueue_event_type { + NO_EVENT, + READABLE_EVENT = 1, + WRITEABLE_EVENT = 2, + EXCEPTION_EVENT = 4, +}; + +static void ioqueue_add_to_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type); +static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types); +static void ioqueue_remove_from_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type); +static void ioqueue_remove_from_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types); diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c new file mode 100755 index 000000000..1bc8df358 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c @@ -0,0 +1,1019 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * ioqueue_epoll.c + * + * This is the implementation of IOQueue framework using /dev/epoll + * API in _both_ Linux user-mode and kernel-mode. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define epoll_data data.ptr +#define epoll_data_type void * +#define ioctl_val_type unsigned long +#define getsockopt_val_ptr int * +#define os_getsockopt getsockopt +#define os_ioctl ioctl +#define os_read read +#define os_close close +#define os_epoll_create epoll_create +#define os_epoll_ctl epoll_ctl +#define os_epoll_wait epoll_wait + +#define THIS_FILE "ioq_epoll" + +//#define TRACE_(expr) PJ_LOG(3,expr) +#define TRACE_(expr) + +/* Enable this during development to warn against stray events. + * But don't enable this during production, for performance reason. + */ +//#define TRACE_WARN(expr) PJ_LOG(2,expr) +#define TRACE_WARN(expr) + +#ifndef EPOLLEXCLUSIVE +#define EPOLLEXCLUSIVE (1U << 28) +#endif + +enum { IO_MASK = EPOLLIN | EPOLLOUT | EPOLLERR }; + +static char ioq_name[32]; + +/* + * Include common ioqueue abstraction. + */ +#include "ioqueue_common_abs.h" + +/* + * This describes each key. + */ +struct pj_ioqueue_key_t { + DECLARE_COMMON_KEY + struct epoll_event ev; +}; + +struct queue { + pj_ioqueue_key_t *key; + enum ioqueue_event_type event_type; +}; + +/* + * This describes the I/O queue. + */ +struct pj_ioqueue_t { + DECLARE_COMMON_IOQUEUE + + unsigned max, count; + // pj_ioqueue_key_t hlist; + pj_ioqueue_key_t active_list; + int epfd; + // struct epoll_event *events; + // struct queue *queue; + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + pj_mutex_t *ref_cnt_mutex; + pj_ioqueue_key_t closing_list; + pj_ioqueue_key_t free_list; +#endif +}; + +/* Include implementation for common abstraction after we declare + * pj_ioqueue_key_t and pj_ioqueue_t. + */ +#include "ioqueue_common_abs.c" + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue); +#endif + +/* EPOLLEXCLUSIVE or EPOLLONESHOT is reported to cause perm handshake error + * on OpenSSL 1.0.2, so let's disable this when using OpenSSL older than + * version 1.1.0. + */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL) + +#include +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define DISABLE_EXCLUSIVE_ONESHOT "problem with OpenSSL ver <= 1.0.2" +#endif + +#endif + +/* + * Run-time detection of epoll exclusive/oneshot on the machine. + */ +static unsigned detect_epoll_support() +{ + static int epoll_support = -1; + struct epoll_event ev; + int rc; + int epfd = -1, evfd = -1, disable_exclusive = 0; + int tmp_epoll_support = 0, support_exclusive = 0, support_oneshot = 0; + + /* Note: this function should be thread-safe */ + if (epoll_support != -1) + return epoll_support; + +#ifdef DISABLE_EXCLUSIVE_ONESHOT + PJ_LOG(3, (THIS_FILE, "epoll EXCLUSIVE/ONESHOT support disabled, reason: %s", DISABLE_EXCLUSIVE_ONESHOT)); + epoll_support = 0; + return epoll_support; +#endif + + epfd = os_epoll_create(5); + if (epfd < 0) + goto on_error; + + evfd = eventfd(0, 0); + if (evfd < 0) + goto on_error; + + /* + * Choose events that should cause an error on EPOLLEXCLUSIVE enabled + * kernels - specifically the combination of EPOLLONESHOT and + * EPOLLEXCLUSIVE + */ + pj_bzero(&ev, sizeof(ev)); + ev.events = EPOLLIN | EPOLLEXCLUSIVE | EPOLLONESHOT; + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev); + if (rc == 0) { + /* The kernel has accepted our invalid request. Assume it probably + * doesn't know about/support EPOLLEXCLUSIVE. But we assume that + * it may still be able to support EPOLLONESHOT, since this was + * added very long time ago. + */ + disable_exclusive = 1; + rc = epoll_ctl(epfd, EPOLL_CTL_DEL, evfd, &ev); + if (rc != 0) + goto on_error; + + } else if (errno != EINVAL) { + /* Unexpected error */ + goto on_error; + } + + /* Check EPOLLEXCLUSIVE support */ + if (!disable_exclusive) { + ev.events = EPOLLIN | EPOLLEXCLUSIVE; + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev); + if (rc == 0) { + support_exclusive = 1; + rc = epoll_ctl(epfd, EPOLL_CTL_DEL, evfd, &ev); + if (rc != 0) + goto on_error; + } + } + + /* Check EPOLLONESHOT support */ + ev.events = EPOLLIN | EPOLLONESHOT; + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev); + if (rc == 0) { + support_oneshot = 1; + rc = epoll_ctl(epfd, EPOLL_CTL_DEL, evfd, &ev); + if (rc != 0) + goto on_error; + } + + tmp_epoll_support = 0; + if (support_exclusive && !disable_exclusive) + tmp_epoll_support |= PJ_IOQUEUE_EPOLL_EXCLUSIVE; + if (support_oneshot) + tmp_epoll_support |= PJ_IOQUEUE_EPOLL_ONESHOT; + + pj_ansi_snprintf(ioq_name, sizeof(ioq_name), "epoll[0x%x]", tmp_epoll_support); + epoll_support = tmp_epoll_support; + + if (epfd > 0) + os_close(epfd); + if (evfd > 0) + os_close(evfd); + + return epoll_support; + +on_error: + rc = PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + PJ_PERROR(2, (THIS_FILE, rc, "detect_epoll_support() error")); + if (epfd >= 0) + os_close(epfd); + if (evfd >= 0) + os_close(evfd); + return 0; +} + +/* + * pj_ioqueue_name() + */ +PJ_DEF(const char *) pj_ioqueue_name(void) +{ + detect_epoll_support(); + return ioq_name; +} + +/* + * pj_ioqueue_create() + * + * Create epoll ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **p_ioqueue) +{ + return pj_ioqueue_create2(pool, max_fd, NULL, p_ioqueue); +} + +/* + * pj_ioqueue_create2() + * + * Create epoll ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_create2(pj_pool_t *pool, pj_size_t max_fd, const pj_ioqueue_cfg *cfg, pj_ioqueue_t **p_ioqueue) +{ + pj_ioqueue_t *ioqueue; + pj_status_t rc; + pj_lock_t *lock; + const unsigned type_mask = PJ_IOQUEUE_EPOLL_EXCLUSIVE | PJ_IOQUEUE_EPOLL_ONESHOT; + unsigned epoll_support, valid_types; + int i; + + /* Check that arguments are valid. */ + PJ_ASSERT_RETURN(pool != NULL && p_ioqueue != NULL && max_fd > 0, PJ_EINVAL); + + /* Check that size of pj_ioqueue_op_key_t is sufficient */ + PJ_ASSERT_RETURN(sizeof(pj_ioqueue_op_key_t) - sizeof(void *) >= sizeof(union operation_key), PJ_EBUG); + + ioqueue = pj_pool_alloc(pool, sizeof(pj_ioqueue_t)); + + ioqueue_init(ioqueue); + + if (cfg) + pj_memcpy(&ioqueue->cfg, cfg, sizeof(*cfg)); + else { + pj_ioqueue_cfg_default(&ioqueue->cfg); + cfg = &ioqueue->cfg; + } + ioqueue->max = max_fd; + ioqueue->count = 0; + pj_list_init(&ioqueue->active_list); + + /* Adjust/validate epoll type according to supported epoll types. + */ + epoll_support = detect_epoll_support(); + valid_types = epoll_support & cfg->epoll_flags; + /* Note that epoll_flags may be used to specify options other than + * epoll types in the future, hence be careful when clearing the + * bits (only bits related to epoll types should be cleared) + */ + ioqueue->cfg.epoll_flags &= ~type_mask; + if (valid_types & PJ_IOQUEUE_EPOLL_EXCLUSIVE) { + ioqueue->cfg.epoll_flags |= PJ_IOQUEUE_EPOLL_EXCLUSIVE; + } else if (valid_types & PJ_IOQUEUE_EPOLL_ONESHOT) { + ioqueue->cfg.epoll_flags |= PJ_IOQUEUE_EPOLL_ONESHOT; + } else if ((cfg->epoll_flags & type_mask) == 0) { + /* user has disabled both EXCLUSIVE and ONESHOT */ + } else { + /* the requested epoll type is not available */ + return PJ_EINVAL; + } + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* When safe unregistration is used (the default), we pre-create + * all keys and put them in the free list. + */ + + /* Mutex to protect key's reference counter + * We don't want to use key's mutex or ioqueue's mutex because + * that would create deadlock situation in some cases. + */ + rc = pj_mutex_create_simple(pool, NULL, &ioqueue->ref_cnt_mutex); + if (rc != PJ_SUCCESS) + return rc; + + /* Init key list */ + pj_list_init(&ioqueue->free_list); + pj_list_init(&ioqueue->closing_list); + + /* Pre-create all keys according to max_fd */ + for (i = 0; i < max_fd; ++i) { + pj_ioqueue_key_t *key; + + key = PJ_POOL_ALLOC_T(pool, pj_ioqueue_key_t); + key->ref_count = 0; + rc = pj_lock_create_recursive_mutex(pool, NULL, &key->lock); + if (rc != PJ_SUCCESS) { + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + pj_mutex_destroy(ioqueue->ref_cnt_mutex); + return rc; + } + + pj_list_push_back(&ioqueue->free_list, key); + } +#endif + + rc = pj_lock_create_simple_mutex(pool, "ioq%p", &lock); + if (rc != PJ_SUCCESS) + return rc; + + rc = pj_ioqueue_set_lock(ioqueue, lock, PJ_TRUE); + if (rc != PJ_SUCCESS) + return rc; + + ioqueue->epfd = os_epoll_create(max_fd); + if (ioqueue->epfd < 0) { + pj_lock_acquire(ioqueue->lock); + ioqueue_destroy(ioqueue); + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + /*ioqueue->events = pj_pool_calloc(pool, max_fd, sizeof(struct epoll_event)); + PJ_ASSERT_RETURN(ioqueue->events != NULL, PJ_ENOMEM); + + ioqueue->queue = pj_pool_calloc(pool, max_fd, sizeof(struct queue)); + PJ_ASSERT_RETURN(ioqueue->queue != NULL, PJ_ENOMEM); + */ + + PJ_LOG(4, ("pjlib", "epoll I/O Queue created (flags:0x%x, ptr=%p)", ioqueue->cfg.epoll_flags, ioqueue)); + + *p_ioqueue = ioqueue; + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_destroy() + * + * Destroy ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_destroy(pj_ioqueue_t *ioqueue) +{ + pj_ioqueue_key_t *key; + + PJ_ASSERT_RETURN(ioqueue, PJ_EINVAL); + PJ_ASSERT_RETURN(ioqueue->epfd > 0, PJ_EINVALIDOP); + + pj_lock_acquire(ioqueue->lock); + os_close(ioqueue->epfd); + ioqueue->epfd = 0; + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Destroy reference counters */ + key = ioqueue->active_list.next; + while (key != &ioqueue->active_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->closing_list.next; + while (key != &ioqueue->closing_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + pj_mutex_destroy(ioqueue->ref_cnt_mutex); +#endif + return ioqueue_destroy(ioqueue); +} + +/* + * pj_ioqueue_register_sock() + * + * Register a socket to ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock2(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, pj_grp_lock_t *grp_lock, + void *user_data, const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + pj_ioqueue_key_t *key = NULL; + pj_uint32_t value; + int rc; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(pool && ioqueue && sock != PJ_INVALID_SOCKET && cb && p_key, PJ_EINVAL); + + pj_lock_acquire(ioqueue->lock); + + if (ioqueue->count >= ioqueue->max) { + status = PJ_ETOOMANY; + TRACE_((THIS_FILE, "pj_ioqueue_register_sock error: too many files")); + goto on_return; + } + + /* Set socket to nonblocking. */ + value = 1; + if ((rc = os_ioctl(sock, FIONBIO, (ioctl_val_type)&value))) { + TRACE_((THIS_FILE, "pj_ioqueue_register_sock error: ioctl rc=%d", rc)); + status = pj_get_netos_error(); + goto on_return; + } + + /* If safe unregistration (PJ_IOQUEUE_HAS_SAFE_UNREG) is used, get + * the key from the free list. Otherwise allocate a new one. + */ +#if PJ_IOQUEUE_HAS_SAFE_UNREG + + /* Scan closing_keys first to let them come back to free_list */ + scan_closing_keys(ioqueue); + + pj_assert(!pj_list_empty(&ioqueue->free_list)); + if (pj_list_empty(&ioqueue->free_list)) { + status = PJ_ETOOMANY; + goto on_return; + } + + key = ioqueue->free_list.next; + pj_list_erase(key); +#else + /* Create key. */ + key = (pj_ioqueue_key_t *)pj_pool_zalloc(pool, sizeof(pj_ioqueue_key_t)); +#endif + + status = ioqueue_init_key(pool, ioqueue, key, sock, grp_lock, user_data, cb); + if (status != PJ_SUCCESS) { + key = NULL; + goto on_return; + } + pj_bzero(&key->ev, sizeof(key->ev)); + key->ev.epoll_data = (epoll_data_type)key; + key->ev.events = 0; + if (ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_EXCLUSIVE) + key->ev.events |= EPOLLEXCLUSIVE; + else if (ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_ONESHOT) + key->ev.events |= EPOLLONESHOT; + + /* Create key's mutex */ + /* rc = pj_mutex_create_recursive(pool, NULL, &key->mutex); + if (rc != PJ_SUCCESS) { + key = NULL; + goto on_return; + } + */ + /* os_epoll_ctl. */ + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_ADD, sock, &key->ev); + if (rc < 0) { + status = pj_get_os_error(); + pj_lock_destroy(key->lock); + key = NULL; + PJ_PERROR(1, (THIS_FILE, status, "epol_ctl(ADD) error")); + goto on_return; + } + + /* Register */ + pj_list_insert_before(&ioqueue->active_list, key); + ++ioqueue->count; + + // TRACE_((THIS_FILE, "socket registered, count=%d", ioqueue->count)); + +on_return: + if (status != PJ_SUCCESS) { + if (key && key->grp_lock) + pj_grp_lock_dec_ref_dbg(key->grp_lock, "ioqueue", 0); + } + *p_key = key; + pj_lock_release(ioqueue->lock); + + return status; +} + +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, void *user_data, + const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + return pj_ioqueue_register_sock2(pool, ioqueue, sock, NULL, user_data, cb, p_key); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Increment key's reference counter */ +static void increment_counter(pj_ioqueue_key_t *key) +{ + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + ++key->ref_count; + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); +} + +/* Decrement the key's reference counter, and when the counter reach zero, + * destroy the key. + * + * Note: MUST NOT CALL THIS FUNCTION WHILE HOLDING ioqueue's LOCK. + */ +static void decrement_counter(pj_ioqueue_key_t *key) +{ + pj_lock_acquire(key->ioqueue->lock); + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + --key->ref_count; + if (key->ref_count == 0) { + + pj_assert(key->closing == 1); + pj_gettickcount(&key->free_time); + key->free_time.msec += PJ_IOQUEUE_KEY_FREE_DELAY; + pj_time_val_normalize(&key->free_time); + + pj_list_erase(key); + pj_list_push_back(&key->ioqueue->closing_list, key); + } + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); + pj_lock_release(key->ioqueue->lock); +} +#endif + +/* + * pj_ioqueue_unregister() + * + * Unregister handle from ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_unregister(pj_ioqueue_key_t *key) +{ + pj_ioqueue_t *ioqueue; + int status; + + PJ_ASSERT_RETURN(key != NULL, PJ_EINVAL); + + ioqueue = key->ioqueue; + + /* Lock the key to make sure no callback is simultaneously modifying + * the key. We need to lock the key before ioqueue here to prevent + * deadlock. + */ + pj_ioqueue_lock_key(key); + + /* Best effort to avoid double key-unregistration */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_SUCCESS; + } + + /* Also lock ioqueue */ + pj_lock_acquire(ioqueue->lock); + + /* Avoid "negative" ioqueue count */ + if (ioqueue->count > 0) { + --ioqueue->count; + } else { + /* If this happens, very likely there is double unregistration + * of a key. + */ + pj_assert(!"Bad ioqueue count in key unregistration!"); + PJ_LOG(1, (THIS_FILE, "Bad ioqueue count in key unregistration!")); + } + +#if !PJ_IOQUEUE_HAS_SAFE_UNREG + pj_list_erase(key); +#endif + + /* Note: although event argument is ignored on EPOLL_CTL_DEL, we still + * need to clear the IO flags to be safe (in case another thread is run + * after we exit this function). + */ + key->ev.events &= ~IO_MASK; + status = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_DEL, key->fd, &key->ev); + if (status != 0) { + status = pj_get_os_error(); + PJ_PERROR(2, (THIS_FILE, status, "Ignoring pj_ioqueue_unregister error: os_epoll_ctl")); + /* From epoll doc: "Closing a file descriptor cause it to be + * removed from all epoll interest lists". So we should just + * proceed instead of returning failure here. + */ + // pj_lock_release(ioqueue->lock); + // pj_ioqueue_unlock_key(key); + // return rc; + } + + /* Destroy the key. */ + pj_sock_close(key->fd); + + pj_lock_release(ioqueue->lock); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Mark key is closing. */ + key->closing = 1; + + /* Decrement counter. */ + decrement_counter(key); + + /* Done. */ + if (key->grp_lock) { + /* just dec_ref and unlock. we will set grp_lock to NULL + * elsewhere */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } +#else + if (key->grp_lock) { + /* set grp_lock to NULL and unlock */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } + + pj_lock_destroy(key->lock); +#endif + + return PJ_SUCCESS; +} + +static void update_epoll_event_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, pj_uint32_t events) +{ + int rc; + /* From epoll_ctl(2): + * EPOLLEXCLUSIVE may be used only in an EPOLL_CTL_ADD operation; + * attempts to employ it with EPOLL_CTL_MOD yield an error. + */ + if (key->ev.events & EPOLLEXCLUSIVE) { + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_DEL, key->fd, &key->ev); + key->ev.events = events; + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_ADD, key->fd, &key->ev); + } else { + key->ev.events = events; + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_MOD, key->fd, &key->ev); + } + + if (rc != 0) { + pj_status_t status = pj_get_os_error(); + PJ_PERROR(1, (THIS_FILE, status, "epol_ctl(MOD) error (events=0x%x)", events)); + } +} + +/* ioqueue_remove_from_set() + * This function is called from ioqueue_dispatch_event() to instruct + * the ioqueue to remove the specified descriptor from ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_remove_from_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_remove_from_set2(ioqueue, key, event_type); +} + +static void ioqueue_remove_from_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_uint32_t events = key->ev.events; + + if (event_types & READABLE_EVENT) + events &= ~EPOLLIN; + if (event_types & WRITEABLE_EVENT) + events &= ~EPOLLOUT; + + /* Note that although EPOLLERR is removed, epoll will still report + * EPOLLERR events to us and there is no way to disable it. But we + * still remove it from "events" anyway to make our interest correct + * in our own record. + */ + if (event_types & EXCEPTION_EVENT) + events &= ~EPOLLERR; + + if (events != key->ev.events) + update_epoll_event_set(ioqueue, key, events); +} + +/* + * ioqueue_add_to_set() + * This function is called from pj_ioqueue_recv(), pj_ioqueue_send() etc + * to instruct the ioqueue to add the specified handle to ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_add_to_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_add_to_set2(ioqueue, key, event_type); +} + +static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_uint32_t events = key->ev.events; + + if (event_types & READABLE_EVENT) + events |= EPOLLIN; + if (event_types & WRITEABLE_EVENT) + events |= EPOLLOUT; + if (event_types & EXCEPTION_EVENT) + events |= EPOLLERR; + + if (events != key->ev.events) + update_epoll_event_set(ioqueue, key, events); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue) +{ + pj_time_val now; + pj_ioqueue_key_t *h; + + pj_gettickcount(&now); + h = ioqueue->closing_list.next; + while (h != &ioqueue->closing_list) { + pj_ioqueue_key_t *next = h->next; + + pj_assert(h->closing != 0); + + if (PJ_TIME_VAL_GTE(now, h->free_time)) { + pj_list_erase(h); + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // h->grp_lock = NULL; + pj_list_push_back(&ioqueue->free_list, h); + } + h = next; + } +} +#endif + +/* + * pj_ioqueue_poll() + * + */ +PJ_DEF(int) pj_ioqueue_poll(pj_ioqueue_t *ioqueue, const pj_time_val *timeout) +{ + int i, count, event_cnt, processed_cnt; + int msec; + // struct epoll_event *events = ioqueue->events; + // struct queue *queue = ioqueue->queue; + enum { MAX_EVENTS = PJ_IOQUEUE_MAX_CAND_EVENTS }; + struct epoll_event events[MAX_EVENTS]; + struct queue queue[MAX_EVENTS]; + pj_timestamp t1, t2; + + PJ_CHECK_STACK(); + + msec = timeout ? PJ_TIME_VAL_MSEC(*timeout) : 9000; + + TRACE_((THIS_FILE, "start os_epoll_wait, msec=%d", msec)); + pj_get_timestamp(&t1); + + // count = os_epoll_wait( ioqueue->epfd, events, ioqueue->max, msec); + count = os_epoll_wait(ioqueue->epfd, events, MAX_EVENTS, msec); + if (count == 0) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Check the closing keys only when there's no activity and when there are + * pending closing keys. + */ + if (count == 0 && !pj_list_empty(&ioqueue->closing_list)) { + pj_lock_acquire(ioqueue->lock); + scan_closing_keys(ioqueue); + pj_lock_release(ioqueue->lock); + } +#endif + TRACE_((THIS_FILE, " os_epoll_wait timed out")); + return count; + } else if (count < 0) { + TRACE_((THIS_FILE, " os_epoll_wait error")); + return -pj_get_netos_error(); + } + + pj_get_timestamp(&t2); + TRACE_((THIS_FILE, " os_epoll_wait returns %d, time=%d usec", count, pj_elapsed_usec(&t1, &t2))); + + /* Lock ioqueue. */ + pj_lock_acquire(ioqueue->lock); + + for (event_cnt = 0, i = 0; i < count; ++i) { + pj_ioqueue_key_t *h = (pj_ioqueue_key_t *)(epoll_data_type)events[i].epoll_data; + + TRACE_((THIS_FILE, " event %d: events=%x", i, events[i].events)); + + /* + * Check readability. + */ + if ((events[i].events & EPOLLIN) && (key_has_pending_read(h) || key_has_pending_accept(h)) && !IS_CLOSING(h)) { + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = READABLE_EVENT; + ++event_cnt; + continue; + } + + /* + * Check for writeability. + */ + if ((events[i].events & EPOLLOUT) && key_has_pending_write(h) && !IS_CLOSING(h)) { + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = WRITEABLE_EVENT; + ++event_cnt; + continue; + } + +#if PJ_HAS_TCP + /* + * Check for completion of connect() operation. + */ + if ((events[i].events & EPOLLOUT) && (h->connecting) && !IS_CLOSING(h)) { + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = WRITEABLE_EVENT; + ++event_cnt; + continue; + } +#endif /* PJ_HAS_TCP */ + + /* + * Check for error condition. + */ + if ((events[i].events & EPOLLERR) && !IS_CLOSING(h)) { + /* + * We need to handle this exception event. If it's related to us + * connecting, report it as such. If not, just report it as a + * read event and the higher layers will handle it. + */ + if (h->connecting) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = EXCEPTION_EVENT; + ++event_cnt; + } else if (key_has_pending_read(h) || key_has_pending_accept(h)) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = READABLE_EVENT; + ++event_cnt; + } + continue; + } + + if (ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_ONESHOT) { + /* We are not processing this event, but we still need to rearm + * to receive future events. + */ + if (!IS_CLOSING(h)) + update_epoll_event_set(ioqueue, h, h->ev.events); + } + + /* There are some innocent cases where this might happen: + * 1. TCP outgoing connection failure. Although app has handled the + * failure event, epoll will keep reporting EPOLLHUP until the + * socket is unregistered. + * 2. Thread A and B are woken up for a single EPOLLIN, (or when + * EPOLLEXCLUSIVE is used, two packets arrives almost simultaneously, + * only single pending recv() was submitted. This also causes both + * threads to be woken up. This scenario can be observed in ioq_reg.c + * test) + * - Thread A is processing the event + * - Thread B waits for ioqueue lock + * - Thread A releases the ioqueue lock, acquires key lock, remove + * pending recv() from list, release key lock (because concurrency + * is enabled). + * - Thread B resumes, it finds no pending recv() op. + * 3. Other scenarios involving TCP in normal operation using plain or + * EPOLLEXCLUSIVE (didn't happen with ONESHOT), for both EPOLLIN + * and EPOLLOUT events. Not sure what, but it happens only + * sporadically, so probably it's fine. This is reproducible with + * "tcp (multithreads)" test in ioq_stress_test.c. + */ + TRACE_WARN((THIS_FILE, " UNHANDLED event %d: events=0x%x, h=%p", i, events[i].events, h)); + } + for (i = 0; i < event_cnt; ++i) { + if (queue[i].key->grp_lock) + pj_grp_lock_add_ref_dbg(queue[i].key->grp_lock, "ioqueue", 0); + } + + PJ_RACE_ME(5); + + pj_lock_release(ioqueue->lock); + + PJ_RACE_ME(5); + + processed_cnt = 0; + + /* Now process the events. */ + for (i = 0; i < event_cnt; ++i) { + /* Just do not exceed PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL */ + if (processed_cnt < PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL) { + pj_bool_t event_done = PJ_FALSE; + switch (queue[i].event_type) { + case READABLE_EVENT: + event_done = ioqueue_dispatch_read_event(ioqueue, queue[i].key); + + break; + case WRITEABLE_EVENT: + event_done = ioqueue_dispatch_write_event(ioqueue, queue[i].key); + + break; + case EXCEPTION_EVENT: + event_done = ioqueue_dispatch_exception_event(ioqueue, queue[i].key); + break; + case NO_EVENT: + pj_assert(!"Invalid event!"); + break; + } + if (event_done) { + ++processed_cnt; + } + } + + /* Re-arm ONESHOT as long as there are pending requests. This is + * necessary to deal with this case: + * - thread A and B are calling ioqueue_recv() + * - packet arrives, thread A is processing + * - but thread A doesn't call ioqueue_recv() again + * - if we don't rearm here, thread B will never get the event. + * + * On the other hand, if thread A calls ioqueue_recv() again above, + * this will result in double epoll_ctl() calls. This should be okay, + * albeit inefficient. We err on the safe side. + */ + if ((ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_ONESHOT) && (queue[i].key->ev.events & IO_MASK)) { + pj_ioqueue_lock_key(queue[i].key); + update_epoll_event_set(ioqueue, queue[i].key, queue[i].key->ev.events); + pj_ioqueue_unlock_key(queue[i].key); + } + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + decrement_counter(queue[i].key); +#endif + + if (queue[i].key->grp_lock) + pj_grp_lock_dec_ref_dbg(queue[i].key->grp_lock, "ioqueue", 0); + } + + /* Special case: + * When epoll returns > 0 but event_cnt, the number of events + * we want to process, is zero. + * There are several possibilities this can happen, see "UNHANDLED event" + * log message above. + */ + if (count > 0 && !event_cnt && msec > 0) { + /* We need to sleep in order to avoid busy polling, such + * as in the case of the thread that doesn't process + * the event as explained above. + * Limit the duration of the sleep, as doing pj_thread_sleep() for + * a long time is very inefficient. The main objective here is just + * to avoid busy loop. + */ + int delay = msec - pj_elapsed_usec(&t1, &t2) / 1000; + if (delay > 10) + delay = 10; + if (delay > 0) + pj_thread_sleep(delay); + } + + TRACE_((THIS_FILE, " poll: count=%d events=%d processed=%d", count, event_cnt, processed_cnt)); + + pj_get_timestamp(&t1); + TRACE_((THIS_FILE, "ioqueue_poll() returns %d, time=%d usec", processed_cnt, pj_elapsed_usec(&t2, &t1))); + + return processed_cnt; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c new file mode 100755 index 000000000..0c82f98df --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c @@ -0,0 +1,1061 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * sock_select.c + * + * This is the implementation of IOQueue using pj_sock_select(). + * It runs anywhere where pj_sock_select() is available (currently + * Win32, Linux, Linux kernel, etc.). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Now that we have access to OS'es , lets check again that + * PJ_IOQUEUE_MAX_HANDLES is not greater than FD_SETSIZE + */ +#if PJ_IOQUEUE_MAX_HANDLES > FD_SETSIZE +#error "PJ_IOQUEUE_MAX_HANDLES cannot be greater than FD_SETSIZE" +#endif + +/* + * Include declaration from common abstraction. + */ +#include "ioqueue_common_abs.h" + +/* + * ISSUES with ioqueue_select() + * + * EAGAIN/EWOULDBLOCK error in recv(): + * - when multiple threads are working with the ioqueue, application + * may receive EAGAIN or EWOULDBLOCK in the receive callback. + * This error happens because more than one thread is watching for + * the same descriptor set, so when all of them call recv() or recvfrom() + * simultaneously, only one will succeed and the rest will get the error. + * + */ +#define THIS_FILE "ioq_select" + +/* + * The select ioqueue relies on socket functions (pj_sock_xxx()) to return + * the correct error code. + */ +#if PJ_RETURN_OS_ERROR(100) != PJ_STATUS_FROM_OS(100) +#error "Error reporting must be enabled for this function to work!" +#endif + +/* + * During debugging build, VALIDATE_FD_SET is set. + * This will check the validity of the fd_sets. + */ +/* +#if defined(PJ_DEBUG) && PJ_DEBUG != 0 +# define VALIDATE_FD_SET 1 +#else +# define VALIDATE_FD_SET 0 +#endif +*/ +#define VALIDATE_FD_SET 0 + +#if 0 +#define TRACE__(args) PJ_LOG(3, args) +#else +#define TRACE__(args) +#endif + +/* + * This describes each key. + */ +struct pj_ioqueue_key_t { + DECLARE_COMMON_KEY +}; + +/* + * This describes the I/O queue itself. + */ +struct pj_ioqueue_t { + DECLARE_COMMON_IOQUEUE + + unsigned max, count; /* Max and current key count */ + int nfds; /* The largest fd value (for select)*/ + pj_ioqueue_key_t active_list; /* List of active keys. */ + pj_fd_set_t rfdset; + pj_fd_set_t wfdset; +#if PJ_HAS_TCP + pj_fd_set_t xfdset; +#endif + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + pj_mutex_t *ref_cnt_mutex; + pj_ioqueue_key_t closing_list; + pj_ioqueue_key_t free_list; +#endif +}; + +/* Proto */ +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +static pj_status_t replace_udp_sock(pj_ioqueue_key_t *h); +#endif + +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) +/* Call SSL Network framework poll */ +pj_status_t ssl_network_event_poll(); +#endif + +/* Include implementation for common abstraction after we declare + * pj_ioqueue_key_t and pj_ioqueue_t. + */ +#include "ioqueue_common_abs.c" + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue); +#endif + +/* + * pj_ioqueue_name() + */ +PJ_DEF(const char *) pj_ioqueue_name(void) +{ + return "select"; +} + +/* + * Scan the socket descriptor sets for the largest descriptor. + * This value is needed by select(). + */ +#if defined(PJ_SELECT_NEEDS_NFDS) && PJ_SELECT_NEEDS_NFDS != 0 +static void rescan_fdset(pj_ioqueue_t *ioqueue) +{ + pj_ioqueue_key_t *key = ioqueue->active_list.next; + int max = 0; + + while (key != &ioqueue->active_list) { + if (key->fd > max) + max = key->fd; + key = key->next; + } + + ioqueue->nfds = max; +} +#else +static void rescan_fdset(pj_ioqueue_t *ioqueue) +{ + ioqueue->nfds = FD_SETSIZE - 1; +} +#endif + +/* + * pj_ioqueue_create() + * + * Create select ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **p_ioqueue) +{ + return pj_ioqueue_create2(pool, max_fd, NULL, p_ioqueue); +} + +/* + * pj_ioqueue_create2() + * + * Create select ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_create2(pj_pool_t *pool, pj_size_t max_fd, const pj_ioqueue_cfg *cfg, pj_ioqueue_t **p_ioqueue) +{ + pj_ioqueue_t *ioqueue; + pj_lock_t *lock; + unsigned i; + pj_status_t rc; + + /* Check that arguments are valid. */ + PJ_ASSERT_RETURN(pool != NULL && p_ioqueue != NULL && max_fd > 0 && max_fd <= PJ_IOQUEUE_MAX_HANDLES, PJ_EINVAL); + + /* Check that size of pj_ioqueue_op_key_t is sufficient */ + PJ_ASSERT_RETURN(sizeof(pj_ioqueue_op_key_t) - sizeof(void *) >= sizeof(union operation_key), PJ_EBUG); + + /* Create and init common ioqueue stuffs */ + ioqueue = PJ_POOL_ALLOC_T(pool, pj_ioqueue_t); + ioqueue_init(ioqueue); + + if (cfg) + pj_memcpy(&ioqueue->cfg, cfg, sizeof(*cfg)); + else + pj_ioqueue_cfg_default(&ioqueue->cfg); + ioqueue->max = (unsigned)max_fd; + ioqueue->count = 0; + PJ_FD_ZERO(&ioqueue->rfdset); + PJ_FD_ZERO(&ioqueue->wfdset); +#if PJ_HAS_TCP + PJ_FD_ZERO(&ioqueue->xfdset); +#endif + pj_list_init(&ioqueue->active_list); + + rescan_fdset(ioqueue); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* When safe unregistration is used (the default), we pre-create + * all keys and put them in the free list. + */ + + /* Mutex to protect key's reference counter + * We don't want to use key's mutex or ioqueue's mutex because + * that would create deadlock situation in some cases. + */ + rc = pj_mutex_create_simple(pool, NULL, &ioqueue->ref_cnt_mutex); + if (rc != PJ_SUCCESS) + return rc; + + /* Init key list */ + pj_list_init(&ioqueue->free_list); + pj_list_init(&ioqueue->closing_list); + + /* Pre-create all keys according to max_fd */ + for (i = 0; i < max_fd; ++i) { + pj_ioqueue_key_t *key; + + key = PJ_POOL_ALLOC_T(pool, pj_ioqueue_key_t); + key->ref_count = 0; + rc = pj_lock_create_recursive_mutex(pool, NULL, &key->lock); + if (rc != PJ_SUCCESS) { + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + pj_mutex_destroy(ioqueue->ref_cnt_mutex); + return rc; + } + + pj_list_push_back(&ioqueue->free_list, key); + } +#endif + + /* Create and init ioqueue mutex */ + rc = pj_lock_create_simple_mutex(pool, "ioq%p", &lock); + if (rc != PJ_SUCCESS) + return rc; + + rc = pj_ioqueue_set_lock(ioqueue, lock, PJ_TRUE); + if (rc != PJ_SUCCESS) + return rc; + + PJ_LOG(4, ("pjlib", "select() I/O Queue created (%p)", ioqueue)); + + *p_ioqueue = ioqueue; + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_destroy() + * + * Destroy ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_destroy(pj_ioqueue_t *ioqueue) +{ + pj_ioqueue_key_t *key; + + PJ_ASSERT_RETURN(ioqueue, PJ_EINVAL); + + pj_lock_acquire(ioqueue->lock); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Destroy reference counters */ + key = ioqueue->active_list.next; + while (key != &ioqueue->active_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->closing_list.next; + while (key != &ioqueue->closing_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + pj_mutex_destroy(ioqueue->ref_cnt_mutex); +#endif + + return ioqueue_destroy(ioqueue); +} + +/* + * pj_ioqueue_register_sock() + * + * Register socket handle to ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock2(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, pj_grp_lock_t *grp_lock, + void *user_data, const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + pj_ioqueue_key_t *key = NULL; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + u_long value; +#else + pj_uint32_t value; +#endif + pj_status_t rc = PJ_SUCCESS; + + PJ_ASSERT_RETURN(pool && ioqueue && sock != PJ_INVALID_SOCKET && cb && p_key, PJ_EINVAL); + + /* On platforms with fd_set containing fd bitmap such as *nix family, + * avoid potential memory corruption caused by select() when given + * an fd that is higher than FD_SETSIZE. + */ + if (sizeof(fd_set) < FD_SETSIZE && sock >= FD_SETSIZE) { + PJ_LOG(4, ("pjlib", + "Failed to register socket to ioqueue because " + "socket fd is too big (fd=%d/FD_SETSIZE=%d)", + sock, FD_SETSIZE)); + return PJ_ETOOBIG; + } + + pj_lock_acquire(ioqueue->lock); + + if (ioqueue->count >= ioqueue->max) { + rc = PJ_ETOOMANY; + goto on_return; + } + + /* If safe unregistration (PJ_IOQUEUE_HAS_SAFE_UNREG) is used, get + * the key from the free list. Otherwise allocate a new one. + */ +#if PJ_IOQUEUE_HAS_SAFE_UNREG + + /* Scan closing_keys first to let them come back to free_list */ + scan_closing_keys(ioqueue); + + pj_assert(!pj_list_empty(&ioqueue->free_list)); + if (pj_list_empty(&ioqueue->free_list)) { + rc = PJ_ETOOMANY; + goto on_return; + } + + key = ioqueue->free_list.next; + pj_list_erase(key); +#else + key = (pj_ioqueue_key_t *)pj_pool_zalloc(pool, sizeof(pj_ioqueue_key_t)); +#endif + + rc = ioqueue_init_key(pool, ioqueue, key, sock, grp_lock, user_data, cb); + if (rc != PJ_SUCCESS) { + key = NULL; + goto on_return; + } + + /* Set socket to nonblocking. */ + value = 1; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + if (ioctlsocket(sock, FIONBIO, &value)) { +#else + if (ioctl(sock, FIONBIO, &value)) { +#endif + rc = pj_get_netos_error(); + goto on_return; + } + + /* Put in active list. */ + pj_list_insert_before(&ioqueue->active_list, key); + ++ioqueue->count; + + /* Rescan fdset to get max descriptor */ + rescan_fdset(ioqueue); + +on_return: + /* On error, socket may be left in non-blocking mode. */ + if (rc != PJ_SUCCESS) { + if (key && key->grp_lock) + pj_grp_lock_dec_ref_dbg(key->grp_lock, "ioqueue", 0); + } + *p_key = key; + pj_lock_release(ioqueue->lock); + + return rc; +} + +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, void *user_data, + const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + return pj_ioqueue_register_sock2(pool, ioqueue, sock, NULL, user_data, cb, p_key); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Increment key's reference counter */ +static void increment_counter(pj_ioqueue_key_t *key) +{ + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + ++key->ref_count; + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); +} + +/* Decrement the key's reference counter, and when the counter reach zero, + * destroy the key. + * + * Note: MUST NOT CALL THIS FUNCTION WHILE HOLDING ioqueue's LOCK. + */ +static void decrement_counter(pj_ioqueue_key_t *key) +{ + pj_lock_acquire(key->ioqueue->lock); + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + --key->ref_count; + if (key->ref_count == 0) { + + pj_assert(key->closing == 1); + pj_gettickcount(&key->free_time); + key->free_time.msec += PJ_IOQUEUE_KEY_FREE_DELAY; + pj_time_val_normalize(&key->free_time); + + pj_list_erase(key); + pj_list_push_back(&key->ioqueue->closing_list, key); + /* Rescan fdset to get max descriptor */ + rescan_fdset(key->ioqueue); + } + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); + pj_lock_release(key->ioqueue->lock); +} +#endif + +/* + * pj_ioqueue_unregister() + * + * Unregister handle from ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_unregister(pj_ioqueue_key_t *key) +{ + pj_ioqueue_t *ioqueue; + + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + ioqueue = key->ioqueue; + + /* Lock the key to make sure no callback is simultaneously modifying + * the key. We need to lock the key before ioqueue here to prevent + * deadlock. + */ + pj_ioqueue_lock_key(key); + + /* Best effort to avoid double key-unregistration */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_SUCCESS; + } + + /* Also lock ioqueue */ + pj_lock_acquire(ioqueue->lock); + + /* Avoid "negative" ioqueue count */ + if (ioqueue->count > 0) { + --ioqueue->count; + } else { + /* If this happens, very likely there is double unregistration + * of a key. + */ + pj_assert(!"Bad ioqueue count in key unregistration!"); + PJ_LOG(1, (THIS_FILE, "Bad ioqueue count in key unregistration!")); + } + +#if !PJ_IOQUEUE_HAS_SAFE_UNREG + /* Ticket #520, key will be erased more than once */ + pj_list_erase(key); +#endif + + /* Remove socket from sets and close socket. */ + if (key->fd != PJ_INVALID_SOCKET) { + PJ_FD_CLR(key->fd, &ioqueue->rfdset); + PJ_FD_CLR(key->fd, &ioqueue->wfdset); +#if PJ_HAS_TCP + PJ_FD_CLR(key->fd, &ioqueue->xfdset); +#endif + + pj_sock_close(key->fd); + key->fd = PJ_INVALID_SOCKET; + } + + /* Clear callback */ + key->cb.on_accept_complete = NULL; + key->cb.on_connect_complete = NULL; + key->cb.on_read_complete = NULL; + key->cb.on_write_complete = NULL; + + /* Must release ioqueue lock first before decrementing counter, to + * prevent deadlock. + */ + pj_lock_release(ioqueue->lock); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Mark key is closing. */ + key->closing = 1; + + /* Decrement counter. */ + decrement_counter(key); + + /* Done. */ + if (key->grp_lock) { + /* just dec_ref and unlock. we will set grp_lock to NULL + * elsewhere */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } +#else + if (key->grp_lock) { + /* set grp_lock to NULL and unlock */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } + + pj_lock_destroy(key->lock); +#endif + + return PJ_SUCCESS; +} + +/* This supposed to check whether the fd_set values are consistent + * with the operation currently set in each key. + */ +#if VALIDATE_FD_SET +static void validate_sets(const pj_ioqueue_t *ioqueue, const pj_fd_set_t *rfdset, const pj_fd_set_t *wfdset, + const pj_fd_set_t *xfdset) +{ + pj_ioqueue_key_t *key; + + /* + * This basicly would not work anymore. + * We need to lock key before performing the check, but we can't do + * so because we're holding ioqueue mutex. If we acquire key's mutex + * now, the will cause deadlock. + */ + pj_assert(0); + + key = ioqueue->active_list.next; + while (key != &ioqueue->active_list) { + if (!pj_list_empty(&key->read_list) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + || !pj_list_empty(&key->accept_list) +#endif + ) { + pj_assert(PJ_FD_ISSET(key->fd, rfdset)); + } else { + pj_assert(PJ_FD_ISSET(key->fd, rfdset) == 0); + } + if (!pj_list_empty(&key->write_list) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + || key->connecting +#endif + ) { + pj_assert(PJ_FD_ISSET(key->fd, wfdset)); + } else { + pj_assert(PJ_FD_ISSET(key->fd, wfdset) == 0); + } +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (key->connecting) { + pj_assert(PJ_FD_ISSET(key->fd, xfdset)); + } else { + pj_assert(PJ_FD_ISSET(key->fd, xfdset) == 0); + } +#endif /* PJ_HAS_TCP */ + + key = key->next; + } +} +#endif /* VALIDATE_FD_SET */ + +/* ioqueue_remove_from_set() + * This function is called from ioqueue_dispatch_event() to instruct + * the ioqueue to remove the specified descriptor from ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_remove_from_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_remove_from_set2(ioqueue, key, event_type); +} + +static void ioqueue_remove_from_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_lock_acquire(ioqueue->lock); + + if (event_types & READABLE_EVENT) + PJ_FD_CLR((pj_sock_t)key->fd, &ioqueue->rfdset); + if (event_types & WRITEABLE_EVENT) + PJ_FD_CLR((pj_sock_t)key->fd, &ioqueue->wfdset); +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (event_types & EXCEPTION_EVENT) + PJ_FD_CLR((pj_sock_t)key->fd, &ioqueue->xfdset); +#endif + + pj_lock_release(ioqueue->lock); +} + +/* + * ioqueue_add_to_set() + * This function is called from pj_ioqueue_recv(), pj_ioqueue_send() etc + * to instruct the ioqueue to add the specified handle to ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_add_to_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_add_to_set2(ioqueue, key, event_type); +} + +static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_lock_acquire(ioqueue->lock); + + if (event_types & READABLE_EVENT) + PJ_FD_SET((pj_sock_t)key->fd, &ioqueue->rfdset); + if (event_types & WRITEABLE_EVENT) + PJ_FD_SET((pj_sock_t)key->fd, &ioqueue->wfdset); +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (event_types & EXCEPTION_EVENT) + PJ_FD_SET((pj_sock_t)key->fd, &ioqueue->xfdset); +#endif + + pj_lock_release(ioqueue->lock); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue) +{ + pj_time_val now; + pj_ioqueue_key_t *h; + + pj_gettickcount(&now); + h = ioqueue->closing_list.next; + while (h != &ioqueue->closing_list) { + pj_ioqueue_key_t *next = h->next; + + pj_assert(h->closing != 0); + + if (PJ_TIME_VAL_GTE(now, h->free_time)) { + pj_list_erase(h); + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // h->grp_lock = NULL; + pj_list_push_back(&ioqueue->free_list, h); + } + h = next; + } +} +#endif + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +static pj_status_t replace_udp_sock(pj_ioqueue_key_t *h) +{ + enum flags { HAS_PEER_ADDR = 1, HAS_QOS = 2 }; + pj_sock_t old_sock, new_sock = PJ_INVALID_SOCKET; + pj_sockaddr local_addr, rem_addr; + int val, addr_len; + pj_fd_set_t *fds[3]; + unsigned i, fds_cnt, flags = 0; + pj_qos_params qos_params; + unsigned msec; + pj_status_t status = PJ_EUNKNOWN; + + pj_lock_acquire(h->ioqueue->lock); + + old_sock = h->fd; + + fds_cnt = 0; + fds[fds_cnt++] = &h->ioqueue->rfdset; + fds[fds_cnt++] = &h->ioqueue->wfdset; +#if PJ_HAS_TCP + fds[fds_cnt++] = &h->ioqueue->xfdset; +#endif + + /* Can only replace UDP socket */ + pj_assert(h->fd_type == pj_SOCK_DGRAM()); + + PJ_LOG(4, (THIS_FILE, "Attempting to replace UDP socket %d", old_sock)); + + for (msec = 20; (msec < 1000 && status != PJ_SUCCESS); msec < 1000 ? msec = msec * 2 : 1000) { + if (msec > 20) { + PJ_LOG(4, (THIS_FILE, "Retry to replace UDP socket %d", old_sock)); + pj_thread_sleep(msec); + } + + if (old_sock != PJ_INVALID_SOCKET) { + /* Investigate the old socket */ + addr_len = sizeof(local_addr); + status = pj_sock_getsockname(old_sock, &local_addr, &addr_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error get socket name")); + continue; + } + + addr_len = sizeof(rem_addr); + status = pj_sock_getpeername(old_sock, &rem_addr, &addr_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error get peer name")); + } else { + flags |= HAS_PEER_ADDR; + } + + status = pj_sock_get_qos_params(old_sock, &qos_params); + if (status == PJ_STATUS_FROM_OS(EBADF) || status == PJ_STATUS_FROM_OS(EINVAL)) { + PJ_PERROR(5, (THIS_FILE, status, "Error get qos param")); + continue; + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error get qos param")); + } else { + flags |= HAS_QOS; + } + + /* We're done with the old socket, close it otherwise we'll get + * error in bind() + */ + status = pj_sock_close(old_sock); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error closing socket")); + } + + old_sock = PJ_INVALID_SOCKET; + } + + /* Prepare the new socket */ + status = pj_sock_socket(local_addr.addr.sa_family, PJ_SOCK_DGRAM, 0, &new_sock); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error create socket")); + continue; + } + + /* Even after the socket is closed, we'll still get "Address in use" + * errors, so force it with SO_REUSEADDR + */ + val = 1; + status = pj_sock_setsockopt(new_sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (status == PJ_STATUS_FROM_OS(EBADF) || status == PJ_STATUS_FROM_OS(EINVAL)) { + PJ_PERROR(5, (THIS_FILE, status, "Error set socket option")); + continue; + } + + /* The loop is silly, but what else can we do? */ + addr_len = pj_sockaddr_get_len(&local_addr); + for (msec = 20; msec < 1000; msec < 1000 ? msec = msec * 2 : 1000) { + status = pj_sock_bind(new_sock, &local_addr, addr_len); + if (status != PJ_STATUS_FROM_OS(EADDRINUSE)) + break; + PJ_LOG(4, (THIS_FILE, "Address is still in use, retrying..")); + pj_thread_sleep(msec); + } + + if (status != PJ_SUCCESS) + continue; + + if (flags & HAS_QOS) { + status = pj_sock_set_qos_params(new_sock, &qos_params); + if (status == PJ_STATUS_FROM_OS(EINVAL)) { + PJ_PERROR(5, (THIS_FILE, status, "Error set qos param")); + continue; + } + } + + if (flags & HAS_PEER_ADDR) { + status = pj_sock_connect(new_sock, &rem_addr, addr_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error connect socket")); + continue; + } + } + } + + if (status != PJ_SUCCESS) + goto on_error; + + /* Set socket to nonblocking. */ + val = 1; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + if (ioctlsocket(new_sock, FIONBIO, &val)) { +#else + if (ioctl(new_sock, FIONBIO, &val)) { +#endif + status = pj_get_netos_error(); + goto on_error; + } + + /* Replace the occurrence of old socket with new socket in the + * fd sets. + */ + for (i = 0; i < fds_cnt; ++i) { + if (PJ_FD_ISSET(h->fd, fds[i])) { + PJ_FD_CLR(h->fd, fds[i]); + PJ_FD_SET(new_sock, fds[i]); + } + } + + /* And finally replace the fd in the key */ + h->fd = new_sock; + + PJ_LOG(4, (THIS_FILE, "UDP has been replaced successfully!")); + + pj_lock_release(h->ioqueue->lock); + + return PJ_SUCCESS; + +on_error: + if (new_sock != PJ_INVALID_SOCKET) + pj_sock_close(new_sock); + if (old_sock != PJ_INVALID_SOCKET) + pj_sock_close(old_sock); + + /* Clear the occurrence of old socket in the fd sets. */ + for (i = 0; i < fds_cnt; ++i) { + if (PJ_FD_ISSET(h->fd, fds[i])) { + PJ_FD_CLR(h->fd, fds[i]); + } + } + + h->fd = PJ_INVALID_SOCKET; + PJ_PERROR(1, (THIS_FILE, status, "Error replacing socket %d", old_sock)); + pj_lock_release(h->ioqueue->lock); + return PJ_ESOCKETSTOP; +} +#endif + +/* + * pj_ioqueue_poll() + * + * Few things worth written: + * + * - we used to do only one callback called per poll, but it didn't go + * very well. The reason is because on some situation, the write + * callback gets called all the time, thus doesn't give the read + * callback to get called. This happens, for example, when user + * submit write operation inside the write callback. + * As the result, we changed the behaviour so that now multiple + * callbacks are called in a single poll. It should be fast too, + * just that we need to be carefull with the ioqueue data structs. + * + * - to guarantee preemptiveness etc, the poll function must strictly + * work on fd_set copy of the ioqueue (not the original one). + */ +PJ_DEF(int) pj_ioqueue_poll(pj_ioqueue_t *ioqueue, const pj_time_val *timeout) +{ + pj_fd_set_t rfdset, wfdset, xfdset; + int nfds; + int i, count, event_cnt, processed_cnt; + pj_ioqueue_key_t *h; + enum { MAX_EVENTS = PJ_IOQUEUE_MAX_CAND_EVENTS }; + struct event { + pj_ioqueue_key_t *key; + enum ioqueue_event_type event_type; + } event[MAX_EVENTS]; + + PJ_ASSERT_RETURN(ioqueue, -PJ_EINVAL); + +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) + /* Call SSL Network framework event poll */ + ssl_network_event_poll(); +#endif + + /* Lock ioqueue before making fd_set copies */ + pj_lock_acquire(ioqueue->lock); + + /* We will only do select() when there are sockets to be polled. + * Otherwise select() will return error. + */ + if (PJ_FD_COUNT(&ioqueue->rfdset) == 0 && PJ_FD_COUNT(&ioqueue->wfdset) == 0 +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + && PJ_FD_COUNT(&ioqueue->xfdset) == 0 +#endif + ) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + scan_closing_keys(ioqueue); +#endif + pj_lock_release(ioqueue->lock); + TRACE__((THIS_FILE, " poll: no fd is set")); + if (timeout) + pj_thread_sleep(PJ_TIME_VAL_MSEC(*timeout)); + return 0; + } + + /* Copy ioqueue's pj_fd_set_t to local variables. */ + pj_memcpy(&rfdset, &ioqueue->rfdset, sizeof(pj_fd_set_t)); + pj_memcpy(&wfdset, &ioqueue->wfdset, sizeof(pj_fd_set_t)); +#if PJ_HAS_TCP + pj_memcpy(&xfdset, &ioqueue->xfdset, sizeof(pj_fd_set_t)); +#else + PJ_FD_ZERO(&xfdset); +#endif + +#if VALIDATE_FD_SET + validate_sets(ioqueue, &rfdset, &wfdset, &xfdset); +#endif + + nfds = ioqueue->nfds; + + /* Unlock ioqueue before select(). */ + pj_lock_release(ioqueue->lock); + +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + count = 0; + __try { +#endif + + count = pj_sock_select(nfds + 1, &rfdset, &wfdset, &xfdset, timeout); + +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + /* Ignore Invalid Handle Exception raised by select().*/ + } __except (GetExceptionCode() == STATUS_INVALID_HANDLE ? EXCEPTION_CONTINUE_EXECUTION + : EXCEPTION_CONTINUE_SEARCH) { + } +#endif + + if (count == 0) + return 0; + else if (count < 0) + return -pj_get_netos_error(); + + /* Scan descriptor sets for event and add the events in the event + * array to be processed later in this function. We do this so that + * events can be processed in parallel without holding ioqueue lock. + */ + pj_lock_acquire(ioqueue->lock); + + event_cnt = 0; + + /* Scan for writable sockets first to handle piggy-back data + * coming with accept(). + */ + for (h = ioqueue->active_list.next; h != &ioqueue->active_list && event_cnt < MAX_EVENTS; h = h->next) { + if (h->fd == PJ_INVALID_SOCKET) + continue; + + if ((key_has_pending_write(h) || key_has_pending_connect(h)) && PJ_FD_ISSET(h->fd, &wfdset) && !IS_CLOSING(h)) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + event[event_cnt].key = h; + event[event_cnt].event_type = WRITEABLE_EVENT; + ++event_cnt; + } + + /* Scan for readable socket. */ + if ((key_has_pending_read(h) || key_has_pending_accept(h)) && PJ_FD_ISSET(h->fd, &rfdset) && !IS_CLOSING(h) && + event_cnt < MAX_EVENTS) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + event[event_cnt].key = h; + event[event_cnt].event_type = READABLE_EVENT; + ++event_cnt; + } + +#if PJ_HAS_TCP + if (key_has_pending_connect(h) && PJ_FD_ISSET(h->fd, &xfdset) && !IS_CLOSING(h) && event_cnt < MAX_EVENTS) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + event[event_cnt].key = h; + event[event_cnt].event_type = EXCEPTION_EVENT; + ++event_cnt; + } +#endif + } + + for (i = 0; i < event_cnt; ++i) { + if (event[i].key->grp_lock) + pj_grp_lock_add_ref_dbg(event[i].key->grp_lock, "ioqueue", 0); + } + + PJ_RACE_ME(5); + + pj_lock_release(ioqueue->lock); + + PJ_RACE_ME(5); + + processed_cnt = 0; + + /* Now process all events. The dispatch functions will take care + * of locking in each of the key + */ + for (i = 0; i < event_cnt; ++i) { + + /* Just do not exceed PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL */ + if (processed_cnt < PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL) { + switch (event[i].event_type) { + case READABLE_EVENT: + if (ioqueue_dispatch_read_event(ioqueue, event[i].key)) + ++processed_cnt; + break; + case WRITEABLE_EVENT: + if (ioqueue_dispatch_write_event(ioqueue, event[i].key)) + ++processed_cnt; + break; + case EXCEPTION_EVENT: + if (ioqueue_dispatch_exception_event(ioqueue, event[i].key)) + ++processed_cnt; + break; + case NO_EVENT: + pj_assert(!"Invalid event!"); + break; + } + } + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + decrement_counter(event[i].key); +#endif + + if (event[i].key->grp_lock) + pj_grp_lock_dec_ref_dbg(event[i].key->grp_lock, "ioqueue", 0); + } + + TRACE__((THIS_FILE, " poll: count=%d events=%d processed=%d", count, event_cnt, processed_cnt)); + + return processed_cnt; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c new file mode 100755 index 000000000..e3497785a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJ_LINUX) && PJ_LINUX != 0 +/* The following headers are used to get DEPRECATED addresses */ +#include +#include +#include +#include +#include +#include +#endif + +/* Set to 1 to enable tracing */ +#if 0 +#include +#define THIS_FILE "ip_helper_generic.c" +#define TRACE_(exp) PJ_LOG(5, exp) + static const char *get_os_errmsg(void) + { + static char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(pj_get_os_error(), errmsg, sizeof(errmsg)); + return errmsg; + } + static const char *get_addr(void *addr) + { + static char txt[PJ_INET6_ADDRSTRLEN]; + struct sockaddr *ad = (struct sockaddr*)addr; + if (ad->sa_family != PJ_AF_INET && ad->sa_family != PJ_AF_INET6) + return "?"; + return pj_inet_ntop2(ad->sa_family, pj_sockaddr_get_addr(ad), + txt, sizeof(txt)); + } +#else +#define TRACE_(exp) +#endif + +#if 0 + /* dummy */ + +#elif defined(PJ_HAS_IFADDRS_H) && PJ_HAS_IFADDRS_H != 0 && defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 +/* Using getifaddrs() is preferred since it can work with both IPv4 and IPv6 */ +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + struct ifaddrs *ifap = NULL, *it; + unsigned max; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + TRACE_((THIS_FILE, "Starting interface enum with getifaddrs() for af=%d", af)); + + if (getifaddrs(&ifap) != 0) { + TRACE_((THIS_FILE, " getifarrds() failed: %s", get_os_errmsg())); + return PJ_RETURN_OS_ERROR(pj_get_netos_error()); + } + + it = ifap; + max = *p_cnt; + *p_cnt = 0; + for (; it != NULL && *p_cnt < max; it = it->ifa_next) { + struct sockaddr *ad = it->ifa_addr; + + TRACE_((THIS_FILE, " checking %s", it->ifa_name)); + + if ((it->ifa_flags & IFF_UP) == 0) { + TRACE_((THIS_FILE, " interface is down")); + continue; /* Skip when interface is down */ + } + + if ((it->ifa_flags & IFF_RUNNING) == 0) { + TRACE_((THIS_FILE, " interface is not running")); + continue; /* Skip when interface is not running */ + } + +#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF + if (it->ifa_flags & IFF_LOOPBACK) { + TRACE_((THIS_FILE, " loopback interface")); + continue; /* Skip loopback interface */ + } +#endif + + if (ad == NULL) { + TRACE_((THIS_FILE, " NULL address ignored")); + continue; /* reported to happen on Linux 2.6.25.9 + with ppp interface */ + } + + if (ad->sa_family != af) { + TRACE_((THIS_FILE, " address %s ignored (af=%d)", get_addr(ad), ad->sa_family)); + continue; /* Skip when interface is down */ + } + + /* Ignore 192.0.0.0/29 address. + * Ref: https://datatracker.ietf.org/doc/html/rfc7335#section-4 + */ + if (af == pj_AF_INET() && + (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 4) == 201326592) /* 0b1100000000000000000000000000 + which is 192.0.0.0 >> 4 */ + { + TRACE_((THIS_FILE, " address %s ignored (192.0.0.0/29 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Ignore 0.0.0.0/8 address. This is a special address + * which doesn't seem to have practical use. + */ + if (af == pj_AF_INET() && (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 24) == 0) { + TRACE_((THIS_FILE, " address %s ignored (0.0.0.0/8 class)", get_addr(ad), ad->sa_family)); + continue; + } + + TRACE_((THIS_FILE, " address %s (af=%d) added at index %d", get_addr(ad), ad->sa_family, *p_cnt)); + + pj_bzero(&ifs[*p_cnt], sizeof(ifs[0])); + pj_memcpy(&ifs[*p_cnt], ad, pj_sockaddr_get_len(ad)); + PJ_SOCKADDR_RESET_LEN(&ifs[*p_cnt]); + (*p_cnt)++; + } + + freeifaddrs(ifap); + TRACE_((THIS_FILE, "done, found %d address(es)", *p_cnt)); + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +#elif defined(SIOCGIFCONF) && defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 + +/* Note: this does not work with IPv6 */ +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + pj_sock_t sock; + char buf[512]; + struct ifconf ifc; + struct ifreq *ifr; + int i, count; + pj_status_t status; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + TRACE_((THIS_FILE, "Starting interface enum with SIOCGIFCONF for af=%d", af)); + + status = pj_sock_socket(af, PJ_SOCK_DGRAM, 0, &sock); + if (status != PJ_SUCCESS) + return status; + + /* Query available interfaces */ + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + + if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { + int oserr = pj_get_netos_error(); + TRACE_((THIS_FILE, " ioctl(SIOCGIFCONF) failed: %s", get_os_errmsg())); + pj_sock_close(sock); + return PJ_RETURN_OS_ERROR(oserr); + } + + /* Interface interfaces */ + ifr = (struct ifreq *)ifc.ifc_req; + count = ifc.ifc_len / sizeof(struct ifreq); + if (count > *p_cnt) + count = *p_cnt; + + *p_cnt = 0; + for (i = 0; i < count; ++i) { + struct ifreq *itf = &ifr[i]; + struct ifreq iff = *itf; + struct sockaddr *ad = &itf->ifr_addr; + + TRACE_((THIS_FILE, " checking interface %s", itf->ifr_name)); + + /* Skip address with different family */ + if (ad->sa_family != af) { + TRACE_((THIS_FILE, " address %s (af=%d) ignored", get_addr(ad), (int)ad->sa_family)); + continue; + } + + if (ioctl(sock, SIOCGIFFLAGS, &iff) != 0) { + TRACE_((THIS_FILE, " ioctl(SIOCGIFFLAGS) failed: %s", get_os_errmsg())); + continue; /* Failed to get flags, continue */ + } + + if ((iff.ifr_flags & IFF_UP) == 0) { + TRACE_((THIS_FILE, " interface is down")); + continue; /* Skip when interface is down */ + } + + if ((iff.ifr_flags & IFF_RUNNING) == 0) { + TRACE_((THIS_FILE, " interface is not running")); + continue; /* Skip when interface is not running */ + } + +#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF + if (iff.ifr_flags & IFF_LOOPBACK) { + TRACE_((THIS_FILE, " loopback interface")); + continue; /* Skip loopback interface */ + } +#endif + + /* Ignore 192.0.0.0/29 address. + * Ref: https://datatracker.ietf.org/doc/html/rfc7335#section-4 + */ + if (af == pj_AF_INET() && + (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 4) == 201326592) /* 0b1100000000000000000000000000 + which is 192.0.0.0 >> 4 */ + { + TRACE_((THIS_FILE, " address %s ignored (192.0.0.0/29 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Ignore 0.0.0.0/8 address. This is a special address + * which doesn't seem to have practical use. + */ + if (af == pj_AF_INET() && (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 24) == 0) { + TRACE_((THIS_FILE, " address %s ignored (0.0.0.0/8 class)", get_addr(ad), ad->sa_family)); + continue; + } + + TRACE_((THIS_FILE, " address %s (af=%d) added at index %d", get_addr(ad), ad->sa_family, *p_cnt)); + + pj_bzero(&ifs[*p_cnt], sizeof(ifs[0])); + pj_memcpy(&ifs[*p_cnt], ad, pj_sockaddr_get_len(ad)); + PJ_SOCKADDR_RESET_LEN(&ifs[*p_cnt]); + (*p_cnt)++; + } + + /* Done with socket */ + pj_sock_close(sock); + + TRACE_((THIS_FILE, "done, found %d address(es)", *p_cnt)); + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +#elif defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 +/* Note: this does not work with IPv6 */ +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + struct if_nameindex *if_list; + struct ifreq ifreq; + pj_sock_t sock; + unsigned i, max_count; + pj_status_t status; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + TRACE_((THIS_FILE, "Starting if_nameindex() for af=%d", af)); + + status = pj_sock_socket(af, PJ_SOCK_DGRAM, 0, &sock); + if (status != PJ_SUCCESS) + return status; + + if_list = if_nameindex(); + if (if_list == NULL) + return PJ_ENOTFOUND; + + max_count = *p_cnt; + *p_cnt = 0; + for (i = 0; if_list[i].if_index && *p_cnt < max_count; ++i) { + struct sockaddr *ad; + int rc; + + strncpy(ifreq.ifr_name, if_list[i].if_name, IFNAMSIZ); + + TRACE_((THIS_FILE, " checking interface %s", ifreq.ifr_name)); + + if ((rc = ioctl(sock, SIOCGIFFLAGS, &ifreq)) != 0) { + TRACE_((THIS_FILE, " ioctl(SIOCGIFFLAGS) failed: %s", get_os_errmsg())); + continue; /* Failed to get flags, continue */ + } + + if ((ifreq.ifr_flags & IFF_UP) == 0) { + TRACE_((THIS_FILE, " interface is down")); + continue; /* Skip when interface is down */ + } + + if ((ifreq.ifr_flags & IFF_RUNNING) == 0) { + TRACE_((THIS_FILE, " interface is not running")); + continue; /* Skip when interface is not running */ + } + +#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF + if (ifreq.ifr_flags & IFF_LOOPBACK) { + TRACE_((THIS_FILE, " loopback interface")); + continue; /* Skip loopback interface */ + } +#endif + + /* Note: SIOCGIFADDR does not work for IPv6! */ + if ((rc = ioctl(sock, SIOCGIFADDR, &ifreq)) != 0) { + TRACE_((THIS_FILE, " ioctl(SIOCGIFADDR) failed: %s", get_os_errmsg())); + continue; /* Failed to get address, continue */ + } + + ad = (struct sockaddr *)&ifreq.ifr_addr; + + if (ad->sa_family != af) { + TRACE_((THIS_FILE, " address %s family %d ignored", get_addr(&ifreq.ifr_addr), ifreq.ifr_addr.sa_family)); + continue; /* Not address family that we want, continue */ + } + + /* Ignore 192.0.0.0/29 address. + * Ref: https://datatracker.ietf.org/doc/html/rfc7335#section-4 + */ + if (af == pj_AF_INET() && + (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 4) == 201326592) /* 0b1100000000000000000000000000 + which is 192.0.0.0 >> 4 */ + { + TRACE_((THIS_FILE, " address %s ignored (192.0.0.0/29 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Ignore 0.0.0.0/8 address. This is a special address + * which doesn't seem to have practical use. + */ + if (af == pj_AF_INET() && (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 24) == 0) { + TRACE_((THIS_FILE, " address %s ignored (0.0.0.0/8 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Got an address ! */ + TRACE_((THIS_FILE, " address %s (af=%d) added at index %d", get_addr(ad), ad->sa_family, *p_cnt)); + + pj_bzero(&ifs[*p_cnt], sizeof(ifs[0])); + pj_memcpy(&ifs[*p_cnt], ad, pj_sockaddr_get_len(ad)); + PJ_SOCKADDR_RESET_LEN(&ifs[*p_cnt]); + (*p_cnt)++; + } + + if_freenameindex(if_list); + pj_sock_close(sock); + + TRACE_((THIS_FILE, "done, found %d address(es)", *p_cnt)); + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +#else +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(p_cnt && *p_cnt > 0 && ifs, PJ_EINVAL); + + pj_bzero(ifs, sizeof(ifs[0]) * (*p_cnt)); + + /* Just get one default route */ + status = pj_getdefaultipinterface(af, &ifs[0]); + if (status != PJ_SUCCESS) + return status; + + *p_cnt = 1; + return PJ_SUCCESS; +} +#endif /* SIOCGIFCONF */ + +/* + * Enumerate the local IP interface currently active in the host. + */ +PJ_DEF(pj_status_t) pj_enum_ip_interface(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + unsigned start; + pj_status_t status; + + start = 0; + if (af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) { + unsigned max = *p_cnt; + status = if_enum_by_af(PJ_AF_INET6, &max, &ifs[start]); + if (status == PJ_SUCCESS) { + start += max; + (*p_cnt) -= max; + } + } + + if (af == PJ_AF_INET || af == PJ_AF_UNSPEC) { + unsigned max = *p_cnt; + status = if_enum_by_af(PJ_AF_INET, &max, &ifs[start]); + if (status == PJ_SUCCESS) { + start += max; + (*p_cnt) -= max; + } + } + + *p_cnt = start; + + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +/* + * Enumerate the IP routing table for this host. + */ +PJ_DEF(pj_status_t) pj_enum_ip_route(unsigned *p_cnt, pj_ip_route_entry routes[]) +{ + pj_sockaddr itf; + pj_status_t status; + + PJ_ASSERT_RETURN(p_cnt && *p_cnt > 0 && routes, PJ_EINVAL); + + pj_bzero(routes, sizeof(routes[0]) * (*p_cnt)); + + /* Just get one default route */ + status = pj_getdefaultipinterface(PJ_AF_INET, &itf); + if (status != PJ_SUCCESS) + return status; + + routes[0].ipv4.if_addr.s_addr = itf.ipv4.sin_addr.s_addr; + routes[0].ipv4.dst_addr.s_addr = 0; + routes[0].ipv4.mask.s_addr = 0; + *p_cnt = 1; + + return PJ_SUCCESS; +} + +#if defined(PJ_LINUX) && PJ_LINUX != 0 +static pj_status_t get_ipv6_deprecated(unsigned *count, pj_sockaddr addr[]) +{ + struct { + struct nlmsghdr nlmsg_info; + struct ifaddrmsg ifaddrmsg_info; + } netlink_req; + + long pagesize = sysconf(_SC_PAGESIZE); + if (!pagesize) + pagesize = 4096; /* Assume pagesize is 4096 if sysconf() failed */ + + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + + bzero(&netlink_req, sizeof(netlink_req)); + + netlink_req.nlmsg_info.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + netlink_req.nlmsg_info.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + netlink_req.nlmsg_info.nlmsg_type = RTM_GETADDR; + netlink_req.nlmsg_info.nlmsg_pid = getpid(); + netlink_req.ifaddrmsg_info.ifa_family = AF_INET6; + + int rtn = send(fd, &netlink_req, netlink_req.nlmsg_info.nlmsg_len, 0); + if (rtn < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + + char read_buffer[pagesize]; + size_t idx = 0; + + while (1) { + bzero(read_buffer, pagesize); + int read_size = recv(fd, read_buffer, pagesize, 0); + if (read_size < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + + struct nlmsghdr *nlmsg_ptr = (struct nlmsghdr *)read_buffer; + int nlmsg_len = read_size; + + if (nlmsg_len < sizeof(struct nlmsghdr)) + return PJ_ETOOSMALL; + + if (nlmsg_ptr->nlmsg_type == NLMSG_DONE) + break; + + for (; NLMSG_OK(nlmsg_ptr, nlmsg_len); nlmsg_ptr = NLMSG_NEXT(nlmsg_ptr, nlmsg_len)) { + struct ifaddrmsg *ifaddrmsg_ptr; + struct rtattr *rtattr_ptr; + int ifaddrmsg_len; + + ifaddrmsg_ptr = (struct ifaddrmsg *)NLMSG_DATA(nlmsg_ptr); + + if (ifaddrmsg_ptr->ifa_flags & IFA_F_DEPRECATED || ifaddrmsg_ptr->ifa_flags & IFA_F_TENTATIVE) { + rtattr_ptr = (struct rtattr *)IFA_RTA(ifaddrmsg_ptr); + ifaddrmsg_len = IFA_PAYLOAD(nlmsg_ptr); + + for (; RTA_OK(rtattr_ptr, ifaddrmsg_len); rtattr_ptr = RTA_NEXT(rtattr_ptr, ifaddrmsg_len)) { + switch (rtattr_ptr->rta_type) { + case IFA_ADDRESS: + // Check if addr can contains more data + if (idx >= *count) + break; + // Store deprecated IP + char deprecatedAddr[PJ_INET6_ADDRSTRLEN]; + inet_ntop(ifaddrmsg_ptr->ifa_family, RTA_DATA(rtattr_ptr), deprecatedAddr, + sizeof(deprecatedAddr)); + pj_str_t pj_addr_str; + pj_cstr(&pj_addr_str, deprecatedAddr); + pj_sockaddr_init(pj_AF_INET6(), &addr[idx], &pj_addr_str, 0); + ++idx; + default: + break; + } + } + } + } + } + + close(fd); + *count = idx; + + return PJ_SUCCESS; +} +#endif + +/* + * Enumerate the local IP interface currently active in the host. + */ +PJ_DEF(pj_status_t) pj_enum_ip_interface2(const pj_enum_ip_option *opt, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + pj_enum_ip_option opt_; + + if (opt) + opt_ = *opt; + else + pj_enum_ip_option_default(&opt_); + + if (opt_.af != pj_AF_INET() && opt_.omit_deprecated_ipv6) { + +#if defined(PJ_LINUX) && PJ_LINUX != 0 + pj_sockaddr addrs[*p_cnt]; + pj_sockaddr deprecatedAddrs[*p_cnt]; + unsigned deprecatedCount = *p_cnt; + unsigned cnt = 0; + int i; + pj_status_t status; + + status = get_ipv6_deprecated(&deprecatedCount, deprecatedAddrs); + if (status != PJ_SUCCESS) + return status; + + status = pj_enum_ip_interface(opt_.af, p_cnt, addrs); + if (status != PJ_SUCCESS) + return status; + + for (i = 0; i < *p_cnt; ++i) { + int j; + + ifs[cnt++] = addrs[i]; + + if (addrs[i].addr.sa_family != pj_AF_INET6()) + continue; + + for (j = 0; j < deprecatedCount; ++j) { + if (pj_sockaddr_cmp(&addrs[i], &deprecatedAddrs[j]) == 0) { + cnt--; + break; + } + } + } + + *p_cnt = cnt; + return *p_cnt ? PJ_SUCCESS : PJ_ENOTFOUND; +#else + return PJ_ENOTSUP; +#endif + } + + return pj_enum_ip_interface(opt_.af, p_cnt, ifs); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/list.c b/src/tuya_p2p/pjproject/pjlib/src/pj/list.c new file mode 100755 index 000000000..8cfb2bf81 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/list.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +#if !PJ_FUNCTIONS_ARE_INLINED +#include +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/lock.c b/src/tuya_p2p/pjproject/pjlib/src/pj/lock.c new file mode 100755 index 000000000..a34f3bf27 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/lock.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "lock.c" + +typedef void LOCK_OBJ; + +/* + * Lock structure. + */ +struct pj_lock_t { + LOCK_OBJ *lock_object; + + pj_status_t (*acquire)(LOCK_OBJ *); + pj_status_t (*tryacquire)(LOCK_OBJ *); + pj_status_t (*release)(LOCK_OBJ *); + pj_status_t (*destroy)(LOCK_OBJ *); +}; + +typedef pj_status_t (*FPTR)(LOCK_OBJ *); + +/****************************************************************************** + * Implementation of lock object with mutex. + */ +static pj_lock_t mutex_lock_template = {NULL, (FPTR)&pj_mutex_lock, (FPTR)&pj_mutex_trylock, (FPTR)&pj_mutex_unlock, + (FPTR)&pj_mutex_destroy}; + +static pj_status_t create_mutex_lock(pj_pool_t *pool, const char *name, int type, pj_lock_t **lock) +{ + pj_lock_t *p_lock; + pj_mutex_t *mutex; + pj_status_t rc; + + PJ_ASSERT_RETURN(pool && lock, PJ_EINVAL); + + p_lock = PJ_POOL_ALLOC_T(pool, pj_lock_t); + if (!p_lock) + return PJ_ENOMEM; + + pj_memcpy(p_lock, &mutex_lock_template, sizeof(pj_lock_t)); + rc = pj_mutex_create(pool, name, type, &mutex); + if (rc != PJ_SUCCESS) + return rc; + + p_lock->lock_object = mutex; + *lock = p_lock; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_lock_create_simple_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock) +{ + return create_mutex_lock(pool, name, PJ_MUTEX_SIMPLE, lock); +} + +PJ_DEF(pj_status_t) pj_lock_create_recursive_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock) +{ + return create_mutex_lock(pool, name, PJ_MUTEX_RECURSE, lock); +} + +/****************************************************************************** + * Implementation of NULL lock object. + */ +static pj_status_t null_op(void *arg) +{ + PJ_UNUSED_ARG(arg); + return PJ_SUCCESS; +} + +static pj_lock_t null_lock_template = {NULL, &null_op, &null_op, &null_op, &null_op}; + +PJ_DEF(pj_status_t) pj_lock_create_null_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock) +{ + PJ_UNUSED_ARG(name); + PJ_UNUSED_ARG(pool); + + PJ_ASSERT_RETURN(lock, PJ_EINVAL); + + *lock = &null_lock_template; + return PJ_SUCCESS; +} + +/****************************************************************************** + * Implementation of semaphore lock object. + */ +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 + +static pj_lock_t sem_lock_template = {NULL, (FPTR)&pj_sem_wait, (FPTR)&pj_sem_trywait, (FPTR)&pj_sem_post, + (FPTR)&pj_sem_destroy}; + +PJ_DEF(pj_status_t) +pj_lock_create_semaphore(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_lock_t **lock) +{ + pj_lock_t *p_lock; + pj_sem_t *sem; + pj_status_t rc; + + PJ_ASSERT_RETURN(pool && lock, PJ_EINVAL); + + p_lock = PJ_POOL_ALLOC_T(pool, pj_lock_t); + if (!p_lock) + return PJ_ENOMEM; + + pj_memcpy(p_lock, &sem_lock_template, sizeof(pj_lock_t)); + rc = pj_sem_create(pool, name, initial, max, &sem); + if (rc != PJ_SUCCESS) + return rc; + + p_lock->lock_object = sem; + *lock = p_lock; + + return PJ_SUCCESS; +} + +#endif /* PJ_HAS_SEMAPHORE */ + +PJ_DEF(pj_status_t) pj_lock_acquire(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->acquire)(lock->lock_object); +} + +PJ_DEF(pj_status_t) pj_lock_tryacquire(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->tryacquire)(lock->lock_object); +} + +PJ_DEF(pj_status_t) pj_lock_release(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->release)(lock->lock_object); +} + +PJ_DEF(pj_status_t) pj_lock_destroy(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->destroy)(lock->lock_object); +} + +/****************************************************************************** + * Group lock + */ + +/* Individual lock in the group lock */ +typedef struct grp_lock_item { + PJ_DECL_LIST_MEMBER(struct grp_lock_item); + int prio; + pj_lock_t *lock; + +} grp_lock_item; + +/* Destroy callbacks */ +typedef struct grp_destroy_callback { + PJ_DECL_LIST_MEMBER(struct grp_destroy_callback); + void *comp; + void (*handler)(void *); +} grp_destroy_callback; + +#if PJ_GRP_LOCK_DEBUG +/* Store each add_ref caller */ +typedef struct grp_lock_ref { + PJ_DECL_LIST_MEMBER(struct grp_lock_ref); + const char *file; + int line; +} grp_lock_ref; +#endif + +/* The group lock */ +struct pj_grp_lock_t { + pj_lock_t base; + + pj_pool_t *pool; + pj_atomic_t *ref_cnt; + pj_lock_t *own_lock; + + pj_thread_t *owner; + int owner_cnt; + + grp_lock_item lock_list; + grp_destroy_callback destroy_list; + +#if PJ_GRP_LOCK_DEBUG + grp_lock_ref ref_list; + grp_lock_ref ref_free_list; +#endif +}; + +PJ_DEF(void) pj_grp_lock_config_default(pj_grp_lock_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); +} + +static void grp_lock_set_owner_thread(pj_grp_lock_t *glock) +{ + if (!glock->owner) { +#if PJ_HAS_THREADS + glock->owner = pj_thread_this(); +#else + glock->owner = (pj_thread_t *)-1; +#endif + glock->owner_cnt = 1; + } else { +#if PJ_HAS_THREADS + pj_assert(glock->owner == pj_thread_this()); +#endif + glock->owner_cnt++; + } +} + +static void grp_lock_unset_owner_thread(pj_grp_lock_t *glock) +{ +#if PJ_HAS_THREADS + pj_assert(glock->owner == pj_thread_this()); +#endif + pj_assert(glock->owner_cnt > 0); + if (--glock->owner_cnt <= 0) { + glock->owner = NULL; + glock->owner_cnt = 0; + } +} + +static pj_status_t grp_lock_acquire(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + grp_lock_item *lck; + + pj_assert(pj_atomic_get(glock->ref_cnt) > 0); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + pj_lock_acquire(lck->lock); + lck = lck->next; + } + grp_lock_set_owner_thread(glock); + pj_grp_lock_add_ref(glock); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_tryacquire(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + grp_lock_item *lck; + + pj_assert(pj_atomic_get(glock->ref_cnt) > 0); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + pj_status_t status = pj_lock_tryacquire(lck->lock); + if (status != PJ_SUCCESS) { + lck = lck->prev; + while (lck != &glock->lock_list) { + pj_lock_release(lck->lock); + lck = lck->prev; + } + return status; + } + lck = lck->next; + } + grp_lock_set_owner_thread(glock); + pj_grp_lock_add_ref(glock); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_release(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + grp_lock_item *lck; + + grp_lock_unset_owner_thread(glock); + + lck = glock->lock_list.prev; + while (lck != &glock->lock_list) { + pj_lock_release(lck->lock); + lck = lck->prev; + } + return pj_grp_lock_dec_ref(glock); +} + +static pj_status_t grp_lock_add_handler(pj_grp_lock_t *glock, pj_pool_t *pool, void *comp, void (*destroy)(void *comp), + pj_bool_t acquire_lock) +{ + grp_destroy_callback *cb; + + if (acquire_lock) + grp_lock_acquire(glock); + + if (pool == NULL) + pool = glock->pool; + + cb = PJ_POOL_ZALLOC_T(pool, grp_destroy_callback); + cb->comp = comp; + cb->handler = destroy; + pj_list_push_back(&glock->destroy_list, cb); + + if (acquire_lock) + grp_lock_release(glock); + + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_destroy(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + pj_pool_t *pool = glock->pool; + grp_lock_item *lck; + grp_destroy_callback *cb; + + if (!glock->pool) { + /* already destroyed?! */ + return PJ_EINVAL; + } + + /* Release all chained locks */ + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + if (lck->lock != glock->own_lock) { + int i; + for (i = 0; i < glock->owner_cnt; ++i) + pj_lock_release(lck->lock); + } + lck = lck->next; + } + + /* Call callbacks */ + cb = glock->destroy_list.next; + while (cb != &glock->destroy_list) { + grp_destroy_callback *next = cb->next; + cb->handler(cb->comp); + cb = next; + } + + pj_lock_destroy(glock->own_lock); + pj_atomic_destroy(glock->ref_cnt); + glock->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_grp_lock_create(pj_pool_t *pool, const pj_grp_lock_config *cfg, pj_grp_lock_t **p_grp_lock) +{ + pj_grp_lock_t *glock; + grp_lock_item *own_lock; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && p_grp_lock, PJ_EINVAL); + + PJ_UNUSED_ARG(cfg); + + pool = pj_pool_create(pool->factory, "glck%p", 512, 512, NULL); + if (!pool) + return PJ_ENOMEM; + + glock = PJ_POOL_ZALLOC_T(pool, pj_grp_lock_t); + glock->base.lock_object = glock; + glock->base.acquire = &grp_lock_acquire; + glock->base.tryacquire = &grp_lock_tryacquire; + glock->base.release = &grp_lock_release; + glock->base.destroy = &grp_lock_destroy; + + glock->pool = pool; + pj_list_init(&glock->lock_list); + pj_list_init(&glock->destroy_list); +#if PJ_GRP_LOCK_DEBUG + pj_list_init(&glock->ref_list); + pj_list_init(&glock->ref_free_list); +#endif + + status = pj_atomic_create(pool, 0, &glock->ref_cnt); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &glock->own_lock); + if (status != PJ_SUCCESS) + goto on_error; + + own_lock = PJ_POOL_ZALLOC_T(pool, grp_lock_item); + own_lock->lock = glock->own_lock; + pj_list_push_back(&glock->lock_list, own_lock); + + *p_grp_lock = glock; + return PJ_SUCCESS; + +on_error: + grp_lock_destroy(glock); + return status; +} + +PJ_DEF(pj_status_t) +pj_grp_lock_create_w_handler(pj_pool_t *pool, const pj_grp_lock_config *cfg, void *member, + void (*handler)(void *member), pj_grp_lock_t **p_grp_lock) +{ + pj_status_t status; + + status = pj_grp_lock_create(pool, cfg, p_grp_lock); + if (status == PJ_SUCCESS) { + pj_pool_t *pool = (*p_grp_lock)->pool; + grp_lock_add_handler(*p_grp_lock, pool, member, handler, PJ_FALSE); + } + + return status; +} + +PJ_DEF(pj_status_t) pj_grp_lock_destroy(pj_grp_lock_t *grp_lock) +{ + return grp_lock_destroy(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_acquire(pj_grp_lock_t *grp_lock) +{ + return grp_lock_acquire(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_tryacquire(pj_grp_lock_t *grp_lock) +{ + return grp_lock_tryacquire(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_release(pj_grp_lock_t *grp_lock) +{ + return grp_lock_release(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_replace(pj_grp_lock_t *old_lock, pj_grp_lock_t *new_lock) +{ + grp_destroy_callback *ocb; + + /* Move handlers from old to new */ + ocb = old_lock->destroy_list.next; + while (ocb != &old_lock->destroy_list) { + grp_destroy_callback *ncb; + + ncb = PJ_POOL_ALLOC_T(new_lock->pool, grp_destroy_callback); + ncb->comp = ocb->comp; + ncb->handler = ocb->handler; + pj_list_push_back(&new_lock->destroy_list, ncb); + + ocb = ocb->next; + } + + pj_list_init(&old_lock->destroy_list); + + grp_lock_destroy(old_lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_grp_lock_add_handler(pj_grp_lock_t *glock, pj_pool_t *pool, void *comp, void (*destroy)(void *comp)) +{ + return grp_lock_add_handler(glock, pool, comp, destroy, PJ_TRUE); +} + +PJ_DEF(pj_status_t) pj_grp_lock_del_handler(pj_grp_lock_t *glock, void *comp, void (*destroy)(void *comp)) +{ + grp_destroy_callback *cb; + + grp_lock_acquire(glock); + + cb = glock->destroy_list.next; + while (cb != &glock->destroy_list) { + if (cb->comp == comp && cb->handler == destroy) + break; + cb = cb->next; + } + + if (cb != &glock->destroy_list) + pj_list_erase(cb); + + grp_lock_release(glock); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_add_ref(pj_grp_lock_t *glock) +{ + pj_atomic_inc(glock->ref_cnt); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_dec_ref(pj_grp_lock_t *glock) +{ + int cnt; /* for debugging */ + if ((cnt = pj_atomic_dec_and_get(glock->ref_cnt)) == 0) { + grp_lock_destroy(glock); + return PJ_EGONE; + } + pj_assert(cnt > 0); + return PJ_SUCCESS; +} + +#if PJ_GRP_LOCK_DEBUG +static pj_status_t grp_lock_dec_ref_dump(pj_grp_lock_t *glock) +{ + pj_status_t status; + + status = grp_lock_dec_ref(glock); + if (status == PJ_SUCCESS) { + pj_grp_lock_dump(glock); + } else if (status == PJ_EGONE) { + PJ_LOG(4, (THIS_FILE, "Group lock %p destroyed.", glock)); + } + + return status; +} + +PJ_DEF(pj_status_t) pj_grp_lock_add_ref_dbg(pj_grp_lock_t *glock, const char *file, int line) +{ + grp_lock_ref *ref; + pj_status_t status; + + pj_enter_critical_section(); + if (!pj_list_empty(&glock->ref_free_list)) { + ref = glock->ref_free_list.next; + pj_list_erase(ref); + } else { + ref = PJ_POOL_ALLOC_T(glock->pool, grp_lock_ref); + } + + ref->file = file; + ref->line = line; + pj_list_push_back(&glock->ref_list, ref); + + pj_leave_critical_section(); + + status = grp_lock_add_ref(glock); + + if (status != PJ_SUCCESS) { + pj_enter_critical_section(); + pj_list_erase(ref); + pj_list_push_back(&glock->ref_free_list, ref); + pj_leave_critical_section(); + } + + return status; +} + +PJ_DEF(pj_status_t) pj_grp_lock_dec_ref_dbg(pj_grp_lock_t *glock, const char *file, int line) +{ + grp_lock_ref *ref; + + PJ_UNUSED_ARG(line); + + pj_enter_critical_section(); + /* Find the same source file */ + ref = glock->ref_list.next; + while (ref != &glock->ref_list) { + if (strcmp(ref->file, file) == 0) { + pj_list_erase(ref); + pj_list_push_back(&glock->ref_free_list, ref); + break; + } + ref = ref->next; + } + pj_leave_critical_section(); + + if (ref == &glock->ref_list) { + PJ_LOG(2, (THIS_FILE, + "pj_grp_lock_dec_ref_dbg() could not find " + "matching ref for %s", + file)); + } + + return grp_lock_dec_ref_dump(glock); +} +#else +PJ_DEF(pj_status_t) pj_grp_lock_add_ref(pj_grp_lock_t *glock) +{ + return grp_lock_add_ref(glock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_dec_ref(pj_grp_lock_t *glock) +{ + return grp_lock_dec_ref(glock); +} +#endif + +PJ_DEF(int) pj_grp_lock_get_ref(pj_grp_lock_t *glock) +{ + return pj_atomic_get(glock->ref_cnt); +} + +PJ_DEF(pj_status_t) pj_grp_lock_chain_lock(pj_grp_lock_t *glock, pj_lock_t *lock, int pos) +{ + grp_lock_item *lck, *new_lck; + int i; + + grp_lock_acquire(glock); + + for (i = 0; i < glock->owner_cnt; ++i) + pj_lock_acquire(lock); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + if (lck->prio >= pos) + break; + lck = lck->next; + } + + new_lck = PJ_POOL_ZALLOC_T(glock->pool, grp_lock_item); + new_lck->prio = pos; + new_lck->lock = lock; + pj_list_insert_before(lck, new_lck); + + /* this will also release the new lock */ + grp_lock_release(glock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_grp_lock_unchain_lock(pj_grp_lock_t *glock, pj_lock_t *lock) +{ + grp_lock_item *lck; + + grp_lock_acquire(glock); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + if (lck->lock == lock) + break; + lck = lck->next; + } + + if (lck != &glock->lock_list) { + int i; + + pj_list_erase(lck); + for (i = 0; i < glock->owner_cnt; ++i) + pj_lock_release(lck->lock); + } + + grp_lock_release(glock); + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_grp_lock_dump(pj_grp_lock_t *grp_lock) +{ +#if PJ_GRP_LOCK_DEBUG + grp_lock_ref *ref; + char info_buf[1000]; + pj_str_t info; + + info.ptr = info_buf; + info.slen = 0; + + grp_lock_add_ref(grp_lock); + pj_enter_critical_section(); + + ref = grp_lock->ref_list.next; + while (ref != &grp_lock->ref_list && info.slen < sizeof(info_buf)) { + char *start = info.ptr + info.slen; + int max_len = sizeof(info_buf) - info.slen; + int len; + + len = pj_ansi_snprintf(start, max_len, "\t%s:%d\n", ref->file, ref->line); + if (len < 1 || len >= max_len) { + len = strlen(ref->file); + if (len > max_len - 1) + len = max_len - 1; + + memcpy(start, ref->file, len); + start[len++] = '\n'; + } + + info.slen += len; + + ref = ref->next; + } + + if (ref != &grp_lock->ref_list) { + int i; + for (i = 0; i < 4; ++i) + info_buf[sizeof(info_buf) - i - 1] = '.'; + } + info.ptr[info.slen - 1] = '\0'; + + pj_leave_critical_section(); + + PJ_LOG(4, (THIS_FILE, "Group lock %p, ref_cnt=%d. Reference holders:\n%s", grp_lock, + pj_grp_lock_get_ref(grp_lock) - 1, info.ptr)); + + grp_lock_dec_ref(grp_lock); +#else + PJ_LOG(4, (THIS_FILE, "Group lock %p, ref_cnt=%d.", grp_lock, pj_grp_lock_get_ref(grp_lock))); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/log.c b/src/tuya_p2p/pjproject/pjlib/src/pj/log.c new file mode 100755 index 000000000..262a5fe11 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/log.c @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if PJ_LOG_MAX_LEVEL >= 1 + +#if 0 +PJ_DEF_DATA(int) pj_log_max_level = PJ_LOG_MAX_LEVEL; +#else +static int pj_log_max_level = PJ_LOG_MAX_LEVEL; +#endif + +static void *g_last_thread; + +#if PJ_HAS_THREADS +static long thread_suspended_tls_id = -1; +#if PJ_LOG_ENABLE_INDENT +static long thread_indent_tls_id = -1; +#endif +#endif + +#if !PJ_LOG_ENABLE_INDENT || !PJ_HAS_THREADS +static int log_indent; +#endif + +static pj_log_func *log_writer = &pj_log_write; +static unsigned log_decor = PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_SENDER | PJ_LOG_HAS_NEWLINE | + PJ_LOG_HAS_SPACE | PJ_LOG_HAS_THREAD_SWC | PJ_LOG_HAS_INDENT +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + | PJ_LOG_HAS_COLOR +#endif + ; + +static pj_color_t PJ_LOG_COLOR_0 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R; +static pj_color_t PJ_LOG_COLOR_1 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R; +static pj_color_t PJ_LOG_COLOR_2 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R | PJ_TERM_COLOR_G; +static pj_color_t PJ_LOG_COLOR_3 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +static pj_color_t PJ_LOG_COLOR_4 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +static pj_color_t PJ_LOG_COLOR_5 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +static pj_color_t PJ_LOG_COLOR_6 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +/* Default terminal color */ +static pj_color_t PJ_LOG_COLOR_77 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; + +#if PJ_LOG_USE_STACK_BUFFER == 0 +static char log_buffer[PJ_LOG_MAX_SIZE]; +#endif + +#define LOG_MAX_INDENT 80 + +#if PJ_HAS_THREADS +static void logging_shutdown(void) +{ + if (thread_suspended_tls_id != -1) { + pj_thread_local_free(thread_suspended_tls_id); + thread_suspended_tls_id = -1; + } +#if PJ_LOG_ENABLE_INDENT + if (thread_indent_tls_id != -1) { + pj_thread_local_free(thread_indent_tls_id); + thread_indent_tls_id = -1; + } +#endif +} +#endif /* PJ_HAS_THREADS */ + +#if PJ_LOG_ENABLE_INDENT && PJ_HAS_THREADS +PJ_DEF(void) pj_log_set_indent(int indent) +{ + if (indent < 0) + indent = 0; + pj_thread_local_set(thread_indent_tls_id, (void *)(pj_ssize_t)indent); +} + +static int log_get_raw_indent(void) +{ + return (long)(pj_ssize_t)pj_thread_local_get(thread_indent_tls_id); +} + +#else +PJ_DEF(void) pj_log_set_indent(int indent) +{ + log_indent = indent; + if (log_indent < 0) + log_indent = 0; +} + +static int log_get_raw_indent(void) +{ + return log_indent; +} +#endif /* PJ_LOG_ENABLE_INDENT && PJ_HAS_THREADS */ + +PJ_DEF(int) pj_log_get_indent(void) +{ + int indent = log_get_raw_indent(); + return indent > LOG_MAX_INDENT ? LOG_MAX_INDENT : indent; +} + +PJ_DEF(void) pj_log_add_indent(int indent) +{ + pj_log_set_indent(log_get_raw_indent() + indent); +} + +PJ_DEF(void) pj_log_push_indent(void) +{ + pj_log_add_indent(PJ_LOG_INDENT_SIZE); +} + +PJ_DEF(void) pj_log_pop_indent(void) +{ + pj_log_add_indent(-PJ_LOG_INDENT_SIZE); +} + +pj_status_t pj_log_init(void) +{ +#if PJ_HAS_THREADS + if (thread_suspended_tls_id == -1) { + pj_status_t status; + status = pj_thread_local_alloc(&thread_suspended_tls_id); + if (status != PJ_SUCCESS) + return status; + +#if PJ_LOG_ENABLE_INDENT + status = pj_thread_local_alloc(&thread_indent_tls_id); + if (status != PJ_SUCCESS) { + pj_thread_local_free(thread_suspended_tls_id); + thread_suspended_tls_id = -1; + return status; + } +#endif + pj_atexit(&logging_shutdown); + } +#endif + g_last_thread = NULL; + + /* Normalize log decor, e.g: unset thread flags when threading is + * disabled. + */ + pj_log_set_decor(pj_log_get_decor()); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_log_set_decor(unsigned decor) +{ + log_decor = decor; + +#if !PJ_HAS_THREADS + /* Unset thread related flags */ + if (log_decor & PJ_LOG_HAS_THREAD_ID) { + log_decor &= ~(PJ_LOG_HAS_THREAD_ID); + } + if (log_decor & PJ_LOG_HAS_THREAD_SWC) { + log_decor &= ~(PJ_LOG_HAS_THREAD_SWC); + } +#endif +} + +PJ_DEF(unsigned) pj_log_get_decor(void) +{ + return log_decor; +} + +PJ_DEF(void) pj_log_set_color(int level, pj_color_t color) +{ + switch (level) { + case 0: + PJ_LOG_COLOR_0 = color; + break; + case 1: + PJ_LOG_COLOR_1 = color; + break; + case 2: + PJ_LOG_COLOR_2 = color; + break; + case 3: + PJ_LOG_COLOR_3 = color; + break; + case 4: + PJ_LOG_COLOR_4 = color; + break; + case 5: + PJ_LOG_COLOR_5 = color; + break; + case 6: + PJ_LOG_COLOR_6 = color; + break; + /* Default terminal color */ + case 77: + PJ_LOG_COLOR_77 = color; + break; + default: + /* Do nothing */ + break; + } +} + +PJ_DEF(pj_color_t) pj_log_get_color(int level) +{ + switch (level) { + case 0: + return PJ_LOG_COLOR_0; + case 1: + return PJ_LOG_COLOR_1; + case 2: + return PJ_LOG_COLOR_2; + case 3: + return PJ_LOG_COLOR_3; + case 4: + return PJ_LOG_COLOR_4; + case 5: + return PJ_LOG_COLOR_5; + case 6: + return PJ_LOG_COLOR_6; + default: + /* Return default terminal color */ + return PJ_LOG_COLOR_77; + } +} + +PJ_DEF(void) pj_log_set_level(int level) +{ + pj_log_max_level = level; +} + +#if 1 +PJ_DEF(int) pj_log_get_level(void) +{ + return pj_log_max_level; +} +#endif + +PJ_DEF(void) pj_log_set_log_func(pj_log_func *func) +{ + log_writer = func; +} + +PJ_DEF(pj_log_func *) pj_log_get_log_func(void) +{ + return log_writer; +} + +/* Temporarily suspend logging facility for this thread. + * If thread local storage/variable is not used or not initialized, then + * we can only suspend the logging globally across all threads. This may + * happen e.g. when log function is called before PJLIB is fully initialized + * or after PJLIB is shutdown. + */ +static void suspend_logging(int *saved_level) +{ + /* Save the level regardless, just in case PJLIB is shutdown + * between suspend and resume. + */ + *saved_level = pj_log_max_level; + +#if PJ_HAS_THREADS + if (thread_suspended_tls_id != -1) { + pj_thread_local_set(thread_suspended_tls_id, (void *)(pj_ssize_t)PJ_TRUE); + } else +#endif + { + pj_log_max_level = 0; + } +} + +/* Resume logging facility for this thread */ +static void resume_logging(int *saved_level) +{ +#if PJ_HAS_THREADS + if (thread_suspended_tls_id != -1) { + pj_thread_local_set(thread_suspended_tls_id, (void *)(pj_size_t)PJ_FALSE); + } else +#endif + { + /* Only revert the level if application doesn't change the + * logging level between suspend and resume. + */ + if (pj_log_max_level == 0 && *saved_level) + pj_log_max_level = *saved_level; + } +} + +/* Is logging facility suspended for this thread? */ +static pj_bool_t is_logging_suspended(void) +{ +#if PJ_HAS_THREADS + if (thread_suspended_tls_id != -1) { + return pj_thread_local_get(thread_suspended_tls_id) != NULL; + } else +#endif + { + return pj_log_max_level == 0; + } +} + +PJ_DEF(void) pj_log(const char *sender, int level, const char *format, va_list marker) +{ + pj_time_val now; + pj_parsed_time ptime; + char *pre; +#if PJ_LOG_USE_STACK_BUFFER + char log_buffer[PJ_LOG_MAX_SIZE]; +#endif + int saved_level, len, print_len; + + PJ_CHECK_STACK(); + + if (level > pj_log_max_level) + return; + + if (is_logging_suspended()) + return; + + /* Temporarily disable logging for this thread. Some of PJLIB APIs that + * this function calls below will recursively call the logging function + * back, hence it will cause infinite recursive calls if we allow that. + */ + suspend_logging(&saved_level); + + /* Get current date/time. */ + pj_gettimeofday(&now); + pj_time_decode(&now, &ptime); + + pre = log_buffer; + if (log_decor & PJ_LOG_HAS_LEVEL_TEXT) { + static const char *ltexts[] = {"FATAL:", "ERROR:", " WARN:", " INFO:", "DEBUG:", "TRACE:", "DETRC:"}; + pj_ansi_strcpy(pre, ltexts[level]); + pre += 6; + } + if (log_decor & PJ_LOG_HAS_DAY_NAME) { + static const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + pj_ansi_strcpy(pre, wdays[ptime.wday]); + pre += 3; + } + if (log_decor & PJ_LOG_HAS_YEAR) { + if (pre != log_buffer) + *pre++ = ' '; + pre += pj_utoa(ptime.year, pre); + } + if (log_decor & PJ_LOG_HAS_MONTH) { + *pre++ = '-'; + pre += pj_utoa_pad(ptime.mon + 1, pre, 2, '0'); + } + if (log_decor & PJ_LOG_HAS_DAY_OF_MON) { + *pre++ = '-'; + pre += pj_utoa_pad(ptime.day, pre, 2, '0'); + } + if (log_decor & PJ_LOG_HAS_TIME) { + if (pre != log_buffer) + *pre++ = ' '; + pre += pj_utoa_pad(ptime.hour, pre, 2, '0'); + *pre++ = ':'; + pre += pj_utoa_pad(ptime.min, pre, 2, '0'); + *pre++ = ':'; + pre += pj_utoa_pad(ptime.sec, pre, 2, '0'); + } + if (log_decor & PJ_LOG_HAS_MICRO_SEC) { + *pre++ = '.'; + pre += pj_utoa_pad(ptime.msec, pre, 3, '0'); + } + if (log_decor & PJ_LOG_HAS_SENDER) { + enum { SENDER_WIDTH = PJ_LOG_SENDER_WIDTH }; + pj_size_t sender_len = strlen(sender); + if (pre != log_buffer) + *pre++ = ' '; + if (sender_len <= SENDER_WIDTH) { + while (sender_len < SENDER_WIDTH) + *pre++ = ' ', ++sender_len; + while (*sender) + *pre++ = *sender++; + } else { + int i; + for (i = 0; i < SENDER_WIDTH; ++i) + *pre++ = *sender++; + } + } + if (log_decor & PJ_LOG_HAS_THREAD_ID) { + enum { THREAD_WIDTH = PJ_LOG_THREAD_WIDTH }; + const char *thread_name = pj_thread_get_name(pj_thread_this()); + pj_size_t thread_len = strlen(thread_name); + *pre++ = ' '; + if (thread_len <= THREAD_WIDTH) { + while (thread_len < THREAD_WIDTH) + *pre++ = ' ', ++thread_len; + while (*thread_name) + *pre++ = *thread_name++; + } else { + int i; + for (i = 0; i < THREAD_WIDTH; ++i) + *pre++ = *thread_name++; + } + } + + if (log_decor != 0 && log_decor != PJ_LOG_HAS_NEWLINE) + *pre++ = ' '; + + if (log_decor & PJ_LOG_HAS_THREAD_SWC) { + void *current_thread = (void *)pj_thread_this(); + if (current_thread != g_last_thread) { + *pre++ = '!'; + g_last_thread = current_thread; + } else { + *pre++ = ' '; + } + } else if (log_decor & PJ_LOG_HAS_SPACE) { + *pre++ = ' '; + } + +#if PJ_LOG_ENABLE_INDENT + if (log_decor & PJ_LOG_HAS_INDENT) { + int indent = pj_log_get_indent(); + if (indent > 0) { + pj_memset(pre, PJ_LOG_INDENT_CHAR, indent); + pre += indent; + } + } +#endif + + len = (int)(pre - log_buffer); + + /* Print the whole message to the string log_buffer. */ + print_len = pj_ansi_vsnprintf(pre, sizeof(log_buffer) - len, format, marker); + if (print_len < 0) { + level = 1; + print_len = pj_ansi_snprintf(pre, sizeof(log_buffer) - len, ""); + } + if (print_len < 1 || print_len >= (int)(sizeof(log_buffer) - len)) { + print_len = sizeof(log_buffer) - len - 1; + } + len = len + print_len; + if (len > 0 && len < (int)sizeof(log_buffer) - 2) { + if (log_decor & PJ_LOG_HAS_CR) { + log_buffer[len++] = '\r'; + } + if (log_decor & PJ_LOG_HAS_NEWLINE) { + log_buffer[len++] = '\n'; + } + log_buffer[len] = '\0'; + } else { + len = sizeof(log_buffer) - 1; + if (log_decor & PJ_LOG_HAS_CR) { + log_buffer[sizeof(log_buffer) - 3] = '\r'; + } + if (log_decor & PJ_LOG_HAS_NEWLINE) { + log_buffer[sizeof(log_buffer) - 2] = '\n'; + } + log_buffer[sizeof(log_buffer) - 1] = '\0'; + } + + /* It should be safe to resume logging at this point. Application can + * recursively call the logging function inside the callback. + */ + resume_logging(&saved_level); + + if (log_writer) + (*log_writer)(level, log_buffer, len); +} + +/* +PJ_DEF(void) pj_log_0(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 0, format, arg); + va_end(arg); +} +*/ + +PJ_DEF(void) pj_log_1(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 1, format, arg); + va_end(arg); +} +#endif /* PJ_LOG_MAX_LEVEL >= 1 */ + +#if PJ_LOG_MAX_LEVEL >= 2 +PJ_DEF(void) pj_log_2(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 2, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 3 +PJ_DEF(void) pj_log_3(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 3, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 4 +PJ_DEF(void) pj_log_4(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 4, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 5 +PJ_DEF(void) pj_log_5(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 5, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 6 +PJ_DEF(void) pj_log_6(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 6, format, arg); + va_end(arg); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c b/src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c new file mode 100755 index 000000000..2fd44ecb0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +static void term_set_color(int level) +{ +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 + pj_term_set_color(pj_log_get_color(level)); +#else + PJ_UNUSED_ARG(level); +#endif +} + +static void term_restore_color(void) +{ +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 + /* Set terminal to its default color */ + pj_term_set_color(pj_log_get_color(77)); +#endif +} + +PJ_DEF(void) pj_log_write(int level, const char *buffer, int len) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(len); + + /* Copy to terminal/file. */ + if (pj_log_get_decor() & PJ_LOG_HAS_COLOR) { + term_set_color(level); + printf("%s", buffer); + term_restore_color(); + } else { + printf("%s", buffer); + } +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c new file mode 100755 index 000000000..3d38073a4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c @@ -0,0 +1,2055 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * Contributors: + * - Thanks for Zetron, Inc. (Phil Torre, ptorre@zetron.com) for donating + * the RTEMS port. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H != 0 +#include +#endif + +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 +#include +#endif + +#include // getpid() +#include // errno + +#include +#if defined(PJ_HAS_PTHREAD_NP_H) && PJ_HAS_PTHREAD_NP_H != 0 +#include +#endif +#include + +#define THIS_FILE "os_core_unix.c" + +#define SIGNATURE1 0xDEAFBEEF +#define SIGNATURE2 0xDEADC0DE + +#ifndef PJ_JNI_HAS_JNI_ONLOAD +#define PJ_JNI_HAS_JNI_ONLOAD PJ_ANDROID +#endif + +#if defined(PJ_JNI_HAS_JNI_ONLOAD) && PJ_JNI_HAS_JNI_ONLOAD != 0 + +#include + +JavaVM *pj_jni_jvm = NULL; + +JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + pj_jni_jvm = vm; + + return JNI_VERSION_1_4; +} + +#endif + +struct pj_thread_t { + char obj_name[PJ_MAX_OBJ_NAME]; + pthread_t thread; + pj_thread_proc *proc; + void *arg; + pj_uint32_t signature1; + pj_uint32_t signature2; + + pj_mutex_t *suspended_mutex; + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + pj_uint32_t stk_size; + pj_uint32_t stk_max_usage; + char *stk_start; + const char *caller_file; + int caller_line; +#endif +}; + +struct pj_atomic_t { + pj_mutex_t *mutex; + pj_atomic_value_t value; +}; + +struct pj_mutex_t { + pthread_mutex_t mutex; + char obj_name[PJ_MAX_OBJ_NAME]; +#if PJ_DEBUG + int nesting_level; + pj_thread_t *owner; + char owner_name[PJ_MAX_OBJ_NAME]; +#endif +}; + +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +struct pj_sem_t { +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + dispatch_semaphore_t sem; +#else + sem_t *sem; +#endif + char obj_name[PJ_MAX_OBJ_NAME]; +}; +#endif /* PJ_HAS_SEMAPHORE */ + +#if defined(PJ_HAS_EVENT_OBJ) && PJ_HAS_EVENT_OBJ != 0 +struct pj_event_t { + enum event_state { EV_STATE_OFF, EV_STATE_SET, EV_STATE_PULSED } state; + + pj_mutex_t mutex; + pthread_cond_t cond; + + pj_bool_t auto_reset; + unsigned threads_waiting; + unsigned threads_to_release; +}; +#endif /* PJ_HAS_EVENT_OBJ */ + +/* + * Flag and reference counter for PJLIB instance. + */ +static int initialized; + +#if PJ_HAS_THREADS +static pj_thread_t main_thread; +static long thread_tls_id; +static pj_mutex_t critical_section; +#else +#define MAX_THREADS 32 +static int tls_flag[MAX_THREADS]; +static void *tls[MAX_THREADS]; +#endif + +static unsigned atexit_count; +static void (*atexit_func[32])(void); + +static pj_status_t init_mutex(pj_mutex_t *mutex, const char *name, int type); + +/* + * pj_init(void). + * Init PJLIB! + */ +PJ_DEF(pj_status_t) pj_init(void) +{ + char dummy_guid[PJ_GUID_MAX_LENGTH]; + pj_str_t guid; + pj_status_t rc; + + /* Check if PJLIB have been initialized */ + if (initialized) { + ++initialized; + return PJ_SUCCESS; + } + + /* Init logging */ + pj_log_init(); + +#if PJ_HAS_THREADS + /* Init this thread's TLS. */ + if ((rc = pj_thread_init()) != 0) { + return rc; + } + + /* Critical section. */ + if ((rc = init_mutex(&critical_section, "critsec", PJ_MUTEX_RECURSE)) != 0) + return rc; + +#endif + + /* Initialize exception ID for the pool. + * Must do so after critical section is configured. + */ + rc = pj_exception_id_alloc("PJLIB/No memory", &PJ_NO_MEMORY_EXCEPTION); + if (rc != PJ_SUCCESS) + return rc; + + /* Init random seed. */ + /* Or probably not. Let application in charge of this */ + /* pj_srand( clock() ); */ + + /* Startup GUID. */ + guid.ptr = dummy_guid; + pj_generate_unique_string(&guid); + + /* Startup timestamp */ +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 + { + pj_timestamp dummy_ts; + if ((rc = pj_get_timestamp(&dummy_ts)) != 0) { + return rc; + } + } +#endif + + /* Flag PJLIB as initialized */ + ++initialized; + pj_assert(initialized == 1); + + PJ_LOG(4, (THIS_FILE, "pjlib %s for POSIX initialized", PJ_VERSION)); + + return PJ_SUCCESS; +} + +/* + * pj_atexit() + */ +PJ_DEF(pj_status_t) pj_atexit(void (*func)(void)) +{ + if (atexit_count >= PJ_ARRAY_SIZE(atexit_func)) + return PJ_ETOOMANY; + + atexit_func[atexit_count++] = func; + return PJ_SUCCESS; +} + +/* + * pj_shutdown(void) + */ +PJ_DEF(void) pj_shutdown() +{ + int i; + + /* Only perform shutdown operation when 'initialized' reaches zero */ + pj_assert(initialized > 0); + if (--initialized != 0) + return; + + /* Call atexit() functions */ + for (i = atexit_count - 1; i >= 0; --i) { + (*atexit_func[i])(); + } + atexit_count = 0; + + /* Free exception ID */ + if (PJ_NO_MEMORY_EXCEPTION != -1) { + pj_exception_id_free(PJ_NO_MEMORY_EXCEPTION); + PJ_NO_MEMORY_EXCEPTION = -1; + } + +#if PJ_HAS_THREADS + /* Destroy PJLIB critical section */ + pj_mutex_destroy(&critical_section); + + /* Free PJLIB TLS */ + if (thread_tls_id != -1) { + pj_thread_local_free(thread_tls_id); + thread_tls_id = -1; + } + + /* Ticket #1132: Assertion when (re)starting PJLIB on different thread */ + pj_bzero(&main_thread, sizeof(main_thread)); +#endif + + /* Clear static variables */ + pj_errno_clear_handlers(); +} + +/* + * pj_getpid(void) + */ +PJ_DEF(pj_uint32_t) pj_getpid(void) +{ + PJ_CHECK_STACK(); + return getpid(); +} + +/* + * Check if this thread has been registered to PJLIB. + */ +PJ_DEF(pj_bool_t) pj_thread_is_registered(void) +{ +#if PJ_HAS_THREADS + return pj_thread_local_get(thread_tls_id) != 0; +#else + pj_assert("pj_thread_is_registered() called in non-threading mode!"); + return PJ_TRUE; +#endif +} + +/* Thread priority utils for Android (via JNI as NDK does not provide it). + * Set priority is probably not enough because it does not change the thread + * group in scheduler. + * Temporary solution is to call the Java API to set the thread priority. + * A cool solution would be to port (if possible) the code from the + * android os regarding set_sched groups. + */ +#if PJ_ANDROID + +#include +#include +#include + +PJ_DEF(pj_bool_t) pj_jni_attach_jvm(JNIEnv **jni_env) +{ + if ((*pj_jni_jvm)->GetEnv(pj_jni_jvm, (void **)jni_env, JNI_VERSION_1_4) < 0) { + if ((*pj_jni_jvm)->AttachCurrentThread(pj_jni_jvm, jni_env, NULL) < 0) { + jni_env = NULL; + return PJ_FALSE; + } + return PJ_TRUE; + } + + return PJ_FALSE; +} + +PJ_DEF(void) pj_jni_dettach_jvm(pj_bool_t attached) +{ + if (attached) + (*pj_jni_jvm)->DetachCurrentThread(pj_jni_jvm); +} + +static pj_status_t set_android_thread_priority(int priority) +{ + jclass process_class; + jmethodID set_prio_method; + jthrowable exc; + pj_status_t result = PJ_SUCCESS; + JNIEnv *jni_env = 0; + pj_bool_t attached = pj_jni_attach_jvm(&jni_env); + + PJ_ASSERT_RETURN(jni_env, PJ_FALSE); + + /* Get pointer to the java class */ + process_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, (*jni_env)->FindClass(jni_env, "android/os/Process")); + if (process_class == 0) { + PJ_LOG(5, (THIS_FILE, "Unable to find class android.os.Process")); + result = PJ_EIGNORED; + goto on_return; + } + + /* Get the id of set thread priority function */ + set_prio_method = (*jni_env)->GetStaticMethodID(jni_env, process_class, "setThreadPriority", "(I)V"); + if (set_prio_method == 0) { + PJ_LOG(5, (THIS_FILE, "Unable to find setThreadPriority() method")); + result = PJ_EIGNORED; + goto on_return; + } + + /* Set the thread priority */ + (*jni_env)->CallStaticVoidMethod(jni_env, process_class, set_prio_method, priority); + exc = (*jni_env)->ExceptionOccurred(jni_env); + if (exc) { + (*jni_env)->ExceptionDescribe(jni_env); + (*jni_env)->ExceptionClear(jni_env); + PJ_LOG(4, (THIS_FILE, "Failure in setting thread priority using " + "Java API, fallback to setpriority()")); + setpriority(PRIO_PROCESS, 0, priority); + } else { + PJ_LOG(5, (THIS_FILE, "Setting thread priority to %d successful", priority)); + } + +on_return: + pj_jni_dettach_jvm(attached); + return result; +} + +#endif + +/* + * Get thread priority value for the thread. + */ +PJ_DEF(int) pj_thread_get_prio(pj_thread_t *thread) +{ +#if PJ_HAS_THREADS + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return -1; + + return param.sched_priority; +#else + PJ_UNUSED_ARG(thread); + return 1; +#endif +} + +/* + * Set the thread priority. + */ +PJ_DEF(pj_status_t) pj_thread_set_prio(pj_thread_t *thread, int prio) +{ +#if PJ_HAS_THREADS + +#if PJ_ANDROID + PJ_ASSERT_RETURN(thread == NULL || thread == pj_thread_this(), PJ_EINVAL); + return set_android_thread_priority(prio); +#else + + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + + param.sched_priority = prio; + + rc = pthread_setschedparam(thread->thread, policy, ¶m); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + + return PJ_SUCCESS; + +#endif /* PJ_ANDROID */ + +#else + PJ_UNUSED_ARG(thread); + PJ_UNUSED_ARG(prio); + pj_assert("pj_thread_set_prio() called in non-threading mode!"); + return 1; +#endif +} + +/* + * Get the lowest priority value available on this system. + */ +PJ_DEF(int) pj_thread_get_prio_min(pj_thread_t *thread) +{ + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return -1; + +#if defined(_POSIX_PRIORITY_SCHEDULING) + return sched_get_priority_min(policy); +#elif defined __OpenBSD__ + /* Thread prio min/max are declared in OpenBSD private hdr */ + return 0; +#else + pj_assert("pj_thread_get_prio_min() not supported!"); + return 0; +#endif +} + +/* + * Get the highest priority value available on this system. + */ +PJ_DEF(int) pj_thread_get_prio_max(pj_thread_t *thread) +{ + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return -1; + +#if defined(_POSIX_PRIORITY_SCHEDULING) + return sched_get_priority_max(policy); +#elif defined __OpenBSD__ + /* Thread prio min/max are declared in OpenBSD private hdr */ + return 31; +#else + pj_assert("pj_thread_get_prio_max() not supported!"); + return 0; +#endif +} + +/* + * Get native thread handle + */ +PJ_DEF(void *) pj_thread_get_os_handle(pj_thread_t *thread) +{ + PJ_ASSERT_RETURN(thread, NULL); + +#if PJ_HAS_THREADS + return &thread->thread; +#else + pj_assert("pj_thread_is_registered() called in non-threading mode!"); + return NULL; +#endif +} + +/* + * pj_thread_register(..) + */ +PJ_DEF(pj_status_t) pj_thread_register(const char *cstr_thread_name, pj_thread_desc desc, pj_thread_t **ptr_thread) +{ +#if PJ_HAS_THREADS + char stack_ptr; + pj_status_t rc; + pj_thread_t *thread = (pj_thread_t *)desc; + pj_str_t thread_name = pj_str((char *)cstr_thread_name); + + /* Size sanity check. */ + if (sizeof(pj_thread_desc) < sizeof(pj_thread_t)) { + pj_assert(!"Not enough pj_thread_desc size!"); + return PJ_EBUG; + } + + /* Warn if this thread has been registered before */ + if (pj_thread_local_get(thread_tls_id) != 0) { + // 2006-02-26 bennylp: + // This wouldn't work in all cases!. + // If thread is created by external module (e.g. sound thread), + // thread may be reused while the pool used for the thread descriptor + // has been deleted by application. + //*thread_ptr = (pj_thread_t*)pj_thread_local_get (thread_tls_id); + // return PJ_SUCCESS; + PJ_LOG(4, (THIS_FILE, "Info: possibly re-registering existing " + "thread")); + } + + /* On the other hand, also warn if the thread descriptor buffer seem to + * have been used to register other threads. + */ + pj_assert(thread->signature1 != SIGNATURE1 || thread->signature2 != SIGNATURE2 || + (thread->thread == pthread_self())); + + /* Initialize and set the thread entry. */ + pj_bzero(desc, sizeof(struct pj_thread_t)); + thread->thread = pthread_self(); + thread->signature1 = SIGNATURE1; + thread->signature2 = SIGNATURE2; + + if (cstr_thread_name && pj_strlen(&thread_name) < sizeof(thread->obj_name) - 1) + pj_ansi_snprintf(thread->obj_name, sizeof(thread->obj_name), cstr_thread_name, thread->thread); + else + pj_ansi_snprintf(thread->obj_name, sizeof(thread->obj_name), "thr%p", (void *)thread->thread); + + rc = pj_thread_local_set(thread_tls_id, thread); + if (rc != PJ_SUCCESS) { + pj_bzero(desc, sizeof(struct pj_thread_t)); + return rc; + } + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + thread->stk_start = &stack_ptr; + thread->stk_size = 0xFFFFFFFFUL; + thread->stk_max_usage = 0; +#else + PJ_UNUSED_ARG(stack_ptr); +#endif + + *ptr_thread = thread; + return PJ_SUCCESS; +#else + pj_thread_t *thread = (pj_thread_t *)desc; + *ptr_thread = thread; + return PJ_SUCCESS; +#endif +} + +/* + * pj_thread_init(void) + */ +pj_status_t pj_thread_init(void) +{ +#if PJ_HAS_THREADS + pj_status_t rc; + pj_thread_t *dummy; + + rc = pj_thread_local_alloc(&thread_tls_id); + if (rc != PJ_SUCCESS) { + return rc; + } + return pj_thread_register("thr%p", (long *)&main_thread, &dummy); +#else + PJ_LOG(2, (THIS_FILE, "Thread init error. Threading is not enabled!")); + return PJ_EINVALIDOP; +#endif +} + +#if PJ_HAS_THREADS + +/* + * Set current thread display name + * This can be useful for debugging, as the name is displayed in the thread status + */ +static void set_thread_display_name(const char *name) +{ +#if (defined(PJ_LINUX) && PJ_LINUX != 0) || (defined(PJ_ANDROID) && PJ_ANDROID != 0) + char xname[16]; + // On linux, thread display name length is restricted to 16 (include '\0') + if (pj_ansi_strlen(name) >= 16) { + pj_ansi_snprintf(xname, 16, "%s", name); + name = xname; + } +#endif + +#if defined(PJ_HAS_PTHREAD_SETNAME_NP) && PJ_HAS_PTHREAD_SETNAME_NP != 0 +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 + pthread_setname_np(name); +#else + pthread_setname_np(pthread_self(), name); +#endif +#elif defined(PJ_HAS_PTHREAD_SET_NAME_NP) && PJ_HAS_PTHREAD_SET_NAME_NP != 0 + pthread_set_name_np(pthread_self(), name); +#else +#warning "OS not support set thread display name" + PJ_UNUSED_ARG(name); +#endif +} + +/* + * thread_main() + * + * This is the main entry for all threads. + */ +static void *thread_main(void *param) +{ + pj_thread_t *rec = (pj_thread_t *)param; + void *result; + pj_status_t rc; + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + rec->stk_start = (char *)&rec; +#endif + + /* Set current thread id. */ + rc = pj_thread_local_set(thread_tls_id, rec); + if (rc != PJ_SUCCESS) { + pj_assert(!"Thread TLS ID is not set (pj_init() error?)"); + } + + /* Check if suspension is required. */ + if (rec->suspended_mutex) { + pj_mutex_lock(rec->suspended_mutex); + pj_mutex_unlock(rec->suspended_mutex); + } + + PJ_LOG(6, (rec->obj_name, "Thread started")); + + set_thread_display_name(rec->obj_name); + + /* Call user's entry! */ + result = (void *)(long)(*rec->proc)(rec->arg); + + /* Done. */ + PJ_LOG(6, (rec->obj_name, "Thread quitting")); + + return result; +} +#endif + +/* + * pj_thread_create(...) + */ +PJ_DEF(pj_status_t) +pj_thread_create(pj_pool_t *pool, const char *thread_name, pj_thread_proc *proc, void *arg, pj_size_t stack_size, + unsigned flags, pj_thread_t **ptr_thread) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec; + pthread_attr_t thread_attr; + void *stack_addr; + int rc; + + PJ_UNUSED_ARG(stack_addr); + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(pool && proc && ptr_thread, PJ_EINVAL); + + /* Create thread record and assign name for the thread */ + rec = (struct pj_thread_t *)pj_pool_zalloc(pool, sizeof(pj_thread_t)); + PJ_ASSERT_RETURN(rec, PJ_ENOMEM); + + /* Set name. */ + if (!thread_name) + thread_name = "thr%p"; + + if (strchr(thread_name, '%')) { + pj_ansi_snprintf(rec->obj_name, PJ_MAX_OBJ_NAME, thread_name, rec); + } else { + strncpy(rec->obj_name, thread_name, PJ_MAX_OBJ_NAME); + rec->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + + /* Set default stack size */ + if (stack_size == 0) + stack_size = PJ_THREAD_DEFAULT_STACK_SIZE; + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + rec->stk_size = stack_size; + rec->stk_max_usage = 0; +#endif + + /* Emulate suspended thread with mutex. */ + if (flags & PJ_THREAD_SUSPENDED) { + rc = pj_mutex_create_simple(pool, NULL, &rec->suspended_mutex); + if (rc != PJ_SUCCESS) { + return rc; + } + + pj_mutex_lock(rec->suspended_mutex); + } else { + pj_assert(rec->suspended_mutex == NULL); + } + + /* Init thread attributes */ + pthread_attr_init(&thread_attr); + +#if defined(PJ_THREAD_SET_STACK_SIZE) && PJ_THREAD_SET_STACK_SIZE != 0 + /* Set thread's stack size */ + rc = pthread_attr_setstacksize(&thread_attr, stack_size); + if (rc != 0) { + pthread_attr_destroy(&thread_attr); + return PJ_RETURN_OS_ERROR(rc); + } +#endif /* PJ_THREAD_SET_STACK_SIZE */ + +#if defined(PJ_THREAD_ALLOCATE_STACK) && PJ_THREAD_ALLOCATE_STACK != 0 + /* Allocate memory for the stack */ + stack_addr = pj_pool_alloc(pool, stack_size); + PJ_ASSERT_RETURN(stack_addr, PJ_ENOMEM); + + rc = pthread_attr_setstackaddr(&thread_attr, stack_addr); + if (rc != 0) { + pthread_attr_destroy(&thread_attr); + return PJ_RETURN_OS_ERROR(rc); + } +#endif /* PJ_THREAD_ALLOCATE_STACK */ + + /* Create the thread. */ + rec->proc = proc; + rec->arg = arg; + rc = pthread_create(&rec->thread, &thread_attr, &thread_main, rec); + if (rc != 0) { + pthread_attr_destroy(&thread_attr); + return PJ_RETURN_OS_ERROR(rc); + } + + /* Destroy thread attributes */ + pthread_attr_destroy(&thread_attr); + + *ptr_thread = rec; + + PJ_LOG(6, (rec->obj_name, "Thread created")); + return PJ_SUCCESS; +#else + pj_assert(!"Threading is disabled!"); + return PJ_EINVALIDOP; +#endif +} + +/* + * pj_thread-get_name() + */ +PJ_DEF(const char *) pj_thread_get_name(pj_thread_t *p) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec = (pj_thread_t *)p; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(p, ""); + + return rec->obj_name; +#else + return ""; +#endif +} + +/* + * pj_thread_resume() + */ +PJ_DEF(pj_status_t) pj_thread_resume(pj_thread_t *p) +{ + pj_status_t rc; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(p, PJ_EINVAL); + + rc = pj_mutex_unlock(p->suspended_mutex); + + return rc; +} + +/* + * pj_thread_this() + */ +PJ_DEF(pj_thread_t *) pj_thread_this(void) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec = (pj_thread_t *)pj_thread_local_get(thread_tls_id); + + if (rec == NULL) { + pj_assert(!"Calling pjlib from unknown/external thread. You must " + "register external threads with pj_thread_register() " + "before calling any pjlib functions."); + } + + /* + * MUST NOT check stack because this function is called + * by PJ_CHECK_STACK() itself!!! + * + */ + + return rec; +#else + pj_assert(!"Threading is not enabled!"); + return NULL; +#endif +} + +/* + * pj_thread_join() + */ +PJ_DEF(pj_status_t) pj_thread_join(pj_thread_t *p) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec = (pj_thread_t *)p; + void *ret; + int result; + + PJ_CHECK_STACK(); + + if (p == pj_thread_this()) + return PJ_ECANCELLED; + + PJ_LOG(6, (pj_thread_this()->obj_name, "Joining thread %s", p->obj_name)); + result = pthread_join(rec->thread, &ret); + + if (result == 0) + return PJ_SUCCESS; + else { + /* Calling pthread_join() on a thread that no longer exists and + * getting back ESRCH isn't an error (in this context). + * Thanks Phil Torre . + */ + return result == ESRCH ? PJ_SUCCESS : PJ_RETURN_OS_ERROR(result); + } +#else + PJ_CHECK_STACK(); + pj_assert(!"No multithreading support!"); + return PJ_EINVALIDOP; +#endif +} + +/* + * pj_thread_destroy() + */ +PJ_DEF(pj_status_t) pj_thread_destroy(pj_thread_t *p) +{ + PJ_CHECK_STACK(); + + /* Destroy mutex used to suspend thread */ + if (p->suspended_mutex) { + pj_mutex_destroy(p->suspended_mutex); + p->suspended_mutex = NULL; + } + + return PJ_SUCCESS; +} + +/* + * pj_thread_sleep() + */ +PJ_DEF(pj_status_t) pj_thread_sleep(unsigned msec) +{ +/* TODO: should change this to something like PJ_OS_HAS_NANOSLEEP */ +#if defined(PJ_RTEMS) && PJ_RTEMS != 0 + enum { NANOSEC_PER_MSEC = 1000000 }; + struct timespec req; + + PJ_CHECK_STACK(); + req.tv_sec = msec / 1000; + req.tv_nsec = (msec % 1000) * NANOSEC_PER_MSEC; + + if (nanosleep(&req, NULL) == 0) + return PJ_SUCCESS; + + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + PJ_CHECK_STACK(); + + pj_set_os_error(0); + + usleep(msec * 1000); + + /* MacOS X (reported on 10.5) seems to always set errno to ETIMEDOUT. + * It does so because usleep() is declared to return int, and we're + * supposed to check for errno only when usleep() returns non-zero. + * Unfortunately, usleep() is declared to return void in other platforms + * so it's not possible to always check for the return value (unless + * we add a detection routine in autoconf). + * + * As a workaround, here we check if ETIMEDOUT is returned and + * return successfully if it is. + */ + if (pj_get_native_os_error() == ETIMEDOUT) + return PJ_SUCCESS; + + return pj_get_os_error(); + +#endif /* PJ_RTEMS */ +} + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 +/* + * pj_thread_check_stack() + * Implementation for PJ_CHECK_STACK() + */ +PJ_DEF(void) pj_thread_check_stack(const char *file, int line) +{ + char stk_ptr; + pj_uint32_t usage; + pj_thread_t *thread = pj_thread_this(); + + /* Calculate current usage. */ + usage = (&stk_ptr > thread->stk_start) ? &stk_ptr - thread->stk_start : thread->stk_start - &stk_ptr; + + /* Assert if stack usage is dangerously high. */ + pj_assert("STACK OVERFLOW!! " && (usage <= thread->stk_size - 128)); + + /* Keep statistic. */ + if (usage > thread->stk_max_usage) { + thread->stk_max_usage = usage; + thread->caller_file = file; + thread->caller_line = line; + } +} + +/* + * pj_thread_get_stack_max_usage() + */ +PJ_DEF(pj_uint32_t) pj_thread_get_stack_max_usage(pj_thread_t *thread) +{ + return thread->stk_max_usage; +} + +/* + * pj_thread_get_stack_info() + */ +PJ_DEF(pj_status_t) pj_thread_get_stack_info(pj_thread_t *thread, const char **file, int *line) +{ + pj_assert(thread); + + *file = thread->caller_file; + *line = thread->caller_line; + return 0; +} + +#endif /* PJ_OS_HAS_CHECK_STACK */ + +/////////////////////////////////////////////////////////////////////////////// +/* + * pj_atomic_create() + */ +PJ_DEF(pj_status_t) pj_atomic_create(pj_pool_t *pool, pj_atomic_value_t initial, pj_atomic_t **ptr_atomic) +{ + pj_status_t rc; + pj_atomic_t *atomic_var; + + atomic_var = PJ_POOL_ZALLOC_T(pool, pj_atomic_t); + + PJ_ASSERT_RETURN(atomic_var, PJ_ENOMEM); + +#if PJ_HAS_THREADS + rc = pj_mutex_create(pool, "atm%p", PJ_MUTEX_SIMPLE, &atomic_var->mutex); + if (rc != PJ_SUCCESS) + return rc; +#endif + atomic_var->value = initial; + + *ptr_atomic = atomic_var; + return PJ_SUCCESS; +} + +/* + * pj_atomic_destroy() + */ +PJ_DEF(pj_status_t) pj_atomic_destroy(pj_atomic_t *atomic_var) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(atomic_var, PJ_EINVAL); + +#if PJ_HAS_THREADS + status = pj_mutex_destroy(atomic_var->mutex); + if (status == PJ_SUCCESS) { + atomic_var->mutex = NULL; + } + return status; +#else + return 0; +#endif +} + +/* + * pj_atomic_set() + */ +PJ_DEF(void) pj_atomic_set(pj_atomic_t *atomic_var, pj_atomic_value_t value) +{ + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_ON_FAIL(atomic_var, return ); + +#if PJ_HAS_THREADS + status = pj_mutex_lock(atomic_var->mutex); + if (status != PJ_SUCCESS) { + return; + } +#endif + atomic_var->value = value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif +} + +/* + * pj_atomic_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_get(pj_atomic_t *atomic_var) +{ + pj_atomic_value_t oldval; + + PJ_CHECK_STACK(); + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + oldval = atomic_var->value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + return oldval; +} + +/* + * pj_atomic_inc_and_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_inc_and_get(pj_atomic_t *atomic_var) +{ + pj_atomic_value_t new_value; + + PJ_CHECK_STACK(); + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + new_value = ++atomic_var->value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + + return new_value; +} +/* + * pj_atomic_inc() + */ +PJ_DEF(void) pj_atomic_inc(pj_atomic_t *atomic_var) +{ + PJ_ASSERT_ON_FAIL(atomic_var, return ); + pj_atomic_inc_and_get(atomic_var); +} + +/* + * pj_atomic_dec_and_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_dec_and_get(pj_atomic_t *atomic_var) +{ + pj_atomic_value_t new_value; + + PJ_CHECK_STACK(); + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + new_value = --atomic_var->value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + + return new_value; +} + +/* + * pj_atomic_dec() + */ +PJ_DEF(void) pj_atomic_dec(pj_atomic_t *atomic_var) +{ + PJ_ASSERT_ON_FAIL(atomic_var, return ); + pj_atomic_dec_and_get(atomic_var); +} + +/* + * pj_atomic_add_and_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_add_and_get(pj_atomic_t *atomic_var, pj_atomic_value_t value) +{ + pj_atomic_value_t new_value; + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + + atomic_var->value += value; + new_value = atomic_var->value; + +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + + return new_value; +} + +/* + * pj_atomic_add() + */ +PJ_DEF(void) pj_atomic_add(pj_atomic_t *atomic_var, pj_atomic_value_t value) +{ + PJ_ASSERT_ON_FAIL(atomic_var, return ); + pj_atomic_add_and_get(atomic_var, value); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * pj_thread_local_alloc() + */ +PJ_DEF(pj_status_t) pj_thread_local_alloc(long *p_index) +{ +#if PJ_HAS_THREADS + pthread_key_t key; + int rc; + + PJ_ASSERT_RETURN(p_index != NULL, PJ_EINVAL); + + pj_assert(sizeof(pthread_key_t) <= sizeof(long)); + if ((rc = pthread_key_create(&key, NULL)) != 0) + return PJ_RETURN_OS_ERROR(rc); + + *p_index = key; + return PJ_SUCCESS; +#else + int i; + for (i = 0; i < MAX_THREADS; ++i) { + if (tls_flag[i] == 0) + break; + } + if (i == MAX_THREADS) + return PJ_ETOOMANY; + + tls_flag[i] = 1; + tls[i] = NULL; + + *p_index = i; + return PJ_SUCCESS; +#endif +} + +/* + * pj_thread_local_free() + */ +PJ_DEF(void) pj_thread_local_free(long index) +{ + PJ_CHECK_STACK(); +#if PJ_HAS_THREADS + pthread_key_delete(index); +#else + tls_flag[index] = 0; +#endif +} + +/* + * pj_thread_local_set() + */ +PJ_DEF(pj_status_t) pj_thread_local_set(long index, void *value) +{ + // Can't check stack because this function is called in the + // beginning before main thread is initialized. + // PJ_CHECK_STACK(); +#if PJ_HAS_THREADS + int rc = pthread_setspecific(index, value); + return rc == 0 ? PJ_SUCCESS : PJ_RETURN_OS_ERROR(rc); +#else + pj_assert(index >= 0 && index < MAX_THREADS); + tls[index] = value; + return PJ_SUCCESS; +#endif +} + +PJ_DEF(void *) pj_thread_local_get(long index) +{ + // Can't check stack because this function is called + // by PJ_CHECK_STACK() itself!!! + // PJ_CHECK_STACK(); +#if PJ_HAS_THREADS + return pthread_getspecific(index); +#else + pj_assert(index >= 0 && index < MAX_THREADS); + return tls[index]; +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +PJ_DEF(void) pj_enter_critical_section(void) +{ +#if PJ_HAS_THREADS + pj_mutex_lock(&critical_section); +#endif +} + +PJ_DEF(void) pj_leave_critical_section(void) +{ +#if PJ_HAS_THREADS + pj_mutex_unlock(&critical_section); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_LINUX) && PJ_LINUX != 0 +PJ_BEGIN_DECL +PJ_DECL(int) pthread_mutexattr_settype(pthread_mutexattr_t *, int); +PJ_END_DECL +#endif + +static pj_status_t init_mutex(pj_mutex_t *mutex, const char *name, int type) +{ +#if PJ_HAS_THREADS + pthread_mutexattr_t attr; + int rc; + + PJ_CHECK_STACK(); + + rc = pthread_mutexattr_init(&attr); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + + if (type == PJ_MUTEX_SIMPLE) { +#if (defined(PJ_LINUX) && PJ_LINUX != 0) || defined(PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE) + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); +#elif (defined(PJ_RTEMS) && PJ_RTEMS != 0) || defined(PJ_PTHREAD_MUTEXATTR_T_HAS_RECURSIVE) + /* Nothing to do, default is simple */ +#else + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); +#endif + } else { +#if (defined(PJ_LINUX) && PJ_LINUX != 0) || defined(PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE) + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#elif (defined(PJ_RTEMS) && PJ_RTEMS != 0) || defined(PJ_PTHREAD_MUTEXATTR_T_HAS_RECURSIVE) + // Phil Torre : + // The RTEMS implementation of POSIX mutexes doesn't include + // pthread_mutexattr_settype(), so what follows is a hack + // until I get RTEMS patched to support the set/get functions. + // + // More info: + // newlib's pthread also lacks pthread_mutexattr_settype(), + // but it seems to have mutexattr.recursive. + PJ_TODO(FIX_RTEMS_RECURSIVE_MUTEX_TYPE) + attr.recursive = 1; +#else + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#endif + } + + if (rc != 0) { + return PJ_RETURN_OS_ERROR(rc); + } + + rc = pthread_mutex_init(&mutex->mutex, &attr); + if (rc != 0) { + return PJ_RETURN_OS_ERROR(rc); + } + + rc = pthread_mutexattr_destroy(&attr); + if (rc != 0) { + pj_status_t status = PJ_RETURN_OS_ERROR(rc); + pthread_mutex_destroy(&mutex->mutex); + return status; + } + +#if PJ_DEBUG + /* Set owner. */ + mutex->nesting_level = 0; + mutex->owner = NULL; + mutex->owner_name[0] = '\0'; +#endif + + /* Set name. */ + if (!name) { + name = "mtx%p"; + } + if (strchr(name, '%')) { + pj_ansi_snprintf(mutex->obj_name, PJ_MAX_OBJ_NAME, name, mutex); + } else { + strncpy(mutex->obj_name, name, PJ_MAX_OBJ_NAME); + mutex->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + + PJ_LOG(6, (mutex->obj_name, "Mutex created")); + return PJ_SUCCESS; +#else /* PJ_HAS_THREADS */ + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_create() + */ +PJ_DEF(pj_status_t) pj_mutex_create(pj_pool_t *pool, const char *name, int type, pj_mutex_t **ptr_mutex) +{ +#if PJ_HAS_THREADS + pj_status_t rc; + pj_mutex_t *mutex; + + PJ_ASSERT_RETURN(pool && ptr_mutex, PJ_EINVAL); + + mutex = PJ_POOL_ALLOC_T(pool, pj_mutex_t); + PJ_ASSERT_RETURN(mutex, PJ_ENOMEM); + + if ((rc = init_mutex(mutex, name, type)) != PJ_SUCCESS) + return rc; + + *ptr_mutex = mutex; + return PJ_SUCCESS; +#else /* PJ_HAS_THREADS */ + *ptr_mutex = (pj_mutex_t *)1; + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_create_simple() + */ +PJ_DEF(pj_status_t) pj_mutex_create_simple(pj_pool_t *pool, const char *name, pj_mutex_t **mutex) +{ + return pj_mutex_create(pool, name, PJ_MUTEX_SIMPLE, mutex); +} + +/* + * pj_mutex_create_recursive() + */ +PJ_DEF(pj_status_t) pj_mutex_create_recursive(pj_pool_t *pool, const char *name, pj_mutex_t **mutex) +{ + return pj_mutex_create(pool, name, PJ_MUTEX_RECURSE, mutex); +} + +/* + * pj_mutex_lock() + */ +PJ_DEF(pj_status_t) pj_mutex_lock(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + +#if PJ_DEBUG + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s is waiting (mutex owner=%s)", pj_thread_this()->obj_name, + mutex->owner_name)); +#else + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s is waiting", pj_thread_this()->obj_name)); +#endif + + status = pthread_mutex_lock(&mutex->mutex); + +#if PJ_DEBUG + if (status == PJ_SUCCESS) { + mutex->owner = pj_thread_this(); + pj_ansi_strcpy(mutex->owner_name, mutex->owner->obj_name); + ++mutex->nesting_level; + } + + PJ_LOG(6, (mutex->obj_name, + (status == 0 ? "Mutex acquired by thread %s (level=%d)" : "Mutex acquisition FAILED by %s (level=%d)"), + pj_thread_this()->obj_name, mutex->nesting_level)); +#else + PJ_LOG(6, (mutex->obj_name, (status == 0 ? "Mutex acquired by thread %s" : "FAILED by %s"), + pj_thread_this()->obj_name)); +#endif + + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); +#else /* PJ_HAS_THREADS */ + pj_assert(mutex == (pj_mutex_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_unlock() + */ +PJ_DEF(pj_status_t) pj_mutex_unlock(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + +#if PJ_DEBUG + pj_assert(mutex->owner == pj_thread_this()); + if (--mutex->nesting_level == 0) { + mutex->owner = NULL; + mutex->owner_name[0] = '\0'; + } + + PJ_LOG(6, (mutex->obj_name, "Mutex released by thread %s (level=%d)", pj_thread_this()->obj_name, + mutex->nesting_level)); +#else + PJ_LOG(6, (mutex->obj_name, "Mutex released by thread %s", pj_thread_this()->obj_name)); +#endif + + status = pthread_mutex_unlock(&mutex->mutex); + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); + +#else /* PJ_HAS_THREADS */ + pj_assert(mutex == (pj_mutex_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_trylock() + */ +PJ_DEF(pj_status_t) pj_mutex_trylock(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + int status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s is trying", pj_thread_this()->obj_name)); + + status = pthread_mutex_trylock(&mutex->mutex); + + if (status == 0) { +#if PJ_DEBUG + mutex->owner = pj_thread_this(); + pj_ansi_strcpy(mutex->owner_name, mutex->owner->obj_name); + ++mutex->nesting_level; + + PJ_LOG(6, (mutex->obj_name, "Mutex acquired by thread %s (level=%d)", pj_thread_this()->obj_name, + mutex->nesting_level)); +#else + PJ_LOG(6, (mutex->obj_name, "Mutex acquired by thread %s", pj_thread_this()->obj_name)); +#endif + } else { + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s's trylock() failed", pj_thread_this()->obj_name)); + } + + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); +#else /* PJ_HAS_THREADS */ + pj_assert(mutex == (pj_mutex_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_destroy() + */ +PJ_DEF(pj_status_t) pj_mutex_destroy(pj_mutex_t *mutex) +{ + enum { RETRY = 4 }; + int status = 0; + unsigned retry; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + +#if PJ_HAS_THREADS + PJ_LOG(6, (mutex->obj_name, "Mutex destroyed by thread %s", pj_thread_this()->obj_name)); + + for (retry = 0; retry < RETRY; ++retry) { + status = pthread_mutex_destroy(&mutex->mutex); + if (status == PJ_SUCCESS) + break; + else if (retry < RETRY - 1 && status == EBUSY) + pthread_mutex_unlock(&mutex->mutex); + } + + if (status == 0) + return PJ_SUCCESS; + else { + return PJ_RETURN_OS_ERROR(status); + } +#else + pj_assert(mutex == (pj_mutex_t *)1); + status = PJ_SUCCESS; + return status; +#endif +} + +#if PJ_DEBUG +PJ_DEF(pj_bool_t) pj_mutex_is_locked(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + return mutex->owner == pj_thread_this(); +#else + return 1; +#endif +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/* + * Include Read/Write mutex emulation for POSIX platforms that lack it (e.g. + * RTEMS). Otherwise use POSIX rwlock. + */ +#if defined(PJ_EMULATE_RWMUTEX) && PJ_EMULATE_RWMUTEX != 0 +/* We need semaphore functionality to emulate rwmutex */ +#if !defined(PJ_HAS_SEMAPHORE) || PJ_HAS_SEMAPHORE == 0 +#error "Semaphore support needs to be enabled to emulate rwmutex" +#endif +#include "os_rwmutex.c" +#else +struct pj_rwmutex_t { + pthread_rwlock_t rwlock; +}; + +PJ_DEF(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, pj_rwmutex_t **p_mutex) +{ + pj_rwmutex_t *rwm; + pj_status_t status; + + PJ_UNUSED_ARG(name); + + rwm = PJ_POOL_ALLOC_T(pool, pj_rwmutex_t); + PJ_ASSERT_RETURN(rwm, PJ_ENOMEM); + + status = pthread_rwlock_init(&rwm->rwlock, NULL); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + *p_mutex = rwm; + return PJ_SUCCESS; +} + +/* + * Lock the mutex for reading. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_rdlock(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +/* + * Lock the mutex for writing. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_wrlock(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +/* + * Release read lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex) +{ + return pj_rwmutex_unlock_write(mutex); +} + +/* + * Release write lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_unlock(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +/* + * Destroy reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_destroy(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +#endif /* PJ_EMULATE_RWMUTEX */ + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 + +/* + * pj_sem_create() + */ +PJ_DEF(pj_status_t) pj_sem_create(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_sem_t **ptr_sem) +{ +#if PJ_HAS_THREADS + pj_sem_t *sem; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(pool != NULL && ptr_sem != NULL, PJ_EINVAL); + + sem = PJ_POOL_ALLOC_T(pool, pj_sem_t); + PJ_ASSERT_RETURN(sem, PJ_ENOMEM); + +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + sem->sem = dispatch_semaphore_create(initial); + if (sem->sem == NULL) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + /* MacOS X doesn't support anonymous semaphore */ + { + char sem_name[PJ_GUID_MAX_LENGTH + 1]; + pj_str_t nam; + + /* We should use SEM_NAME_LEN, but this doesn't seem to be + * declared anywhere? The value here is just from trial and error + * to get the longest name supported. + */ +#define MAX_SEM_NAME_LEN 23 + + /* Create a unique name for the semaphore. */ + if (PJ_GUID_STRING_LENGTH <= MAX_SEM_NAME_LEN) { + nam.ptr = sem_name; + pj_generate_unique_string(&nam); + sem_name[nam.slen] = '\0'; + } else { + pj_create_random_string(sem_name, MAX_SEM_NAME_LEN); + sem_name[MAX_SEM_NAME_LEN] = '\0'; + } + + /* Create semaphore */ + sem->sem = sem_open(sem_name, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, initial); + if (sem->sem == SEM_FAILED) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + + /* And immediately release the name as we don't need it */ + sem_unlink(sem_name); + } +#endif +#else + sem->sem = PJ_POOL_ALLOC_T(pool, sem_t); + if (sem_init(sem->sem, 0, initial) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#endif + + /* Set name. */ + if (!name) { + name = "sem%p"; + } + if (strchr(name, '%')) { + pj_ansi_snprintf(sem->obj_name, PJ_MAX_OBJ_NAME, name, sem); + } else { + strncpy(sem->obj_name, name, PJ_MAX_OBJ_NAME); + sem->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + + PJ_LOG(6, (sem->obj_name, "Semaphore created")); + + *ptr_sem = sem; + return PJ_SUCCESS; +#else + *ptr_sem = (pj_sem_t *)1; + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_wait() + */ +PJ_DEF(pj_status_t) pj_sem_wait(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + long result; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + + PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s is waiting", pj_thread_this()->obj_name)); + +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + result = dispatch_semaphore_wait(sem->sem, DISPATCH_TIME_FOREVER); +#else + result = sem_wait(sem->sem); +#endif + + if (result == 0) { + PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); + } else { + PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s FAILED to acquire", pj_thread_this()->obj_name)); + } + + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_trywait() + */ +PJ_DEF(pj_status_t) pj_sem_trywait(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + long result; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + result = dispatch_semaphore_wait(sem->sem, DISPATCH_TIME_NOW); +#else + result = sem_trywait(sem->sem); +#endif + + if (result == 0) { + PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); + } + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_post() + */ +PJ_DEF(pj_status_t) pj_sem_post(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + int result; + PJ_LOG(6, (sem->obj_name, "Semaphore released by thread %s", pj_thread_this()->obj_name)); +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + dispatch_semaphore_signal(sem->sem); + result = 0; +#else + result = sem_post(sem->sem); +#endif + + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_destroy() + */ +PJ_DEF(pj_status_t) pj_sem_destroy(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + int result; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + + PJ_LOG(6, (sem->obj_name, "Semaphore destroyed by thread %s", pj_thread_this()->obj_name)); +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + dispatch_release(sem->sem); + result = 0; +#else + result = sem_close(sem->sem); +#endif +#else + result = sem_destroy(sem->sem); +#endif + + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +#endif /* PJ_HAS_SEMAPHORE */ + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_HAS_EVENT_OBJ) && PJ_HAS_EVENT_OBJ != 0 + +/* + * pj_event_create() + */ +PJ_DEF(pj_status_t) +pj_event_create(pj_pool_t *pool, const char *name, pj_bool_t manual_reset, pj_bool_t initial, pj_event_t **ptr_event) +{ + pj_event_t *event; + + event = PJ_POOL_ALLOC_T(pool, pj_event_t); + + init_mutex(&event->mutex, name, PJ_MUTEX_SIMPLE); + pthread_cond_init(&event->cond, 0); + event->auto_reset = !manual_reset; + event->threads_waiting = 0; + + if (initial) { + event->state = EV_STATE_SET; + event->threads_to_release = 1; + } else { + event->state = EV_STATE_OFF; + event->threads_to_release = 0; + } + + *ptr_event = event; + return PJ_SUCCESS; +} + +static void event_on_one_release(pj_event_t *event) +{ + if (event->state == EV_STATE_SET) { + if (event->auto_reset) { + event->threads_to_release = 0; + event->state = EV_STATE_OFF; + } else { + /* Manual reset remains on */ + } + } else { + if (event->auto_reset) { + /* Only release one */ + event->threads_to_release = 0; + event->state = EV_STATE_OFF; + } else { + event->threads_to_release--; + pj_assert(event->threads_to_release >= 0); + if (event->threads_to_release == 0) + event->state = EV_STATE_OFF; + } + } +} + +/* + * pj_event_wait() + */ +PJ_DEF(pj_status_t) pj_event_wait(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + event->threads_waiting++; + while (event->state == EV_STATE_OFF) + pthread_cond_wait(&event->cond, &event->mutex.mutex); + event->threads_waiting--; + event_on_one_release(event); + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_trywait() + */ +PJ_DEF(pj_status_t) pj_event_trywait(pj_event_t *event) +{ + pj_status_t status; + + pthread_mutex_lock(&event->mutex.mutex); + status = event->state != EV_STATE_OFF ? PJ_SUCCESS : -1; + if (status == PJ_SUCCESS) { + event_on_one_release(event); + } + pthread_mutex_unlock(&event->mutex.mutex); + + return status; +} + +/* + * pj_event_set() + */ +PJ_DEF(pj_status_t) pj_event_set(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + event->threads_to_release = 1; + event->state = EV_STATE_SET; + if (event->auto_reset) + pthread_cond_signal(&event->cond); + else + pthread_cond_broadcast(&event->cond); + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_pulse() + */ +PJ_DEF(pj_status_t) pj_event_pulse(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + if (event->threads_waiting) { + event->threads_to_release = event->auto_reset ? 1 : event->threads_waiting; + event->state = EV_STATE_PULSED; + if (event->threads_to_release == 1) + pthread_cond_signal(&event->cond); + else + pthread_cond_broadcast(&event->cond); + } + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_reset() + */ +PJ_DEF(pj_status_t) pj_event_reset(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + event->state = EV_STATE_OFF; + event->threads_to_release = 0; + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_destroy() + */ +PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) +{ + pj_mutex_destroy(&event->mutex); + pthread_cond_destroy(&event->cond); + return PJ_SUCCESS; +} + +#endif /* PJ_HAS_EVENT_OBJ */ + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 +/* + * Terminal + */ + +/** + * Set terminal color. + */ +PJ_DEF(pj_status_t) pj_term_set_color(pj_color_t color) +{ + /* put bright prefix to ansi_color */ + char ansi_color[12] = "\033[01;3"; + + if (color & PJ_TERM_COLOR_BRIGHT) { + color ^= PJ_TERM_COLOR_BRIGHT; + } else { + strcpy(ansi_color, "\033[00;3"); + } + + switch (color) { + case 0: + /* black color */ + strcat(ansi_color, "0m"); + break; + case PJ_TERM_COLOR_R: + /* red color */ + strcat(ansi_color, "1m"); + break; + case PJ_TERM_COLOR_G: + /* green color */ + strcat(ansi_color, "2m"); + break; + case PJ_TERM_COLOR_B: + /* blue color */ + strcat(ansi_color, "4m"); + break; + case PJ_TERM_COLOR_R | PJ_TERM_COLOR_G: + /* yellow color */ + strcat(ansi_color, "3m"); + break; + case PJ_TERM_COLOR_R | PJ_TERM_COLOR_B: + /* magenta color */ + strcat(ansi_color, "5m"); + break; + case PJ_TERM_COLOR_G | PJ_TERM_COLOR_B: + /* cyan color */ + strcat(ansi_color, "6m"); + break; + case PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B: + /* white color */ + strcat(ansi_color, "7m"); + break; + default: + /* default console color */ + strcpy(ansi_color, "\033[00m"); + break; + } + + fputs(ansi_color, stdout); + + return PJ_SUCCESS; +} + +/** + * Get current terminal foreground color. + */ +PJ_DEF(pj_color_t) pj_term_get_color(void) +{ + return 0; +} + +#endif /* PJ_TERM_HAS_COLOR */ + +#if !defined(PJ_DARWINOS) || PJ_DARWINOS == 0 +/* + * pj_run_app() + */ +PJ_DEF(int) pj_run_app(pj_main_func_ptr main_func, int argc, char *argv[], unsigned flags) +{ + return (*main_func)(argc, argv); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c new file mode 100755 index 000000000..aa86d6753 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(pj_status_t) pj_get_os_error(void) +{ + return PJ_STATUS_FROM_OS(errno); +} + +PJ_DEF(void) pj_set_os_error(pj_status_t code) +{ + errno = PJ_STATUS_TO_OS(code); +} + +PJ_DEF(pj_status_t) pj_get_netos_error(void) +{ + return PJ_STATUS_FROM_OS(errno); +} + +PJ_DEF(void) pj_set_netos_error(pj_status_t code) +{ + errno = PJ_STATUS_TO_OS(code); +} + +PJ_BEGIN_DECL + +PJ_DECL(int) platform_strerror(pj_os_err_type code, char *buf, pj_size_t bufsize); +PJ_END_DECL + +/* + * platform_strerror() + * + * Platform specific error message. This file is called by pj_strerror() + * in errno.c + */ +int platform_strerror(pj_os_err_type os_errcode, char *buf, pj_size_t bufsize) +{ + const char *syserr = strerror(os_errcode); + pj_size_t len = syserr ? strlen(syserr) : 0; + + if (len >= bufsize) + len = bufsize - 1; + if (len > 0) + pj_memcpy(buf, syserr, len); + buf[len] = '\0'; + return len; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c new file mode 100755 index 000000000..701d212a1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* + * FYI these links contain useful infos about predefined macros across + * platforms: + * - http://predef.sourceforge.net/preos.html + */ + +#if defined(PJ_HAS_SYS_UTSNAME_H) && PJ_HAS_SYS_UTSNAME_H != 0 +/* For uname() */ +#include +#include +#define PJ_HAS_UNAME 1 +#endif + +#if defined(PJ_HAS_LIMITS_H) && PJ_HAS_LIMITS_H != 0 +/* Include to get to get various glibc macros. + * See http://predef.sourceforge.net/prelib.html + */ +#include +#endif + +#if defined(_MSC_VER) +/* For all Windows including mobile */ +#include +#endif + +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#include "TargetConditionals.h" +#endif + +#ifndef PJ_SYS_INFO_BUFFER_SIZE +#define PJ_SYS_INFO_BUFFER_SIZE 64 +#endif + +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 && TARGET_OS_IPHONE +#include +#include +void pj_iphone_os_get_sys_info(pj_sys_info *si, pj_str_t *si_buffer); +#endif + +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +PJ_BEGIN_DECL +unsigned pj_symbianos_get_model_info(char *buf, unsigned buf_size); +unsigned pj_symbianos_get_platform_info(char *buf, unsigned buf_size); +void pj_symbianos_get_sdk_info(pj_str_t *name, pj_uint32_t *ver); +PJ_END_DECL +#endif + +static char *ver_info(pj_uint32_t ver, char *buf) +{ + pj_size_t len; + + if (ver == 0) { + *buf = '\0'; + return buf; + } + + sprintf(buf, "-%u.%u", (ver & 0xFF000000) >> 24, (ver & 0x00FF0000) >> 16); + len = strlen(buf); + + if (ver & 0xFFFF) { + sprintf(buf + len, ".%u", (ver & 0xFF00) >> 8); + len = strlen(buf); + + if (ver & 0x00FF) { + sprintf(buf + len, ".%u", (ver & 0xFF)); + } + } + + return buf; +} + +static pj_uint32_t parse_version(char *str) +{ + int i, maxtok; + pj_ssize_t found_idx; + pj_uint32_t version = 0; + pj_str_t in_str = pj_str(str); + pj_str_t token, delim; + + while (*str && !pj_isdigit(*str)) + str++; + + maxtok = 4; + delim = pj_str(".-"); + for (found_idx = pj_strtok(&in_str, &delim, &token, 0), i = 0; found_idx != in_str.slen && i < maxtok; + ++i, found_idx = pj_strtok(&in_str, &delim, &token, found_idx + token.slen)) { + int n; + + if (!pj_isdigit(*token.ptr)) + break; + + n = atoi(token.ptr); + version |= (n << ((3 - i) * 8)); + } + + return version; +} + +PJ_DEF(const pj_sys_info *) pj_get_sys_info(void) +{ + static char si_buffer[PJ_SYS_INFO_BUFFER_SIZE]; + static pj_sys_info si; + static pj_bool_t si_initialized; + pj_size_t left = PJ_SYS_INFO_BUFFER_SIZE, len; + + if (si_initialized) + return &si; + + si.machine.ptr = si.os_name.ptr = si.sdk_name.ptr = si.info.ptr = ""; + +#define ALLOC_CP_STR(str, field) \ + do { \ + len = pj_ansi_strlen(str); \ + if (len && left >= len + 1) { \ + si.field.ptr = si_buffer + PJ_SYS_INFO_BUFFER_SIZE - left; \ + si.field.slen = len; \ + pj_memcpy(si.field.ptr, str, len + 1); \ + left -= (len + 1); \ + } \ + } while (0) + + /* + * Machine and OS info. + */ +#if defined(PJ_HAS_UNAME) && PJ_HAS_UNAME +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 && TARGET_OS_IPHONE && \ + (!defined TARGET_IPHONE_SIMULATOR || TARGET_IPHONE_SIMULATOR == 0) + { + pj_str_t buf = {si_buffer + PJ_SYS_INFO_BUFFER_SIZE - left, left}; + pj_str_t machine = {"arm-", 4}; + pj_str_t sdk_name = {"iOS-SDK", 7}; + size_t size = PJ_SYS_INFO_BUFFER_SIZE - machine.slen; + char tmp[PJ_SYS_INFO_BUFFER_SIZE]; + int name[] = {CTL_HW, HW_MACHINE}; + + pj_iphone_os_get_sys_info(&si, &buf); + left -= si.os_name.slen + 1; + + si.os_ver = parse_version(si.machine.ptr); + + pj_memcpy(tmp, machine.ptr, machine.slen); + sysctl(name, 2, tmp + machine.slen, &size, NULL, 0); + ALLOC_CP_STR(tmp, machine); + si.sdk_name = sdk_name; + +#ifdef PJ_SDK_NAME + pj_memcpy(tmp, PJ_SDK_NAME, pj_ansi_strlen(PJ_SDK_NAME) + 1); + si.sdk_ver = parse_version(tmp); +#endif + } +#else + { + struct utsname u; + + /* Successful uname() returns zero on Linux and positive value + * on OpenSolaris. + */ + if (uname(&u) == -1) + goto get_sdk_info; + + ALLOC_CP_STR(u.machine, machine); + ALLOC_CP_STR(u.sysname, os_name); + + si.os_ver = parse_version(u.release); + } +#endif +#elif defined(_MSC_VER) + { +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + si.os_name = pj_str("winphone"); +#else + OSVERSIONINFO ovi; + + ovi.dwOSVersionInfoSize = sizeof(ovi); + + if (GetVersionEx(&ovi) == FALSE) + goto get_sdk_info; + + si.os_ver = (ovi.dwMajorVersion << 24) | (ovi.dwMinorVersion << 16); +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE + si.os_name = pj_str("wince"); +#else + si.os_name = pj_str("win32"); +#endif +#endif + } + + { + SYSTEM_INFO wsi; + +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + GetNativeSystemInfo(&wsi); +#else + GetSystemInfo(&wsi); +#endif + + switch (wsi.wProcessorArchitecture) { +#if (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE) || (defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8) + case PROCESSOR_ARCHITECTURE_ARM: + si.machine = pj_str("arm"); + break; + case PROCESSOR_ARCHITECTURE_SHX: + si.machine = pj_str("shx"); + break; +#else + case PROCESSOR_ARCHITECTURE_AMD64: + si.machine = pj_str("x86_64"); + break; + case PROCESSOR_ARCHITECTURE_IA64: + si.machine = pj_str("ia64"); + break; + case PROCESSOR_ARCHITECTURE_INTEL: + si.machine = pj_str("i386"); + break; +#endif /* PJ_WIN32_WINCE */ + } +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + /* Avoid compile warning. */ + goto get_sdk_info; +#endif + } +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + { + pj_symbianos_get_model_info(si_buffer, sizeof(si_buffer)); + ALLOC_CP_STR(si_buffer, machine); + + char *p = si_buffer + sizeof(si_buffer) - left; + unsigned plen; + plen = pj_symbianos_get_platform_info(p, left); + if (plen) { + /* Output format will be "Series60vX.X" */ + si.os_name = pj_str("S60"); + si.os_ver = parse_version(p + 9); + } else { + si.os_name = pj_str("Unknown"); + } + + /* Avoid compile warning on Symbian. */ + goto get_sdk_info; + } +#endif + + /* + * SDK info. + */ +get_sdk_info: + +#if defined(__GLIBC__) + si.sdk_ver = (__GLIBC__ << 24) | (__GLIBC_MINOR__ << 16); + si.sdk_name = pj_str("glibc"); +#elif defined(__GNU_LIBRARY__) + si.sdk_ver = (__GNU_LIBRARY__ << 24) | (__GNU_LIBRARY_MINOR__ << 16); + si.sdk_name = pj_str("libc"); +#elif defined(__UCLIBC__) + si.sdk_ver = (__UCLIBC_MAJOR__ << 24) | (__UCLIBC_MINOR__ << 16); + si.sdk_name = pj_str("uclibc"); +#elif defined(_WIN32_WCE) && _WIN32_WCE +/* Old window mobile declares _WIN32_WCE as decimal (e.g. 300, 420, etc.), + * but then it was changed to use hex, e.g. 0x420, etc. See + * http://social.msdn.microsoft.com/forums/en-US/vssmartdevicesnative/thread/8a97c59f-5a1c-4bc6-99e6-427f065ff439/ + */ +#if _WIN32_WCE <= 500 + si.sdk_ver = ((_WIN32_WCE / 100) << 24) | (((_WIN32_WCE % 100) / 10) << 16) | ((_WIN32_WCE % 10) << 8); +#else + si.sdk_ver = (((_WIN32_WCE & 0xFF00) >> 8) << 24) | (((_WIN32_WCE & 0x00F0) >> 4) << 16) | + (((_WIN32_WCE & 0x000F) >> 0) << 8); +#endif + si.sdk_name = pj_str("cesdk"); +#elif defined(_MSC_VER) + /* No SDK info is easily obtainable for Visual C, so lets just use + * _MSC_VER. The _MSC_VER macro reports the major and minor versions + * of the compiler. For example, 1310 for Microsoft Visual C++ .NET 2003. + * 1310 represents version 13 and a 1.0 point release. + * The Visual C++ 2005 compiler version is 1400. + */ + si.sdk_ver = ((_MSC_VER / 100) << 24) | (((_MSC_VER % 100) / 10) << 16) | ((_MSC_VER % 10) << 8); + si.sdk_name = pj_str("msvc"); +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + pj_symbianos_get_sdk_info(&si.sdk_name, &si.sdk_ver); +#endif + + /* + * Build the info string. + */ + { + char tmp[PJ_SYS_INFO_BUFFER_SIZE]; + char os_ver[20], sdk_ver[20]; + int cnt; + + cnt = pj_ansi_snprintf(tmp, sizeof(tmp), "%s%s%s%s%s%s%s", si.os_name.ptr, ver_info(si.os_ver, os_ver), + (si.machine.slen ? "/" : ""), si.machine.ptr, (si.sdk_name.slen ? "/" : ""), + si.sdk_name.ptr, ver_info(si.sdk_ver, sdk_ver)); + if (cnt > 0 && cnt < (int)sizeof(tmp)) { + ALLOC_CP_STR(tmp, info); + } + } + + si_initialized = PJ_TRUE; + return &si; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m new file mode 100755 index 000000000..d27331024 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "TargetConditionals.h" + +#if !defined TARGET_IPHONE_SIMULATOR || TARGET_IPHONE_SIMULATOR == 0 + +#include +#include + +#include + +void pj_iphone_os_get_sys_info(pj_sys_info *si, pj_str_t *si_buffer) +{ + unsigned buf_len = si_buffer->slen, left = si_buffer->slen, len; + UIDevice *device = [UIDevice currentDevice]; + + if ([device respondsToSelector:@selector(isMultitaskingSupported)]) + si->flags |= PJ_SYS_HAS_IOS_BG; + +#define ALLOC_CP_STR(str,field) \ + do { \ + len = [str length]; \ + if (len && left >= len+1) { \ + si->field.ptr = si_buffer->ptr + buf_len - left; \ + si->field.slen = len; \ + [str getCString:si->field.ptr maxLength:len+1 \ + encoding:NSASCIIStringEncoding]; \ + left -= (len+1); \ + } \ + } while (0) + + ALLOC_CP_STR([device systemName], os_name); + ALLOC_CP_STR([device systemVersion], machine); +} + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c new file mode 100755 index 000000000..8444b9e27 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Note: + * + * DO NOT BUILD THIS FILE DIRECTLY. THIS FILE WILL BE INCLUDED BY os_core_*.c + * WHEN MACRO PJ_EMULATE_RWMUTEX IS SET. + */ + +/* + * os_rwmutex.c: + * + * Implementation of Read-Write mutex for platforms that lack it (e.g. + * Win32, RTEMS). + */ + +struct pj_rwmutex_t { + pj_mutex_t *read_lock; + /* write_lock must use semaphore, because write_lock may be released + * by thread other than the thread that acquire the write_lock in the + * first place. + */ + pj_sem_t *write_lock; + pj_int32_t reader_count; +}; + +/* + * Create reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, pj_rwmutex_t **p_mutex) +{ + pj_status_t status; + pj_rwmutex_t *rwmutex; + + PJ_ASSERT_RETURN(pool && p_mutex, PJ_EINVAL); + + *p_mutex = NULL; + rwmutex = PJ_POOL_ALLOC_T(pool, pj_rwmutex_t); + + status = pj_mutex_create_simple(pool, name, &rwmutex->read_lock); + if (status != PJ_SUCCESS) + return status; + + status = pj_sem_create(pool, name, 1, 1, &rwmutex->write_lock); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(rwmutex->read_lock); + return status; + } + + rwmutex->reader_count = 0; + *p_mutex = rwmutex; + return PJ_SUCCESS; +} + +/* + * Lock the mutex for reading. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + status = pj_mutex_lock(mutex->read_lock); + if (status != PJ_SUCCESS) { + pj_assert(!"This pretty much is unexpected"); + return status; + } + + mutex->reader_count++; + + pj_assert(mutex->reader_count < 0x7FFFFFF0L); + + if (mutex->reader_count == 1) + pj_sem_wait(mutex->write_lock); + + status = pj_mutex_unlock(mutex->read_lock); + return status; +} + +/* + * Lock the mutex for writing. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + return pj_sem_wait(mutex->write_lock); +} + +/* + * Release read lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + status = pj_mutex_lock(mutex->read_lock); + if (status != PJ_SUCCESS) { + pj_assert(!"This pretty much is unexpected"); + return status; + } + + pj_assert(mutex->reader_count >= 1); + + --mutex->reader_count; + if (mutex->reader_count == 0) + pj_sem_post(mutex->write_lock); + + status = pj_mutex_unlock(mutex->read_lock); + return status; +} + +/* + * Release write lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + pj_assert(mutex->reader_count <= 1); + return pj_sem_post(mutex->write_lock); +} + +/* + * Destroy reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + pj_mutex_destroy(mutex->read_lock); + pj_sem_destroy(mutex->write_lock); + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c new file mode 100755 index 000000000..9707efd51 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +PJ_DEF(pj_status_t) pj_gettimeofday(pj_time_val *tv) +{ + struct timeb tb; + + PJ_CHECK_STACK(); + + ftime(&tb); + tv->sec = tb.time; + tv->msec = tb.millitm; + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c new file mode 100755 index 000000000..0f23094ce --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +#if !defined(PJ_WIN32) || PJ_WIN32 == 0 + +PJ_DEF(pj_status_t) pj_time_decode(const pj_time_val *tv, pj_parsed_time *pt) +{ + struct tm local_time; + + PJ_CHECK_STACK(); + +#if defined(PJ_HAS_LOCALTIME_R) && PJ_HAS_LOCALTIME_R != 0 + localtime_r((time_t *)&tv->sec, &local_time); +#else + /* localtime() is NOT thread-safe. */ + local_time = *localtime((time_t *)&tv->sec); +#endif + + pt->year = local_time.tm_year + 1900; + pt->mon = local_time.tm_mon; + pt->day = local_time.tm_mday; + pt->hour = local_time.tm_hour; + pt->min = local_time.tm_min; + pt->sec = local_time.tm_sec; + pt->wday = local_time.tm_wday; + pt->msec = tv->msec; + + return PJ_SUCCESS; +} + +/** + * Encode parsed time to time value. + */ +PJ_DEF(pj_status_t) pj_time_encode(const pj_parsed_time *pt, pj_time_val *tv) +{ + struct tm local_time; + + local_time.tm_year = pt->year - 1900; + local_time.tm_mon = pt->mon; + local_time.tm_mday = pt->day; + local_time.tm_hour = pt->hour; + local_time.tm_min = pt->min; + local_time.tm_sec = pt->sec; + local_time.tm_isdst = 0; + + tv->sec = mktime(&local_time); + tv->msec = pt->msec; + + return PJ_SUCCESS; +} + +#endif /* !PJ_WIN32 */ + +static int get_tz_offset_secs() +{ + time_t epoch_plus_11h = 60 * 60 * 11; + struct tm ltime, gtime; + int offset_min; + +#if defined(PJ_HAS_LOCALTIME_R) && PJ_HAS_LOCALTIME_R != 0 + localtime_r(&epoch_plus_11h, <ime); + gmtime_r(&epoch_plus_11h, >ime); +#else + ltime = *localtime(&epoch_plus_11h); + gtime = *gmtime(&epoch_plus_11h); +#endif + + offset_min = (ltime.tm_hour * 60 + ltime.tm_min) - (gtime.tm_hour * 60 + gtime.tm_min); + return offset_min * 60; +} + +/** + * Convert local time to GMT. + */ +PJ_DEF(pj_status_t) pj_time_local_to_gmt(pj_time_val *tv) +{ + tv->sec -= get_tz_offset_secs(); + return PJ_SUCCESS; +} + +/** + * Convert GMT to local time. + */ +PJ_DEF(pj_status_t) pj_time_gmt_to_local(pj_time_val *tv) +{ + tv->sec += get_tz_offset_secs(); + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c new file mode 100755 index 000000000..b00ffe58b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0 +#include +#endif + +#include + +/////////////////////////////////////////////////////////////////////////////// + +PJ_DEF(pj_status_t) pj_gettimeofday(pj_time_val *p_tv) +{ + struct timeval the_time; + int rc; + + PJ_CHECK_STACK(); + + rc = gettimeofday(&the_time, NULL); + if (rc != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + + p_tv->sec = the_time.tv_sec; + p_tv->msec = the_time.tv_usec / 1000; + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c new file mode 100755 index 000000000..2dc6ca705 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 + +#define U32MAX (0xFFFFFFFFUL) +#define NANOSEC (1000000000UL) +#define USEC (1000000UL) +#define MSEC (1000) + +#define u64tohighprec(u64) ((pj_highprec_t)((pj_int64_t)(u64))) + +static pj_highprec_t get_elapsed(const pj_timestamp *start, const pj_timestamp *stop) +{ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + return u64tohighprec(stop->u64 - start->u64); +#else + pj_highprec_t elapsed_hi, elapsed_lo; + + elapsed_hi = stop->u32.hi - start->u32.hi; + elapsed_lo = stop->u32.lo - start->u32.lo; + + /* elapsed_hi = elapsed_hi * U32MAX */ + pj_highprec_mul(elapsed_hi, U32MAX); + + return elapsed_hi + elapsed_lo; +#endif +} + +static pj_highprec_t elapsed_msec(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_timestamp ts_freq; + pj_highprec_t freq, elapsed; + + if (pj_get_timestamp_freq(&ts_freq) != PJ_SUCCESS) + return 0; + + /* Convert frequency timestamp */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + freq = u64tohighprec(ts_freq.u64); +#else + freq = ts_freq.u32.hi; + pj_highprec_mul(freq, U32MAX); + freq += ts_freq.u32.lo; +#endif + + /* Get elapsed time in cycles. */ + elapsed = get_elapsed(start, stop); + + /* usec = elapsed * MSEC / freq */ + pj_highprec_div(freq, MSEC); + + /* Avoid division by zero. */ + if (freq == 0) { + /* Regard freq = 1 */ + pj_highprec_mul(elapsed, MSEC); + } else { + pj_highprec_div(elapsed, freq); + } + return elapsed; +} + +static pj_highprec_t elapsed_usec(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_timestamp ts_freq; + pj_highprec_t freq, elapsed; + + if (pj_get_timestamp_freq(&ts_freq) != PJ_SUCCESS) + return 0; + + /* Convert frequency timestamp */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + freq = u64tohighprec(ts_freq.u64); +#else + freq = ts_freq.u32.hi; + pj_highprec_mul(freq, U32MAX); + freq += ts_freq.u32.lo; +#endif + + /* Avoid division by zero. */ + if (freq == 0) + freq = 1; + + /* Get elapsed time in cycles. */ + elapsed = get_elapsed(start, stop); + + /* usec = elapsed * USEC / freq */ + pj_highprec_mul(elapsed, USEC); + pj_highprec_div(elapsed, freq); + + return elapsed; +} + +PJ_DEF(pj_uint32_t) pj_elapsed_nanosec(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_timestamp ts_freq; + pj_highprec_t freq, elapsed; + + if (pj_get_timestamp_freq(&ts_freq) != PJ_SUCCESS) + return 0; + + /* Convert frequency timestamp */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + freq = u64tohighprec(ts_freq.u64); +#else + freq = ts_freq.u32.hi; + pj_highprec_mul(freq, U32MAX); + freq += ts_freq.u32.lo; +#endif + + /* Avoid division by zero. */ + if (freq == 0) + freq = 1; + + /* Get elapsed time in cycles. */ + elapsed = get_elapsed(start, stop); + + /* usec = elapsed * USEC / freq */ + pj_highprec_mul(elapsed, NANOSEC); + pj_highprec_div(elapsed, freq); + + return (pj_uint32_t)elapsed; +} + +PJ_DEF(pj_uint32_t) pj_elapsed_usec(const pj_timestamp *start, const pj_timestamp *stop) +{ + return (pj_uint32_t)elapsed_usec(start, stop); +} + +PJ_DEF(pj_uint32_t) pj_elapsed_msec(const pj_timestamp *start, const pj_timestamp *stop) +{ + return (pj_uint32_t)elapsed_msec(start, stop); +} + +PJ_DEF(pj_uint64_t) pj_elapsed_msec64(const pj_timestamp *start, const pj_timestamp *stop) +{ + return (pj_uint64_t)elapsed_msec(start, stop); +} + +PJ_DEF(pj_time_val) pj_elapsed_time(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_highprec_t elapsed = elapsed_msec(start, stop); + pj_time_val tv_elapsed; + + if (PJ_HIGHPREC_VALUE_IS_ZERO(elapsed)) { + tv_elapsed.sec = tv_elapsed.msec = 0; + return tv_elapsed; + } else { + pj_highprec_t sec, msec; + + sec = elapsed; + pj_highprec_div(sec, MSEC); + tv_elapsed.sec = (long)sec; + + msec = elapsed; + pj_highprec_mod(msec, MSEC); + tv_elapsed.msec = (long)msec; + + return tv_elapsed; + } +} + +PJ_DEF(pj_uint32_t) pj_elapsed_cycle(const pj_timestamp *start, const pj_timestamp *stop) +{ + return stop->u32.lo - start->u32.lo; +} + +PJ_DEF(pj_status_t) pj_gettickcount(pj_time_val *tv) +{ + pj_timestamp ts, start; + pj_status_t status; + + if ((status = pj_get_timestamp(&ts)) != PJ_SUCCESS) + return status; + + pj_set_timestamp32(&start, 0, 0); + *tv = pj_elapsed_time(&start, &ts); + + return PJ_SUCCESS; +} + +#endif /* PJ_HAS_HIGH_RES_TIMER */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c new file mode 100755 index 000000000..a4cd0c07b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0 +#include + +#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) +#define USE_POSIX_TIMERS 1 +#endif + +#endif + +#if defined(PJ_HAS_PENTIUM) && PJ_HAS_PENTIUM != 0 && defined(PJ_TIMESTAMP_USE_RDTSC) && \ + PJ_TIMESTAMP_USE_RDTSC != 0 && defined(PJ_M_I386) && PJ_M_I386 != 0 && defined(PJ_LINUX) && PJ_LINUX != 0 +static int machine_speed_mhz; +static pj_timestamp machine_speed; + +static __inline__ unsigned long long int rdtsc() +{ + unsigned long long int x; + __asm__ volatile(".byte 0x0f, 0x31" : "=A"(x)); + return x; +} + +/* Determine machine's CPU MHz to get the counter's frequency. + */ +static int get_machine_speed_mhz() +{ + FILE *strm; + char buf[512]; + int len; + char *pos, *end; + + PJ_CHECK_STACK(); + + /* Open /proc/cpuinfo and read the file */ + strm = fopen("/proc/cpuinfo", "r"); + if (!strm) + return -1; + len = fread(buf, 1, sizeof(buf), strm); + fclose(strm); + if (len < 1) { + return -1; + } + buf[len] = '\0'; + + /* Locate the MHz digit. */ + pos = strstr(buf, "cpu MHz"); + if (!pos) + return -1; + pos = strchr(pos, ':'); + if (!pos) + return -1; + end = (pos += 2); + while (isdigit(*end)) + ++end; + *end = '\0'; + + /* Return the Mhz part, and give it a +1. */ + return atoi(pos) + 1; +} + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + if (machine_speed_mhz == 0) { + machine_speed_mhz = get_machine_speed_mhz(); + if (machine_speed_mhz > 0) { + machine_speed.u64 = machine_speed_mhz * 1000000.0; + } + } + + if (machine_speed_mhz == -1) { + ts->u64 = 0; + return -1; + } + ts->u64 = rdtsc(); + return 0; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + if (machine_speed_mhz == 0) { + machine_speed_mhz = get_machine_speed_mhz(); + if (machine_speed_mhz > 0) { + machine_speed.u64 = machine_speed_mhz * 1000000.0; + } + } + + if (machine_speed_mhz == -1) { + freq->u64 = 1; /* return 1 to prevent division by zero in apps. */ + return -1; + } + + freq->u64 = machine_speed.u64; + return 0; +} + +#elif defined(PJ_DARWINOS) && PJ_DARWINOS != 0 + +/* SYSTEM_CLOCK will stop when the device is in deep sleep, so we use + * KERN_BOOTTIME instead. + * See ticket #2140 for more details. + */ +#define USE_KERN_BOOTTIME 1 + +#if USE_KERN_BOOTTIME +#include +#else +#include +#include +#include +#endif + +#ifndef NSEC_PER_SEC +#define NSEC_PER_SEC 1000000000 +#endif + +#if USE_KERN_BOOTTIME +static int64_t get_boottime() +{ + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int rc; + + rc = sysctl(mib, 2, &boottime, &size, NULL, 0); + if (rc != 0) + return 0; + + return (int64_t)boottime.tv_sec * 1000000 + (int64_t)boottime.tv_usec; +} +#endif + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ +#if USE_KERN_BOOTTIME + int64_t before_now, after_now; + struct timeval now; + + after_now = get_boottime(); + do { + before_now = after_now; + gettimeofday(&now, NULL); + after_now = get_boottime(); + } while (after_now != before_now); + + ts->u64 = (int64_t)now.tv_sec * 1000000 + (int64_t)now.tv_usec; + ts->u64 -= before_now; + ts->u64 *= 1000; +#else + mach_timespec_t tp; + int ret; + clock_serv_t serv; + + ret = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &serv); + if (ret != KERN_SUCCESS) { + return PJ_RETURN_OS_ERROR(EINVAL); + } + + ret = clock_get_time(serv, &tp); + if (ret != KERN_SUCCESS) { + return PJ_RETURN_OS_ERROR(EINVAL); + } + + ts->u64 = tp.tv_sec; + ts->u64 *= NSEC_PER_SEC; + ts->u64 += tp.tv_nsec; +#endif + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = NSEC_PER_SEC; + + return PJ_SUCCESS; +} + +#elif defined(__ANDROID__) + +#include +#include + +#if defined(PJ_HAS_ANDROID_ALARM_H) && PJ_HAS_ANDROID_ALARM_H != 0 +#include +#include +#endif + +#define NSEC_PER_SEC 1000000000 + +#if defined(ANDROID_ALARM_GET_TIME) +static int s_alarm_fd = -1; + +void close_alarm_fd() +{ + if (s_alarm_fd != -1) + close(s_alarm_fd); + s_alarm_fd = -1; +} +#endif + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + struct timespec tp; + int err = -1; + +#if defined(ANDROID_ALARM_GET_TIME) + if (s_alarm_fd == -1) { + int fd = open("/dev/alarm", O_RDONLY); + if (fd >= 0) { + s_alarm_fd = fd; + pj_atexit(&close_alarm_fd); + } + } + + if (s_alarm_fd != -1) { + err = ioctl(s_alarm_fd, ANDROID_ALARM_GET_TIME(ANDROID_ALARM_ELAPSED_REALTIME), &tp); + } +#elif defined(CLOCK_BOOTTIME) + err = clock_gettime(CLOCK_BOOTTIME, &tp); +#endif + + if (err != 0) { + /* Fallback to CLOCK_MONOTONIC if /dev/alarm is not found, or + * getting ANDROID_ALARM_ELAPSED_REALTIME fails, or + * CLOCK_BOOTTIME fails. + */ + err = clock_gettime(CLOCK_MONOTONIC, &tp); + } + + if (err != 0) { + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + ts->u64 = tp.tv_sec; + ts->u64 *= NSEC_PER_SEC; + ts->u64 += tp.tv_nsec; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = NSEC_PER_SEC; + + return PJ_SUCCESS; +} + +#elif defined(USE_POSIX_TIMERS) && USE_POSIX_TIMERS != 0 +#include +#include +#include + +#define NSEC_PER_SEC 1000000000 + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + struct timespec tp; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + ts->u64 = tp.tv_sec; + ts->u64 *= NSEC_PER_SEC; + ts->u64 += tp.tv_nsec; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = NSEC_PER_SEC; + + return PJ_SUCCESS; +} + +#else +#include +#include + +#define USEC_PER_SEC 1000000 + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) { + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + ts->u64 = tv.tv_sec; + ts->u64 *= USEC_PER_SEC; + ts->u64 += tv.tv_usec; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = USEC_PER_SEC; + + return PJ_SUCCESS; +} + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool.c new file mode 100755 index 000000000..054192e4c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if !PJ_HAS_POOL_ALT_API + +/* Include inline definitions when inlining is disabled. */ +#if !PJ_FUNCTIONS_ARE_INLINED +#include +#endif + +#define LOG(expr) PJ_LOG(6, expr) +#define ALIGN_PTR(PTR, ALIGNMENT) (PTR + (-(pj_ssize_t)(PTR) & (ALIGNMENT - 1))) + +PJ_DEF_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +PJ_DEF(int) pj_NO_MEMORY_EXCEPTION() +{ + return PJ_NO_MEMORY_EXCEPTION; +} + +/* + * Create new block. + * Create a new big chunk of memory block, from which user allocation will be + * taken from. + */ +static pj_pool_block *pj_pool_create_block(pj_pool_t *pool, pj_size_t size) +{ + pj_pool_block *block; + + PJ_CHECK_STACK(); + pj_assert(size >= sizeof(pj_pool_block)); + + LOG((pool->obj_name, "create_block(sz=%u), cur.cap=%u, cur.used=%u", size, pool->capacity, + pj_pool_get_used_size(pool))); + + /* Request memory from allocator. */ + block = (pj_pool_block *)(*pool->factory->policy.block_alloc)(pool->factory, size); + if (block == NULL) { + (*pool->callback)(pool, size); + return NULL; + } + + /* Add capacity. */ + pool->capacity += size; + + /* Set start and end of buffer. */ + block->buf = ((unsigned char *)block) + sizeof(pj_pool_block); + block->end = ((unsigned char *)block) + size; + + /* Set the start pointer, aligning it as needed */ + block->cur = ALIGN_PTR(block->buf, PJ_POOL_ALIGNMENT); + + /* Insert in the front of the list. */ + pj_list_insert_after(&pool->block_list, block); + + LOG((pool->obj_name, " block created, buffer=%p-%p", block->buf, block->end)); + + return block; +} + +/* + * Allocate memory chunk for user from available blocks. + * This will iterate through block list to find space to allocate the chunk. + * If no space is available in all the blocks, a new block might be created + * (depending on whether the pool is allowed to resize). + */ +PJ_DEF(void *) pj_pool_allocate_find(pj_pool_t *pool, pj_size_t size) +{ + pj_pool_block *block = pool->block_list.next; + void *p; + pj_size_t block_size; + + PJ_CHECK_STACK(); + + while (block != &pool->block_list) { + p = pj_pool_alloc_from_block(block, size); + if (p != NULL) + return p; + block = block->next; + } + /* No available space in all blocks. */ + + /* If pool is configured NOT to expand, return error. */ + if (pool->increment_size == 0) { + LOG((pool->obj_name, + "Can't expand pool to allocate %u bytes " + "(used=%u, cap=%u)", + size, pj_pool_get_used_size(pool), pool->capacity)); + (*pool->callback)(pool, size); + return NULL; + } + + /* If pool is configured to expand, but the increment size + * is less than the required size, expand the pool by multiple + * increment size. Also count the size wasted due to aligning + * the block. + */ + if (pool->increment_size < size + sizeof(pj_pool_block) + PJ_POOL_ALIGNMENT) { + pj_size_t count; + count = (size + pool->increment_size + sizeof(pj_pool_block) + PJ_POOL_ALIGNMENT) / pool->increment_size; + block_size = count * pool->increment_size; + + } else { + block_size = pool->increment_size; + } + + LOG((pool->obj_name, "%u bytes requested, resizing pool by %u bytes (used=%u, cap=%u)", size, block_size, + pj_pool_get_used_size(pool), pool->capacity)); + + block = pj_pool_create_block(pool, block_size); + if (!block) + return NULL; + + p = pj_pool_alloc_from_block(block, size); + pj_assert(p != NULL); +#if PJ_DEBUG + if (p == NULL) { + PJ_UNUSED_ARG(p); + } +#endif + return p; +} + +/* + * Internal function to initialize pool. + */ +PJ_DEF(void) pj_pool_init_int(pj_pool_t *pool, const char *name, pj_size_t increment_size, pj_pool_callback *callback) +{ + PJ_CHECK_STACK(); + + pool->increment_size = increment_size; + pool->callback = callback; + + if (name) { + if (strchr(name, '%') != NULL) { + pj_ansi_snprintf(pool->obj_name, sizeof(pool->obj_name), name, pool); + } else { + pj_ansi_strncpy(pool->obj_name, name, PJ_MAX_OBJ_NAME); + pool->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + } else { + pool->obj_name[0] = '\0'; + } +} + +/* + * Create new memory pool. + */ +PJ_DEF(pj_pool_t *) +pj_pool_create_int(pj_pool_factory *f, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback) +{ + pj_pool_t *pool; + pj_pool_block *block; + pj_uint8_t *buffer; + + PJ_CHECK_STACK(); + + /* Size must be at least sizeof(pj_pool)+sizeof(pj_pool_block) */ + PJ_ASSERT_RETURN(initial_size >= sizeof(pj_pool_t) + sizeof(pj_pool_block), NULL); + + /* If callback is NULL, set calback from the policy */ + if (callback == NULL) + callback = f->policy.callback; + + /* Allocate initial block */ + buffer = (pj_uint8_t *)(*f->policy.block_alloc)(f, initial_size); + if (!buffer) + return NULL; + + /* Set pool administrative data. */ + pool = (pj_pool_t *)buffer; + pj_bzero(pool, sizeof(*pool)); + + pj_list_init(&pool->block_list); + pool->factory = f; + + /* Create the first block from the memory. */ + block = (pj_pool_block *)(buffer + sizeof(*pool)); + block->buf = ((unsigned char *)block) + sizeof(pj_pool_block); + block->end = buffer + initial_size; + + /* Set the start pointer, aligning it as needed */ + block->cur = ALIGN_PTR(block->buf, PJ_POOL_ALIGNMENT); + + pj_list_insert_after(&pool->block_list, block); + + pj_pool_init_int(pool, name, increment_size, callback); + + /* Pool initial capacity and used size */ + pool->capacity = initial_size; + + LOG((pool->obj_name, "pool created, size=%u", pool->capacity)); + return pool; +} + +/* + * Reset the pool to the state when it was created. + * All blocks will be deallocated except the first block. All memory areas + * are marked as free. + */ +static void reset_pool(pj_pool_t *pool) +{ + pj_pool_block *block; + + PJ_CHECK_STACK(); + + block = pool->block_list.prev; + if (block == &pool->block_list) + return; + + /* Skip the first block because it is occupying the same memory + as the pool itself. + */ + block = block->prev; + + while (block != &pool->block_list) { + pj_pool_block *prev = block->prev; + pj_list_erase(block); + (*pool->factory->policy.block_free)(pool->factory, block, block->end - (unsigned char *)block); + block = prev; + } + + block = pool->block_list.next; + + /* Set the start pointer, aligning it as needed */ + block->cur = ALIGN_PTR(block->buf, PJ_POOL_ALIGNMENT); + + pool->capacity = block->end - (unsigned char *)pool; +} + +/* + * The public function to reset pool. + */ +PJ_DEF(void) pj_pool_reset(pj_pool_t *pool) +{ + LOG((pool->obj_name, "reset(): cap=%d, used=%d(%d%%)", pool->capacity, pj_pool_get_used_size(pool), + pj_pool_get_used_size(pool) * 100 / pool->capacity)); + + reset_pool(pool); +} + +/* + * Destroy the pool. + */ +PJ_DEF(void) pj_pool_destroy_int(pj_pool_t *pool) +{ + pj_size_t initial_size; + + LOG((pool->obj_name, "destroy(): cap=%d, used=%d(%d%%), block0=%p-%p", pool->capacity, pj_pool_get_used_size(pool), + pj_pool_get_used_size(pool) * 100 / pool->capacity, ((pj_pool_block *)pool->block_list.next)->buf, + ((pj_pool_block *)pool->block_list.next)->end)); + + reset_pool(pool); + initial_size = ((pj_pool_block *)pool->block_list.next)->end - (unsigned char *)pool; + if (pool->factory->policy.block_free) + (*pool->factory->policy.block_free)(pool->factory, pool, initial_size); +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c new file mode 100755 index 000000000..a45d35b31 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +static struct pj_pool_factory stack_based_factory; + +struct creation_param { + void *stack_buf; + pj_size_t size; +}; + +static int is_initialized; +static long tls = -1; +static void *stack_alloc(pj_pool_factory *factory, pj_size_t size); + +static void pool_buf_cleanup(void) +{ + if (tls != -1) { + pj_thread_local_free(tls); + tls = -1; + } + if (is_initialized) + is_initialized = 0; +} + +static pj_status_t pool_buf_initialize(void) +{ + pj_atexit(&pool_buf_cleanup); + + stack_based_factory.policy.block_alloc = &stack_alloc; + return pj_thread_local_alloc(&tls); +} + +static void *stack_alloc(pj_pool_factory *factory, pj_size_t size) +{ + struct creation_param *param; + void *buf; + + PJ_UNUSED_ARG(factory); + + param = (struct creation_param *)pj_thread_local_get(tls); + if (param == NULL) { + /* Don't assert(), this is normal no-memory situation */ + return NULL; + } + + pj_thread_local_set(tls, NULL); + + PJ_ASSERT_RETURN(size <= param->size, NULL); + + buf = param->stack_buf; + + /* Prevent the buffer from being reused */ + param->stack_buf = NULL; + + return buf; +} + +PJ_DEF(pj_pool_t *) pj_pool_create_on_buf(const char *name, void *buf, pj_size_t size) +{ +#if PJ_HAS_POOL_ALT_API == 0 + struct creation_param param; + pj_size_t align_diff; + + PJ_ASSERT_RETURN(buf && size, NULL); + + if (!is_initialized) { + if (pool_buf_initialize() != PJ_SUCCESS) + return NULL; + is_initialized = 1; + } + + /* Check and align buffer */ + align_diff = (pj_size_t)buf; + if (align_diff & (PJ_POOL_ALIGNMENT - 1)) { + align_diff &= (PJ_POOL_ALIGNMENT - 1); + buf = (void *)(((char *)buf) + align_diff); + size -= align_diff; + } + + param.stack_buf = buf; + param.size = size; + pj_thread_local_set(tls, ¶m); + + return pj_pool_create_int(&stack_based_factory, name, size, 0, pj_pool_factory_default_policy.callback); +#else + PJ_UNUSED_ARG(buf); + return pj_pool_create(NULL, name, size, size, NULL); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c new file mode 100755 index 000000000..2210ac1d3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#if !PJ_HAS_POOL_ALT_API + +static pj_pool_t *cpool_create_pool(pj_pool_factory *pf, const char *name, pj_size_t initial_size, + pj_size_t increment_sz, pj_pool_callback *callback); +static void cpool_release_pool(pj_pool_factory *pf, pj_pool_t *pool); +static void cpool_dump_status(pj_pool_factory *factory, pj_bool_t detail); +static pj_bool_t cpool_on_block_alloc(pj_pool_factory *f, pj_size_t sz); +static void cpool_on_block_free(pj_pool_factory *f, pj_size_t sz); + +static pj_size_t pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE] = {256, 512, 1024, 2048, 4096, 8192, 12288, 16384, + 20480, 24576, 28672, 32768, 40960, 49152, 57344, 65536}; + +/* Index where the search for size should begin. + * Start with pool_sizes[5], which is 8192. + */ +#define START_SIZE 5 + +PJ_DEF(void) pj_caching_pool_init(pj_caching_pool *cp, const pj_pool_factory_policy *policy, pj_size_t max_capacity) +{ + int i; + pj_pool_t *pool; + + PJ_CHECK_STACK(); + + pj_bzero(cp, sizeof(*cp)); + + cp->max_capacity = max_capacity; + pj_list_init(&cp->used_list); + for (i = 0; i < PJ_CACHING_POOL_ARRAY_SIZE; ++i) + pj_list_init(&cp->free_list[i]); + + if (policy == NULL) { + policy = &pj_pool_factory_default_policy; + } + + pj_memcpy(&cp->factory.policy, policy, sizeof(pj_pool_factory_policy)); + cp->factory.create_pool = &cpool_create_pool; + cp->factory.release_pool = &cpool_release_pool; + cp->factory.dump_status = &cpool_dump_status; + cp->factory.on_block_alloc = &cpool_on_block_alloc; + cp->factory.on_block_free = &cpool_on_block_free; + + pool = pj_pool_create_on_buf("cachingpool", cp->pool_buf, sizeof(cp->pool_buf)); + pj_lock_create_simple_mutex(pool, "cachingpool", &cp->lock); +} + +PJ_DEF(void) pj_caching_pool_destroy(pj_caching_pool *cp) +{ + int i; + pj_pool_t *pool; + + PJ_CHECK_STACK(); + + /* Delete all pool in free list */ + for (i = 0; i < PJ_CACHING_POOL_ARRAY_SIZE; ++i) { + pj_pool_t *next; + pool = (pj_pool_t *)cp->free_list[i].next; + for (; pool != (void *)&cp->free_list[i]; pool = next) { + next = pool->next; + pj_list_erase(pool); + pj_pool_destroy_int(pool); + } + } + + /* Delete all pools in used list */ + pool = (pj_pool_t *)cp->used_list.next; + while (pool != (pj_pool_t *)&cp->used_list) { + pj_pool_t *next = pool->next; + pj_list_erase(pool); + PJ_LOG(4, (pool->obj_name, "Pool is not released by application, releasing now")); + pj_pool_destroy_int(pool); + pool = next; + } + + if (cp->lock) { + pj_lock_destroy(cp->lock); + pj_lock_create_null_mutex(NULL, "cachingpool", &cp->lock); + } +} + +static pj_pool_t *cpool_create_pool(pj_pool_factory *pf, const char *name, pj_size_t initial_size, + pj_size_t increment_sz, pj_pool_callback *callback) +{ + pj_caching_pool *cp = (pj_caching_pool *)pf; + pj_pool_t *pool; + int idx; + + PJ_CHECK_STACK(); + + pj_lock_acquire(cp->lock); + + /* Use pool factory's policy when callback is NULL */ + if (callback == NULL) { + callback = pf->policy.callback; + } + + /* Search the suitable size for the pool. + * We'll just do linear search to the size array, as the array size itself + * is only a few elements. Binary search I suspect will be less efficient + * for this purpose. + */ + if (initial_size <= pool_sizes[START_SIZE]) { + for (idx = START_SIZE - 1; idx >= 0 && pool_sizes[idx] >= initial_size; --idx) + ; + ++idx; + } else { + for (idx = START_SIZE + 1; idx < PJ_CACHING_POOL_ARRAY_SIZE && pool_sizes[idx] < initial_size; ++idx) + ; + } + + /* Check whether there's a pool in the list. */ + if (idx == PJ_CACHING_POOL_ARRAY_SIZE || pj_list_empty(&cp->free_list[idx])) { + /* No pool is available. */ + /* Set minimum size. */ + if (idx < PJ_CACHING_POOL_ARRAY_SIZE) + initial_size = pool_sizes[idx]; + + /* Create new pool */ + pool = pj_pool_create_int(&cp->factory, name, initial_size, increment_sz, callback); + if (!pool) { + pj_lock_release(cp->lock); + return NULL; + } + + } else { + /* Get one pool from the list. */ + pool = (pj_pool_t *)cp->free_list[idx].next; + pj_list_erase(pool); + + /* Initialize the pool. */ + pj_pool_init_int(pool, name, increment_sz, callback); + + /* Update pool manager's free capacity. */ + if (cp->capacity > pj_pool_get_capacity(pool)) { + cp->capacity -= pj_pool_get_capacity(pool); + } else { + cp->capacity = 0; + } + + PJ_LOG(6, (pool->obj_name, "pool reused, size=%u", pool->capacity)); + } + + /* Put in used list. */ + pj_list_insert_before(&cp->used_list, pool); + + /* Mark factory data */ + pool->factory_data = (void *)(pj_ssize_t)idx; + + /* Increment used count. */ + ++cp->used_count; + + pj_lock_release(cp->lock); + return pool; +} + +static void cpool_release_pool(pj_pool_factory *pf, pj_pool_t *pool) +{ + pj_caching_pool *cp = (pj_caching_pool *)pf; + pj_size_t pool_capacity; + unsigned i; + + PJ_CHECK_STACK(); + + PJ_ASSERT_ON_FAIL(pf && pool, return ); + + pj_lock_acquire(cp->lock); + +#if PJ_SAFE_POOL + /* Make sure pool is still in our used list */ + if (pj_list_find_node(&cp->used_list, pool) != pool) { + pj_assert(!"Attempt to destroy pool that has been destroyed before"); + return; + } +#endif + + /* Erase from the used list. */ + pj_list_erase(pool); + + /* Decrement used count. */ + --cp->used_count; + + pool_capacity = pj_pool_get_capacity(pool); + + /* Destroy the pool if the size is greater than our size or if the total + * capacity in our recycle list (plus the size of the pool) exceeds + * maximum capacity. + . */ + if (pool_capacity > pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE - 1] || cp->capacity + pool_capacity > cp->max_capacity) { + pj_pool_destroy_int(pool); + pj_lock_release(cp->lock); + return; + } + + /* Reset pool. */ + PJ_LOG(6, (pool->obj_name, "recycle(): cap=%d, used=%d(%d%%)", pool_capacity, pj_pool_get_used_size(pool), + pj_pool_get_used_size(pool) * 100 / pool_capacity)); + pj_pool_reset(pool); + + pool_capacity = pj_pool_get_capacity(pool); + + /* + * Otherwise put the pool in our recycle list. + */ + i = (unsigned)(unsigned long)(pj_ssize_t)pool->factory_data; + + pj_assert(i < PJ_CACHING_POOL_ARRAY_SIZE); + if (i >= PJ_CACHING_POOL_ARRAY_SIZE) { + /* Something has gone wrong with the pool. */ + pj_pool_destroy_int(pool); + pj_lock_release(cp->lock); + return; + } + + pj_list_insert_after(&cp->free_list[i], pool); + cp->capacity += pool_capacity; + + pj_lock_release(cp->lock); +} + +static void cpool_dump_status(pj_pool_factory *factory, pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + pj_caching_pool *cp = (pj_caching_pool *)factory; + + pj_lock_acquire(cp->lock); + + PJ_LOG(3, ("cachpool", " Dumping caching pool:")); + PJ_LOG(3, ("cachpool", " Capacity=%u, max_capacity=%u, used_cnt=%u", cp->capacity, cp->max_capacity, + cp->used_count)); + if (detail) { + pj_pool_t *pool = (pj_pool_t *)cp->used_list.next; + pj_size_t total_used = 0, total_capacity = 0; + PJ_LOG(3, ("cachpool", " Dumping all active pools:")); + while (pool != (void *)&cp->used_list) { + pj_size_t pool_capacity = pj_pool_get_capacity(pool); + PJ_LOG(3, ("cachpool", " %16s: %8d of %8d (%d%%) used", pj_pool_getobjname(pool), + pj_pool_get_used_size(pool), pool_capacity, pj_pool_get_used_size(pool) * 100 / pool_capacity)); + total_used += pj_pool_get_used_size(pool); + total_capacity += pool_capacity; + pool = pool->next; + } + if (total_capacity) { + PJ_LOG(3, ("cachpool", " Total %9d of %9d (%d %%) used!", total_used, total_capacity, + total_used * 100 / total_capacity)); + } + } + + pj_lock_release(cp->lock); +#else + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(detail); +#endif +} + +static pj_bool_t cpool_on_block_alloc(pj_pool_factory *f, pj_size_t sz) +{ + pj_caching_pool *cp = (pj_caching_pool *)f; + + // Can't lock because mutex is not recursive + // if (cp->mutex) pj_mutex_lock(cp->mutex); + + cp->used_size += sz; + if (cp->used_size > cp->peak_used_size) + cp->peak_used_size = cp->used_size; + + // if (cp->mutex) pj_mutex_unlock(cp->mutex); + + return PJ_TRUE; +} + +static void cpool_on_block_free(pj_pool_factory *f, pj_size_t sz) +{ + pj_caching_pool *cp = (pj_caching_pool *)f; + + // pj_mutex_lock(cp->mutex); + cp->used_size -= sz; + // pj_mutex_unlock(cp->mutex); +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c new file mode 100755 index 000000000..379500cce --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#if PJ_HAS_POOL_ALT_API + +#if PJ_HAS_MALLOC_H +#include +#endif + +#if PJ_HAS_STDLIB_H +#include +#endif + +#if ((defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0)) && defined(PJ_DEBUG) && \ + PJ_DEBUG != 0 && !PJ_NATIVE_STRING_IS_UNICODE +#include +#define TRACE_(msg) OutputDebugString(msg) +#endif + +/* Uncomment this to enable TRACE_ */ +//#undef TRACE_ + +PJ_DEF_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +PJ_DEF(int) pj_NO_MEMORY_EXCEPTION() +{ + return PJ_NO_MEMORY_EXCEPTION; +} + +/* Create pool */ +PJ_DEF(pj_pool_t *) +pj_pool_create_imp(const char *file, int line, void *factory, const char *name, pj_size_t initial_size, + pj_size_t increment_size, pj_pool_callback *callback) +{ + pj_pool_t *pool; + + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(initial_size); + PJ_UNUSED_ARG(increment_size); + + pool = malloc(sizeof(struct pj_pool_t)); + if (!pool) + return NULL; + + if (name) { + pj_ansi_strncpy(pool->obj_name, name, sizeof(pool->obj_name)); + pool->obj_name[sizeof(pool->obj_name) - 1] = '\0'; + } else { + strcpy(pool->obj_name, "altpool"); + } + + pool->factory = NULL; + pool->first_mem = NULL; + pool->used_size = 0; + pool->cb = callback; + + return pool; +} + +/* Release pool */ +PJ_DEF(void) pj_pool_release_imp(pj_pool_t *pool) +{ + pj_pool_reset(pool); + free(pool); +} + +/* Safe release pool */ +PJ_DEF(void) pj_pool_safe_release_imp(pj_pool_t **ppool) +{ + pj_pool_t *pool = *ppool; + *ppool = NULL; + if (pool) + pj_pool_release(pool); +} + +/* Secure release pool */ +PJ_DEF(void) pj_pool_secure_release_imp(pj_pool_t **ppool) +{ + /* Secure release is not implemented, so we just call + * safe release. + */ + pj_pool_safe_release_imp(ppool); +} + +/* Get pool name */ +PJ_DEF(const char *) pj_pool_getobjname_imp(pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + return "pooldbg"; +} + +/* Reset pool */ +PJ_DEF(void) pj_pool_reset_imp(pj_pool_t *pool) +{ + struct pj_pool_mem *mem; + + mem = pool->first_mem; + while (mem) { + struct pj_pool_mem *next = mem->next; + free(mem); + mem = next; + } + + pool->first_mem = NULL; +} + +/* Get capacity */ +PJ_DEF(pj_size_t) pj_pool_get_capacity_imp(pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + + /* Unlimited capacity */ + return 0x7FFFFFFFUL; +} + +/* Get total used size */ +PJ_DEF(pj_size_t) pj_pool_get_used_size_imp(pj_pool_t *pool) +{ + return pool->used_size; +} + +/* Allocate memory from the pool */ +PJ_DEF(void *) pj_pool_alloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz) +{ + struct pj_pool_mem *mem; + + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + + mem = malloc(sz + sizeof(struct pj_pool_mem)); + if (!mem) { + if (pool->cb) + (*pool->cb)(pool, sz); + return NULL; + } + + mem->next = pool->first_mem; + pool->first_mem = mem; + +#ifdef TRACE_ + { + char msg[120]; + pj_ansi_sprintf(msg, "Mem %X (%d+%d bytes) allocated by %s:%d\r\n", mem, sz, sizeof(struct pj_pool_mem), file, + line); + TRACE_(msg); + } +#endif + + return ((char *)mem) + sizeof(struct pj_pool_mem); +} + +/* Allocate memory from the pool and zero the memory */ +PJ_DEF(void *) pj_pool_calloc_imp(const char *file, int line, pj_pool_t *pool, unsigned cnt, unsigned elemsz) +{ + void *mem; + + mem = pj_pool_alloc_imp(file, line, pool, cnt * elemsz); + if (!mem) + return NULL; + + pj_bzero(mem, cnt * elemsz); + return mem; +} + +/* Allocate memory from the pool and zero the memory */ +PJ_DEF(void *) pj_pool_zalloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz) +{ + return pj_pool_calloc_imp(file, line, pool, 1, sz); +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c new file mode 100755 index 000000000..a96a439a0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#if !PJ_HAS_POOL_ALT_API + +/* + * This file contains pool default policy definition and implementation. + */ +#include "pool_signature.h" + +static void *default_block_alloc(pj_pool_factory *factory, pj_size_t size) +{ + void *p; + + PJ_CHECK_STACK(); + + if (factory->on_block_alloc) { + int rc; + rc = factory->on_block_alloc(factory, size); + if (!rc) + return NULL; + } + + p = malloc(size + (SIG_SIZE << 1)); + + if (p == NULL) { + if (factory->on_block_free) + factory->on_block_free(factory, size); + } else { + /* Apply signature when PJ_SAFE_POOL is set. It will move + * "p" pointer forward. + */ + APPLY_SIG(p, size); + } + + return p; +} + +static void default_block_free(pj_pool_factory *factory, void *mem, pj_size_t size) +{ + PJ_CHECK_STACK(); + + if (factory->on_block_free) + factory->on_block_free(factory, size); + + /* Check and remove signature when PJ_SAFE_POOL is set. It will + * move "mem" pointer backward. + */ + REMOVE_SIG(mem, size); + + /* Note that when PJ_SAFE_POOL is set, the actual size of the block + * is size + SIG_SIZE*2. + */ + + free(mem); +} + +static void default_pool_callback(pj_pool_t *pool, pj_size_t size) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(size); + + PJ_THROW(PJ_NO_MEMORY_EXCEPTION); +} + +PJ_DEF_DATA(pj_pool_factory_policy) +pj_pool_factory_default_policy = {&default_block_alloc, &default_block_free, &default_pool_callback, 0}; + +PJ_DEF(const pj_pool_factory_policy *) pj_pool_factory_get_default_policy(void) +{ + return &pj_pool_factory_default_policy; +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h new file mode 100755 index 000000000..2d4debe2f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#if PJ_SAFE_POOL +#define SIG_SIZE sizeof(pj_uint32_t) + +static void apply_signature(void *p, pj_size_t size); +static void check_pool_signature(void *p, pj_size_t size); + +#define APPLY_SIG(p, sz) apply_signature(p, sz), p = (void *)(((char *)p) + SIG_SIZE) +#define REMOVE_SIG(p, sz) check_pool_signature(p, sz), p = (void *)(((char *)p) - SIG_SIZE) + +#define SIG_BEGIN 0x600DC0DE +#define SIG_END 0x0BADC0DE + +static void apply_signature(void *p, pj_size_t size) +{ + pj_uint32_t sig; + + sig = SIG_BEGIN; + pj_memcpy(p, &sig, SIG_SIZE); + + sig = SIG_END; + pj_memcpy(((char *)p) + SIG_SIZE + size, &sig, SIG_SIZE); +} + +static void check_pool_signature(void *p, pj_size_t size) +{ + pj_uint32_t sig; + pj_uint8_t *mem = (pj_uint8_t *)p; + + /* Check that signature at the start of the block is still intact */ + sig = SIG_BEGIN; + pj_assert(!pj_memcmp(mem - SIG_SIZE, &sig, SIG_SIZE)); + + /* Check that signature at the end of the block is still intact. + * Note that "mem" has been incremented by SIG_SIZE + */ + sig = SIG_END; + pj_assert(!pj_memcmp(mem + size, &sig, SIG_SIZE)); +} + +#else +#define SIG_SIZE 0 +#define APPLY_SIG(p, sz) +#define REMOVE_SIG(p, sz) +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/rand.c b/src/tuya_p2p/pjproject/pjlib/src/pj/rand.c new file mode 100755 index 000000000..c017405b0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/rand.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(void) pj_srand(unsigned int seed) +{ + PJ_CHECK_STACK(); + platform_srand(seed); +} + +PJ_DEF(int) pj_rand(void) +{ + PJ_CHECK_STACK(); + return platform_rand(); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c b/src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c new file mode 100755 index 000000000..1f5dd90a9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +static void left_rotate(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *rnode, *parent; + + PJ_CHECK_STACK(); + + rnode = node->right; + if (rnode == tree->null) + return; + + node->right = rnode->left; + if (rnode->left != tree->null) + rnode->left->parent = node; + parent = node->parent; + rnode->parent = parent; + if (parent != tree->null) { + if (parent->left == node) + parent->left = rnode; + else + parent->right = rnode; + } else { + tree->root = rnode; + } + rnode->left = node; + node->parent = rnode; +} + +static void right_rotate(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *lnode, *parent; + + PJ_CHECK_STACK(); + + lnode = node->left; + if (lnode == tree->null) + return; + + node->left = lnode->right; + if (lnode->right != tree->null) + lnode->right->parent = node; + parent = node->parent; + lnode->parent = parent; + + if (parent != tree->null) { + if (parent->left == node) + parent->left = lnode; + else + parent->right = lnode; + } else { + tree->root = lnode; + } + lnode->right = node; + node->parent = lnode; +} + +static void insert_fixup(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *temp, *parent; + + PJ_CHECK_STACK(); + + while (node != tree->root && node->parent->color == PJ_RBCOLOR_RED) { + parent = node->parent; + if (parent == parent->parent->left) { + temp = parent->parent->right; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node = parent; + node->color = PJ_RBCOLOR_BLACK; + node = node->parent; + node->color = PJ_RBCOLOR_RED; + } else { + if (node == parent->right) { + node = parent; + left_rotate(tree, node); + } + temp = node->parent; + temp->color = PJ_RBCOLOR_BLACK; + temp = temp->parent; + temp->color = PJ_RBCOLOR_RED; + right_rotate(tree, temp); + } + } else { + temp = parent->parent->left; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node = parent; + node->color = PJ_RBCOLOR_BLACK; + node = node->parent; + node->color = PJ_RBCOLOR_RED; + } else { + if (node == parent->left) { + node = parent; + right_rotate(tree, node); + } + temp = node->parent; + temp->color = PJ_RBCOLOR_BLACK; + temp = temp->parent; + temp->color = PJ_RBCOLOR_RED; + left_rotate(tree, temp); + } + } + } + + tree->root->color = PJ_RBCOLOR_BLACK; +} + +static void delete_fixup(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *temp; + + PJ_CHECK_STACK(); + + while (node != tree->root && node->color == PJ_RBCOLOR_BLACK) { + if (node->parent->left == node) { + temp = node->parent->right; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node->parent->color = PJ_RBCOLOR_RED; + left_rotate(tree, node->parent); + temp = node->parent->right; + } + if (temp->left->color == PJ_RBCOLOR_BLACK && temp->right->color == PJ_RBCOLOR_BLACK) { + temp->color = PJ_RBCOLOR_RED; + node = node->parent; + } else { + if (temp->right->color == PJ_RBCOLOR_BLACK) { + temp->left->color = PJ_RBCOLOR_BLACK; + temp->color = PJ_RBCOLOR_RED; + right_rotate(tree, temp); + temp = node->parent->right; + } + temp->color = node->parent->color; + temp->right->color = PJ_RBCOLOR_BLACK; + node->parent->color = PJ_RBCOLOR_BLACK; + left_rotate(tree, node->parent); + node = tree->root; + } + } else { + temp = node->parent->left; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node->parent->color = PJ_RBCOLOR_RED; + right_rotate(tree, node->parent); + temp = node->parent->left; + } + if (temp->right->color == PJ_RBCOLOR_BLACK && temp->left->color == PJ_RBCOLOR_BLACK) { + temp->color = PJ_RBCOLOR_RED; + node = node->parent; + } else { + if (temp->left->color == PJ_RBCOLOR_BLACK) { + temp->right->color = PJ_RBCOLOR_BLACK; + temp->color = PJ_RBCOLOR_RED; + left_rotate(tree, temp); + temp = node->parent->left; + } + temp->color = node->parent->color; + node->parent->color = PJ_RBCOLOR_BLACK; + temp->left->color = PJ_RBCOLOR_BLACK; + right_rotate(tree, node->parent); + node = tree->root; + } + } + } + + node->color = PJ_RBCOLOR_BLACK; +} + +PJ_DEF(void) pj_rbtree_init(pj_rbtree *tree, pj_rbtree_comp *comp) +{ + PJ_CHECK_STACK(); + + tree->null = tree->root = &tree->null_node; + tree->null->key = NULL; + tree->null->user_data = NULL; + tree->size = 0; + tree->null->left = tree->null->right = tree->null->parent = tree->null; + tree->null->color = PJ_RBCOLOR_BLACK; + tree->comp = comp; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_first(pj_rbtree *tree) +{ + register pj_rbtree_node *node = tree->root; + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + while (node->left != null) + node = node->left; + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_last(pj_rbtree *tree) +{ + register pj_rbtree_node *node = tree->root; + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + while (node->right != null) + node = node->right; + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_next(pj_rbtree *tree, register pj_rbtree_node *node) +{ + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + if (node->right != null) { + for (node = node->right; node->left != null; node = node->left) + /* void */; + } else { + register pj_rbtree_node *temp = node->parent; + while (temp != null && temp->right == node) { + node = temp; + temp = temp->parent; + } + node = temp; + } + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_prev(pj_rbtree *tree, register pj_rbtree_node *node) +{ + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + if (node->left != null) { + for (node = node->left; node->right != null; node = node->right) + /* void */; + } else { + register pj_rbtree_node *temp = node->parent; + while (temp != null && temp->left == node) { + node = temp; + temp = temp->parent; + } + node = temp; + } + return node != null ? node : NULL; +} + +PJ_DEF(int) pj_rbtree_insert(pj_rbtree *tree, pj_rbtree_node *element) +{ + int rv = 0; + pj_rbtree_node *node, *parent = tree->null, *null = tree->null; + pj_rbtree_comp *comp = tree->comp; + + PJ_CHECK_STACK(); + + node = tree->root; + while (node != null) { + rv = (*comp)(element->key, node->key); + if (rv == 0) { + /* found match, i.e. entry with equal key already exist */ + return -1; + } + parent = node; + node = rv < 0 ? node->left : node->right; + } + + element->color = PJ_RBCOLOR_RED; + element->left = element->right = null; + + node = element; + if (parent != null) { + node->parent = parent; + if (rv < 0) + parent->left = node; + else + parent->right = node; + insert_fixup(tree, node); + } else { + tree->root = node; + node->parent = null; + node->color = PJ_RBCOLOR_BLACK; + } + + ++tree->size; + return 0; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_find(pj_rbtree *tree, const void *key) +{ + int rv; + pj_rbtree_node *node = tree->root; + pj_rbtree_node *null = tree->null; + pj_rbtree_comp *comp = tree->comp; + + while (node != null) { + rv = (*comp)(key, node->key); + if (rv == 0) + return node; + node = rv < 0 ? node->left : node->right; + } + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_erase(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *succ; + pj_rbtree_node *null = tree->null; + pj_rbtree_node *child; + pj_rbtree_node *parent; + + PJ_CHECK_STACK(); + + if (node->left == null || node->right == null) { + succ = node; + } else { + for (succ = node->right; succ->left != null; succ = succ->left) + /* void */; + } + + child = succ->left != null ? succ->left : succ->right; + parent = succ->parent; + child->parent = parent; + + if (parent != null) { + if (parent->left == succ) + parent->left = child; + else + parent->right = child; + } else + tree->root = child; + + if (succ != node) { + succ->parent = node->parent; + succ->left = node->left; + succ->right = node->right; + succ->color = node->color; + + parent = node->parent; + if (parent != null) { + if (parent->left == node) + parent->left = succ; + else + parent->right = succ; + } + if (node->left != null) + node->left->parent = succ; + ; + if (node->right != null) + node->right->parent = succ; + + if (tree->root == node) + tree->root = succ; + } + + if (succ->color == PJ_RBCOLOR_BLACK) { + if (child != null) + delete_fixup(tree, child); + tree->null->color = PJ_RBCOLOR_BLACK; + } + + --tree->size; + return node; +} + +PJ_DEF(unsigned) pj_rbtree_max_height(pj_rbtree *tree, pj_rbtree_node *node) +{ + unsigned l, r; + + PJ_CHECK_STACK(); + + if (node == NULL) + node = tree->root; + + l = node->left != tree->null ? pj_rbtree_max_height(tree, node->left) + 1 : 0; + r = node->right != tree->null ? pj_rbtree_max_height(tree, node->right) + 1 : 0; + return l > r ? l : r; +} + +PJ_DEF(unsigned) pj_rbtree_min_height(pj_rbtree *tree, pj_rbtree_node *node) +{ + unsigned l, r; + + PJ_CHECK_STACK(); + + if (node == NULL) + node = tree->root; + + l = (node->left != tree->null) ? pj_rbtree_max_height(tree, node->left) + 1 : 0; + r = (node->right != tree->null) ? pj_rbtree_max_height(tree, node->right) + 1 : 0; + return l > r ? r : l; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c new file mode 100755 index 000000000..acd53fb0b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c @@ -0,0 +1,833 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "sock_bsd.c" + +/* + * Address families conversion. + * The values here are indexed based on pj_addr_family. + */ +const pj_uint16_t PJ_AF_UNSPEC = AF_UNSPEC; +const pj_uint16_t PJ_AF_UNIX = AF_UNIX; +const pj_uint16_t PJ_AF_INET = AF_INET; +const pj_uint16_t PJ_AF_INET6 = AF_INET6; +#ifdef AF_PACKET +const pj_uint16_t PJ_AF_PACKET = AF_PACKET; +#else +const pj_uint16_t PJ_AF_PACKET = 0xFFFF; +#endif +#ifdef AF_IRDA +const pj_uint16_t PJ_AF_IRDA = AF_IRDA; +#else +const pj_uint16_t PJ_AF_IRDA = 0xFFFF; +#endif + +/* + * Socket types conversion. + * The values here are indexed based on pj_sock_type + */ +const pj_uint16_t PJ_SOCK_STREAM = SOCK_STREAM; +const pj_uint16_t PJ_SOCK_DGRAM = SOCK_DGRAM; +const pj_uint16_t PJ_SOCK_RAW = SOCK_RAW; +const pj_uint16_t PJ_SOCK_RDM = SOCK_RDM; + +/* + * Socket level values. + */ +const pj_uint16_t PJ_SOL_SOCKET = SOL_SOCKET; +#if (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) || (defined(IPPROTO_IP)) +const pj_uint16_t PJ_SOL_IP = IPPROTO_IP; +#elif defined(SOL_IP) +const pj_uint16_t PJ_SOL_IP = SOL_IP; +#else +const pj_uint16_t PJ_SOL_IP = 0; +#endif /* SOL_IP */ + +#if defined(SOL_TCP) +const pj_uint16_t PJ_SOL_TCP = SOL_TCP; +#elif defined(IPPROTO_TCP) +const pj_uint16_t PJ_SOL_TCP = IPPROTO_TCP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_TCP = IPPROTO_TCP; +#else +const pj_uint16_t PJ_SOL_TCP = 6; +#endif /* SOL_TCP */ + +#ifdef SOL_UDP +const pj_uint16_t PJ_SOL_UDP = SOL_UDP; +#elif defined(IPPROTO_UDP) +const pj_uint16_t PJ_SOL_UDP = IPPROTO_UDP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_UDP = IPPROTO_UDP; +#else +const pj_uint16_t PJ_SOL_UDP = 17; +#endif /* SOL_UDP */ + +#ifdef SOL_IPV6 +const pj_uint16_t PJ_SOL_IPV6 = SOL_IPV6; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +#if defined(IPPROTO_IPV6) || (_WIN32_WINNT >= 0x0501) +const pj_uint16_t PJ_SOL_IPV6 = IPPROTO_IPV6; +#else +const pj_uint16_t PJ_SOL_IPV6 = 41; +#endif +#else +const pj_uint16_t PJ_SOL_IPV6 = 41; +#endif /* SOL_IPV6 */ + +/* IP_TOS */ +#ifdef IP_TOS +const pj_uint16_t PJ_IP_TOS = IP_TOS; +#else +const pj_uint16_t PJ_IP_TOS = 1; +#endif + +/* TOS settings (declared in netinet/ip.h) */ +#ifdef IPTOS_LOWDELAY +const pj_uint16_t PJ_IPTOS_LOWDELAY = IPTOS_LOWDELAY; +#else +const pj_uint16_t PJ_IPTOS_LOWDELAY = 0x10; +#endif +#ifdef IPTOS_THROUGHPUT +const pj_uint16_t PJ_IPTOS_THROUGHPUT = IPTOS_THROUGHPUT; +#else +const pj_uint16_t PJ_IPTOS_THROUGHPUT = 0x08; +#endif +#ifdef IPTOS_RELIABILITY +const pj_uint16_t PJ_IPTOS_RELIABILITY = IPTOS_RELIABILITY; +#else +const pj_uint16_t PJ_IPTOS_RELIABILITY = 0x04; +#endif +#ifdef IPTOS_MINCOST +const pj_uint16_t PJ_IPTOS_MINCOST = IPTOS_MINCOST; +#else +const pj_uint16_t PJ_IPTOS_MINCOST = 0x02; +#endif + +/* IPV6_TCLASS */ +#ifdef IPV6_TCLASS +const pj_uint16_t PJ_IPV6_TCLASS = IPV6_TCLASS; +#else +const pj_uint16_t PJ_IPV6_TCLASS = 0xFFFF; +#endif + +/* optname values. */ +const pj_uint16_t PJ_SO_TYPE = SO_TYPE; +const pj_uint16_t PJ_SO_RCVBUF = SO_RCVBUF; +const pj_uint16_t PJ_SO_SNDBUF = SO_SNDBUF; +const pj_uint16_t PJ_TCP_NODELAY = TCP_NODELAY; +const pj_uint16_t PJ_SO_REUSEADDR = SO_REUSEADDR; +#ifdef SO_NOSIGPIPE +const pj_uint16_t PJ_SO_NOSIGPIPE = SO_NOSIGPIPE; +#else +const pj_uint16_t PJ_SO_NOSIGPIPE = 0xFFFF; +#endif +#if defined(SO_PRIORITY) +const pj_uint16_t PJ_SO_PRIORITY = SO_PRIORITY; +#else +/* This is from Linux, YMMV */ +const pj_uint16_t PJ_SO_PRIORITY = 12; +#endif + +/* Multicasting is not supported e.g. in PocketPC 2003 SDK */ +#ifdef IP_MULTICAST_IF +const pj_uint16_t PJ_IP_MULTICAST_IF = IP_MULTICAST_IF; +const pj_uint16_t PJ_IP_MULTICAST_TTL = IP_MULTICAST_TTL; +const pj_uint16_t PJ_IP_MULTICAST_LOOP = IP_MULTICAST_LOOP; +const pj_uint16_t PJ_IP_ADD_MEMBERSHIP = IP_ADD_MEMBERSHIP; +const pj_uint16_t PJ_IP_DROP_MEMBERSHIP = IP_DROP_MEMBERSHIP; +#else +const pj_uint16_t PJ_IP_MULTICAST_IF = 0xFFFF; +const pj_uint16_t PJ_IP_MULTICAST_TTL = 0xFFFF; +const pj_uint16_t PJ_IP_MULTICAST_LOOP = 0xFFFF; +const pj_uint16_t PJ_IP_ADD_MEMBERSHIP = 0xFFFF; +const pj_uint16_t PJ_IP_DROP_MEMBERSHIP = 0xFFFF; +#endif + +/* recv() and send() flags */ +const int PJ_MSG_OOB = MSG_OOB; +const int PJ_MSG_PEEK = MSG_PEEK; +const int PJ_MSG_DONTROUTE = MSG_DONTROUTE; + +#if 0 +static void CHECK_ADDR_LEN(const pj_sockaddr *addr, int len) +{ + pj_sockaddr *a = (pj_sockaddr*)addr; + pj_assert((a->addr.sa_family==PJ_AF_INET && len==sizeof(pj_sockaddr_in)) || + (a->addr.sa_family==PJ_AF_INET6 && len==sizeof(pj_sockaddr_in6))); + +} +#else +#define CHECK_ADDR_LEN(addr, len) +#endif + +/* + * Convert 16-bit value from network byte order to host byte order. + */ +PJ_DEF(pj_uint16_t) pj_ntohs(pj_uint16_t netshort) +{ + return ntohs(netshort); +} + +/* + * Convert 16-bit value from host byte order to network byte order. + */ +PJ_DEF(pj_uint16_t) pj_htons(pj_uint16_t hostshort) +{ + return htons(hostshort); +} + +/* + * Convert 32-bit value from network byte order to host byte order. + */ +PJ_DEF(pj_uint32_t) pj_ntohl(pj_uint32_t netlong) +{ + return ntohl(netlong); +} + +/* + * Convert 32-bit value from host byte order to network byte order. + */ +PJ_DEF(pj_uint32_t) pj_htonl(pj_uint32_t hostlong) +{ + return htonl(hostlong); +} + +/* + * Convert an Internet host address given in network byte order + * to string in standard numbers and dots notation. + */ +PJ_DEF(char *) pj_inet_ntoa(pj_in_addr inaddr) +{ +#if 0 + return inet_ntoa(*(struct in_addr*)&inaddr); +#else + struct in_addr addr; + // addr.s_addr = inaddr.s_addr; + pj_memcpy(&addr, &inaddr, sizeof(addr)); + return inet_ntoa(addr); +#endif +} + +/* + * This function converts the Internet host address cp from the standard + * numbers-and-dots notation into binary data and stores it in the structure + * that inp points to. + */ +PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp) +{ + char tempaddr[PJ_INET_ADDRSTRLEN]; + + /* Initialize output with PJ_INADDR_NONE. + * Some apps relies on this instead of the return value + * (and anyway the return value is quite confusing!) + */ + inp->s_addr = PJ_INADDR_NONE; + + /* Caution: + * this function might be called with cp->slen >= 16 + * (i.e. when called with hostname to check if it's an IP addr). + */ + PJ_ASSERT_RETURN(cp && cp->slen && inp, 0); + if (cp->slen >= PJ_INET_ADDRSTRLEN) { + return 0; + } + + pj_memcpy(tempaddr, cp->ptr, cp->slen); + tempaddr[cp->slen] = '\0'; + +#if defined(PJ_SOCK_HAS_INET_ATON) && PJ_SOCK_HAS_INET_ATON != 0 + return inet_aton(tempaddr, (struct in_addr *)inp); +#else + inp->s_addr = inet_addr(tempaddr); + return inp->s_addr == PJ_INADDR_NONE ? 0 : 1; +#endif +} + +/* + * Convert text to IPv4/IPv6 address. + */ +PJ_DEF(pj_status_t) pj_inet_pton(int af, const pj_str_t *src, void *dst) +{ + char tempaddr[PJ_INET6_ADDRSTRLEN]; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(src && src->slen && dst, PJ_EINVAL); + + /* Initialize output with PJ_IN_ADDR_NONE for IPv4 (to be + * compatible with pj_inet_aton() + */ + if (af == PJ_AF_INET) { + ((pj_in_addr *)dst)->s_addr = PJ_INADDR_NONE; + } + + /* Caution: + * this function might be called with cp->slen >= 46 + * (i.e. when called with hostname to check if it's an IP addr). + */ + if (src->slen >= PJ_INET6_ADDRSTRLEN) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(tempaddr, src->ptr, src->slen); + tempaddr[src->slen] = '\0'; + +#if defined(PJ_SOCK_HAS_INET_PTON) && PJ_SOCK_HAS_INET_PTON != 0 + /* + * Implementation using inet_pton() + */ + if (inet_pton(af, tempaddr, dst) != 1) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + +#elif defined(PJ_WIN32) || defined(PJ_WIN64) || defined(PJ_WIN32_WINCE) + /* + * Implementation on Windows, using WSAStringToAddress(). + * Should also work on Unicode systems. + */ + { + PJ_DECL_UNICODE_TEMP_BUF(wtempaddr, PJ_INET6_ADDRSTRLEN) + pj_sockaddr sock_addr; + int addr_len = sizeof(sock_addr); + int rc; + + sock_addr.addr.sa_family = (pj_uint16_t)af; + rc = WSAStringToAddress(PJ_STRING_TO_NATIVE(tempaddr, wtempaddr, sizeof(wtempaddr)), af, NULL, + (LPSOCKADDR)&sock_addr, &addr_len); + if (rc != 0) { + /* If you get rc 130022 Invalid argument (WSAEINVAL) with IPv6, + * check that you have IPv6 enabled (install it in the network + * adapter). + */ + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + if (sock_addr.addr.sa_family == PJ_AF_INET) { + pj_memcpy(dst, &sock_addr.ipv4.sin_addr, 4); + return PJ_SUCCESS; + } else if (sock_addr.addr.sa_family == PJ_AF_INET6) { + pj_memcpy(dst, &sock_addr.ipv6.sin6_addr, 16); + return PJ_SUCCESS; + } else { + pj_assert(!"Shouldn't happen"); + return PJ_EBUG; + } + } +#elif !defined(PJ_HAS_IPV6) || PJ_HAS_IPV6 == 0 + /* IPv6 support is disabled, just return error without raising assertion */ + return PJ_EIPV6NOTSUP; +#else + pj_assert(!"Not supported"); + return PJ_EIPV6NOTSUP; +#endif +} + +/* + * Convert IPv4/IPv6 address to text. + */ +PJ_DEF(pj_status_t) pj_inet_ntop(int af, const void *src, char *dst, int size) + +{ + PJ_ASSERT_RETURN(src && dst && size, PJ_EINVAL); + + *dst = '\0'; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EAFNOTSUP); + +#if defined(PJ_SOCK_HAS_INET_NTOP) && PJ_SOCK_HAS_INET_NTOP != 0 + /* + * Implementation using inet_ntop() + */ + if (inet_ntop(af, src, dst, size) == NULL) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + +#elif defined(PJ_WIN32) || defined(PJ_WIN64) || defined(PJ_WIN32_WINCE) + /* + * Implementation on Windows, using WSAAddressToString(). + * Should also work on Unicode systems. + */ + { + PJ_DECL_UNICODE_TEMP_BUF(wtempaddr, PJ_INET6_ADDRSTRLEN) + pj_sockaddr sock_addr; + DWORD addr_len, addr_str_len; + int rc; + + pj_bzero(&sock_addr, sizeof(sock_addr)); + sock_addr.addr.sa_family = (pj_uint16_t)af; + if (af == PJ_AF_INET) { + if (size < PJ_INET_ADDRSTRLEN) + return PJ_ETOOSMALL; + pj_memcpy(&sock_addr.ipv4.sin_addr, src, 4); + addr_len = sizeof(pj_sockaddr_in); + addr_str_len = PJ_INET_ADDRSTRLEN; + } else if (af == PJ_AF_INET6) { + if (size < PJ_INET6_ADDRSTRLEN) + return PJ_ETOOSMALL; + pj_memcpy(&sock_addr.ipv6.sin6_addr, src, 16); + addr_len = sizeof(pj_sockaddr_in6); + addr_str_len = PJ_INET6_ADDRSTRLEN; + } else { + pj_assert(!"Unsupported address family"); + return PJ_EAFNOTSUP; + } + +#if PJ_NATIVE_STRING_IS_UNICODE + rc = WSAAddressToString((LPSOCKADDR)&sock_addr, addr_len, NULL, wtempaddr, &addr_str_len); + if (rc == 0) { + pj_unicode_to_ansi(wtempaddr, wcslen(wtempaddr), dst, size); + } +#else + rc = WSAAddressToString((LPSOCKADDR)&sock_addr, addr_len, NULL, dst, &addr_str_len); +#endif + + if (rc != 0) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + } + +#elif !defined(PJ_HAS_IPV6) || PJ_HAS_IPV6 == 0 + /* IPv6 support is disabled, just return error without raising assertion */ + return PJ_EIPV6NOTSUP; +#else + pj_assert(!"Not supported"); + return PJ_EIPV6NOTSUP; +#endif +} + +/* + * Get hostname. + */ +PJ_DEF(const pj_str_t *) pj_gethostname(void) +{ + static char buf[PJ_MAX_HOSTNAME]; + static pj_str_t hostname; + + PJ_CHECK_STACK(); + + if (hostname.ptr == NULL) { + hostname.ptr = buf; + if (gethostname(buf, sizeof(buf)) != 0) { + hostname.ptr[0] = '\0'; + hostname.slen = 0; + } else { + hostname.slen = strlen(buf); + } + } + return &hostname; +} + +#if defined(PJ_WIN32) || defined(PJ_WIN64) +/* + * Create new socket/endpoint for communication and returns a descriptor. + */ +PJ_DEF(pj_status_t) pj_sock_socket(int af, int type, int proto, pj_sock_t *sock) +{ + PJ_CHECK_STACK(); + + /* Sanity checks. */ + PJ_ASSERT_RETURN(sock != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN((SOCKET)PJ_INVALID_SOCKET == INVALID_SOCKET, (*sock = PJ_INVALID_SOCKET, PJ_EINVAL)); + + *sock = WSASocket(af, type, proto, NULL, 0, WSA_FLAG_OVERLAPPED); + + if (*sock == PJ_INVALID_SOCKET) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + +#if PJ_SOCK_DISABLE_WSAECONNRESET && (!defined(PJ_WIN32_WINCE) || PJ_WIN32_WINCE == 0) + +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12) +#endif + + /* Disable WSAECONNRESET for UDP. + * See https://github.com/pjsip/pjproject/issues/1197 + */ + if (type == PJ_SOCK_DGRAM) { + DWORD dwBytesReturned = 0; + BOOL bNewBehavior = FALSE; + DWORD rc; + + rc = WSAIoctl(*sock, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, + NULL); + + if (rc == SOCKET_ERROR) { + // Ignored.. + } + } +#endif + + return PJ_SUCCESS; +} + +#else +/* + * Create new socket/endpoint for communication and returns a descriptor. + */ +PJ_DEF(pj_status_t) pj_sock_socket(int af, int type, int proto, pj_sock_t *sock) +{ + + PJ_CHECK_STACK(); + + /* Sanity checks. */ + PJ_ASSERT_RETURN(sock != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_INVALID_SOCKET == -1, (*sock = PJ_INVALID_SOCKET, PJ_EINVAL)); + + *sock = socket(af, type, proto); + if (*sock == PJ_INVALID_SOCKET) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + pj_int32_t val = 1; + if (type == pj_SOCK_STREAM()) { + pj_sock_setsockopt(*sock, pj_SOL_SOCKET(), pj_SO_NOSIGPIPE(), &val, sizeof(val)); + } +#if defined(PJ_SOCK_HAS_IPV6_V6ONLY) && PJ_SOCK_HAS_IPV6_V6ONLY != 0 + if (af == PJ_AF_INET6) { + pj_sock_setsockopt(*sock, PJ_SOL_IPV6, IPV6_V6ONLY, &val, sizeof(val)); + } +#endif +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + if (type == pj_SOCK_DGRAM()) { + pj_sock_setsockopt(*sock, pj_SOL_SOCKET(), SO_NOSIGPIPE, &val, sizeof(val)); + } +#endif + return PJ_SUCCESS; + } +} +#endif + +/* + * Bind socket. + */ +PJ_DEF(pj_status_t) pj_sock_bind(pj_sock_t sock, const pj_sockaddr_t *addr, int len) +{ + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(addr && len >= (int)sizeof(struct sockaddr_in), PJ_EINVAL); + + CHECK_ADDR_LEN(addr, len); + + if (bind(sock, (struct sockaddr *)addr, len) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Bind socket. + */ +PJ_DEF(pj_status_t) pj_sock_bind_in(pj_sock_t sock, pj_uint32_t addr32, pj_uint16_t port) +{ + pj_sockaddr_in addr; + + PJ_CHECK_STACK(); + + PJ_SOCKADDR_SET_LEN(&addr, sizeof(pj_sockaddr_in)); + addr.sin_family = PJ_AF_INET; + pj_bzero(addr.sin_zero_pad, sizeof(addr.sin_zero_pad)); + addr.sin_addr.s_addr = pj_htonl(addr32); + addr.sin_port = pj_htons(port); + + return pj_sock_bind(sock, &addr, sizeof(pj_sockaddr_in)); +} + +/* + * Close socket. + */ +PJ_DEF(pj_status_t) pj_sock_close(pj_sock_t sock) +{ + int rc; + + PJ_CHECK_STACK(); +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + rc = closesocket(sock); +#else + rc = close(sock); +#endif + + if (rc != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Get remote's name. + */ +PJ_DEF(pj_status_t) pj_sock_getpeername(pj_sock_t sock, pj_sockaddr_t *addr, int *namelen) +{ + PJ_CHECK_STACK(); + if (getpeername(sock, (struct sockaddr *)addr, (socklen_t *)namelen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + PJ_SOCKADDR_RESET_LEN(addr); + return PJ_SUCCESS; + } +} + +/* + * Get socket name. + */ +PJ_DEF(pj_status_t) pj_sock_getsockname(pj_sock_t sock, pj_sockaddr_t *addr, int *namelen) +{ + PJ_CHECK_STACK(); + if (getsockname(sock, (struct sockaddr *)addr, (socklen_t *)namelen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + PJ_SOCKADDR_RESET_LEN(addr); + return PJ_SUCCESS; + } +} + +/* + * Send data + */ +PJ_DEF(pj_status_t) pj_sock_send(pj_sock_t sock, const void *buf, pj_ssize_t *len, unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(len, PJ_EINVAL); + +#ifdef MSG_NOSIGNAL + /* Suppress SIGPIPE. See https://github.com/pjsip/pjproject/issues/1538 */ + flags |= MSG_NOSIGNAL; +#endif + + *len = send(sock, (const char *)buf, (int)(*len), flags); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Send data. + */ +PJ_DEF(pj_status_t) +pj_sock_sendto(pj_sock_t sock, const void *buf, pj_ssize_t *len, unsigned flags, const pj_sockaddr_t *to, int tolen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(len, PJ_EINVAL); + + CHECK_ADDR_LEN(to, tolen); + + *len = sendto(sock, (const char *)buf, (int)(*len), flags, (const struct sockaddr *)to, tolen); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Receive data. + */ +PJ_DEF(pj_status_t) pj_sock_recv(pj_sock_t sock, void *buf, pj_ssize_t *len, unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(buf && len, PJ_EINVAL); + + *len = recv(sock, (char *)buf, (int)(*len), flags); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Receive data. + */ +PJ_DEF(pj_status_t) +pj_sock_recvfrom(pj_sock_t sock, void *buf, pj_ssize_t *len, unsigned flags, pj_sockaddr_t *from, int *fromlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(buf && len, PJ_EINVAL); + + *len = recvfrom(sock, (char *)buf, (int)(*len), flags, (struct sockaddr *)from, (socklen_t *)fromlen); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + if (from) { + PJ_SOCKADDR_RESET_LEN(from); + } + return PJ_SUCCESS; + } +} + +/* + * Get socket option. + */ +PJ_DEF(pj_status_t) +pj_sock_getsockopt(pj_sock_t sock, pj_uint16_t level, pj_uint16_t optname, void *optval, int *optlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(optval && optlen, PJ_EINVAL); + + if (getsockopt(sock, level, optname, (char *)optval, (socklen_t *)optlen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Set socket option. + */ +PJ_DEF(pj_status_t) +pj_sock_setsockopt(pj_sock_t sock, pj_uint16_t level, pj_uint16_t optname, const void *optval, int optlen) +{ + int status; + PJ_CHECK_STACK(); + +#if (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_SUNOS) && PJ_SUNOS) + /* Some opt may still need int value (e.g:SO_EXCLUSIVEADDRUSE in win32). */ + status = setsockopt(sock, level, ((optname & 0xff00) == 0xff00) ? (int)optname | 0xffff0000 : optname, + (const char *)optval, optlen); +#else + status = setsockopt(sock, level, optname, (const char *)optval, optlen); +#endif + + if (status != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Set socket option. + */ +PJ_DEF(pj_status_t) pj_sock_setsockopt_params(pj_sock_t sockfd, const pj_sockopt_params *params) +{ + unsigned int i = 0; + pj_status_t retval = PJ_SUCCESS; + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(params, PJ_EINVAL); + + for (; i < params->cnt && i < PJ_MAX_SOCKOPT_PARAMS; ++i) { + pj_status_t status = + pj_sock_setsockopt(sockfd, (pj_uint16_t)params->options[i].level, (pj_uint16_t)params->options[i].optname, + params->options[i].optval, params->options[i].optlen); + if (status != PJ_SUCCESS) { + retval = status; + PJ_PERROR(4, (THIS_FILE, status, "Warning: error applying sock opt %d", params->options[i].optname)); + } + } + + return retval; +} + +/* + * Connect socket. + */ +PJ_DEF(pj_status_t) pj_sock_connect(pj_sock_t sock, const pj_sockaddr_t *addr, int namelen) +{ + PJ_CHECK_STACK(); + if (connect(sock, (struct sockaddr *)addr, namelen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Shutdown socket. + */ +#if PJ_HAS_TCP +PJ_DEF(pj_status_t) pj_sock_shutdown(pj_sock_t sock, int how) +{ + PJ_CHECK_STACK(); + if (shutdown(sock, how) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Start listening to incoming connections. + */ +PJ_DEF(pj_status_t) pj_sock_listen(pj_sock_t sock, int backlog) +{ + PJ_CHECK_STACK(); + if (listen(sock, backlog) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Accept incoming connections + */ +PJ_DEF(pj_status_t) pj_sock_accept(pj_sock_t serverfd, pj_sock_t *newsock, pj_sockaddr_t *addr, int *addrlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(newsock != NULL, PJ_EINVAL); + +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + if (addr) { + PJ_SOCKADDR_SET_LEN(addr, *addrlen); + } +#endif + + *newsock = accept(serverfd, (struct sockaddr *)addr, (socklen_t *)addrlen); + if (*newsock == PJ_INVALID_SOCKET) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + if (addr) { + PJ_SOCKADDR_RESET_LEN(addr); + } +#endif + + return PJ_SUCCESS; + } +} +#endif /* PJ_HAS_TCP */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c new file mode 100755 index 000000000..87f1030dc --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c @@ -0,0 +1,1432 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 + /* Enable some tracing */ +#include +#define THIS_FILE "sock_common.c" +#define TRACE_(arg) PJ_LOG(4, arg) +#else +#define TRACE_(arg) +#endif + +/* + * Convert address string with numbers and dots to binary IP address. + */ +PJ_DEF(pj_in_addr) pj_inet_addr(const pj_str_t *cp) +{ + pj_in_addr addr; + + pj_inet_aton(cp, &addr); + return addr; +} + +/* + * Convert address string with numbers and dots to binary IP address. + */ +PJ_DEF(pj_in_addr) pj_inet_addr2(const char *cp) +{ + pj_str_t str = pj_str((char *)cp); + return pj_inet_addr(&str); +} + +/* + * Get text representation. + */ +PJ_DEF(char *) pj_inet_ntop2(int af, const void *src, char *dst, int size) +{ + pj_status_t status; + + status = pj_inet_ntop(af, src, dst, size); + return (status == PJ_SUCCESS) ? dst : NULL; +} + +/* + * Print socket address. + */ +PJ_DEF(char *) pj_sockaddr_print(const pj_sockaddr_t *addr, char *buf, int size, unsigned flags) +{ + enum { WITH_PORT = 1, WITH_BRACKETS = 2 }; + + char txt[PJ_INET6_ADDRSTRLEN]; + char port[32]; + const pj_addr_hdr *h = (const pj_addr_hdr *)addr; + char *bquote, *equote; + pj_status_t status; + + status = pj_inet_ntop(h->sa_family, pj_sockaddr_get_addr(addr), txt, sizeof(txt)); + if (status != PJ_SUCCESS) + return ""; + + if (h->sa_family != PJ_AF_INET6 || (flags & WITH_BRACKETS) == 0) { + bquote = ""; + equote = ""; + } else { + bquote = "["; + equote = "]"; + } + + if (flags & WITH_PORT) { + pj_ansi_snprintf(port, sizeof(port), ":%d", pj_sockaddr_get_port(addr)); + } else { + port[0] = '\0'; + } + + pj_ansi_snprintf(buf, size, "%s%s%s%s", bquote, txt, equote, port); + + return buf; +} + +/* + * Set the IP address of an IP socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard numbers and dots notation or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_in_set_str_addr(pj_sockaddr_in *addr, const pj_str_t *str_addr) +{ + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(!str_addr || str_addr->slen < PJ_MAX_HOSTNAME, + (addr->sin_addr.s_addr = PJ_INADDR_NONE, PJ_EINVAL)); + + PJ_SOCKADDR_RESET_LEN(addr); + addr->sin_family = PJ_AF_INET; + pj_bzero(addr->sin_zero_pad, sizeof(addr->sin_zero_pad)); + + if (str_addr && str_addr->slen) { + addr->sin_addr = pj_inet_addr(str_addr); + if (addr->sin_addr.s_addr == PJ_INADDR_NONE) { + pj_addrinfo ai; + unsigned count = 1; + pj_status_t status; + + status = pj_getaddrinfo(pj_AF_INET(), str_addr, &count, &ai); + if (status == PJ_SUCCESS) { + pj_memcpy(&addr->sin_addr, &ai.ai_addr.ipv4.sin_addr, sizeof(addr->sin_addr)); + } else { + return status; + } + } + + } else { + addr->sin_addr.s_addr = 0; + } + + return PJ_SUCCESS; +} + +/* Set address from a name */ +PJ_DEF(pj_status_t) pj_sockaddr_set_str_addr(int af, pj_sockaddr *addr, const pj_str_t *str_addr) +{ + pj_status_t status; + + if (af == PJ_AF_INET) { + return pj_sockaddr_in_set_str_addr(&addr->ipv4, str_addr); + } + + PJ_ASSERT_RETURN(af == PJ_AF_INET6, PJ_EAFNOTSUP); + + /* IPv6 specific */ + + addr->ipv6.sin6_family = PJ_AF_INET6; + PJ_SOCKADDR_RESET_LEN(addr); + + if (str_addr && str_addr->slen) { +#if defined(PJ_SOCKADDR_USE_GETADDRINFO) && PJ_SOCKADDR_USE_GETADDRINFO != 0 + if (1) { +#else + status = pj_inet_pton(PJ_AF_INET6, str_addr, &addr->ipv6.sin6_addr); + if (status != PJ_SUCCESS) { +#endif + pj_addrinfo ai; + unsigned count = 1; + + status = pj_getaddrinfo(PJ_AF_INET6, str_addr, &count, &ai); + if (status == PJ_SUCCESS) { + pj_memcpy(&addr->ipv6.sin6_addr, &ai.ai_addr.ipv6.sin6_addr, sizeof(addr->ipv6.sin6_addr)); + addr->ipv6.sin6_scope_id = ai.ai_addr.ipv6.sin6_scope_id; + } + } + } else { + status = PJ_SUCCESS; + } + + return status; +} + +/* + * Set the IP address and port of an IP socket address. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_in_init(pj_sockaddr_in *addr, const pj_str_t *str_addr, pj_uint16_t port) +{ + PJ_ASSERT_RETURN(addr, (addr->sin_addr.s_addr = PJ_INADDR_NONE, PJ_EINVAL)); + + PJ_SOCKADDR_RESET_LEN(addr); + addr->sin_family = PJ_AF_INET; + pj_bzero(addr->sin_zero_pad, sizeof(addr->sin_zero_pad)); + pj_sockaddr_in_set_port(addr, port); + return pj_sockaddr_in_set_str_addr(addr, str_addr); +} + +/* + * Initialize IP socket address based on the address and port info. + */ +PJ_DEF(pj_status_t) pj_sockaddr_init(int af, pj_sockaddr *addr, const pj_str_t *cp, pj_uint16_t port) +{ + pj_status_t status; + + if (af == PJ_AF_INET) { + return pj_sockaddr_in_init(&addr->ipv4, cp, port); + } + + /* IPv6 specific */ + PJ_ASSERT_RETURN(af == PJ_AF_INET6, PJ_EAFNOTSUP); + + pj_bzero(addr, sizeof(pj_sockaddr_in6)); + addr->addr.sa_family = PJ_AF_INET6; + + status = pj_sockaddr_set_str_addr(af, addr, cp); + if (status != PJ_SUCCESS) + return status; + + addr->ipv6.sin6_port = pj_htons(port); + return PJ_SUCCESS; +} + +/* + * Compare two socket addresses. + */ +PJ_DEF(int) pj_sockaddr_cmp(const pj_sockaddr_t *addr1, const pj_sockaddr_t *addr2) +{ + const pj_sockaddr *a1 = (const pj_sockaddr *)addr1; + const pj_sockaddr *a2 = (const pj_sockaddr *)addr2; + int port1, port2; + int result; + + /* Compare address family */ + if (a1->addr.sa_family < a2->addr.sa_family) + return -1; + else if (a1->addr.sa_family > a2->addr.sa_family) + return 1; + + /* Compare addresses */ + result = pj_memcmp(pj_sockaddr_get_addr(a1), pj_sockaddr_get_addr(a2), pj_sockaddr_get_addr_len(a1)); + if (result != 0) + return result; + + /* Compare port number */ + port1 = pj_sockaddr_get_port(a1); + port2 = pj_sockaddr_get_port(a2); + + if (port1 < port2) + return -1; + else if (port1 > port2) + return 1; + + /* TODO: + * Do we need to compare flow label and scope id in IPv6? + */ + + /* Looks equal */ + return 0; +} + +/* + * Get first IP address associated with the hostname. + */ +PJ_DEF(pj_in_addr) pj_gethostaddr(void) +{ + pj_sockaddr_in addr; + const pj_str_t *hostname = pj_gethostname(); + + pj_sockaddr_in_set_str_addr(&addr, hostname); + return addr.sin_addr; +} + +/* + * Get port number of a pj_sockaddr_in + */ +PJ_DEF(pj_uint16_t) pj_sockaddr_in_get_port(const pj_sockaddr_in *addr) +{ + return pj_ntohs(addr->sin_port); +} + +/* + * Get the address part + */ +PJ_DEF(void *) pj_sockaddr_get_addr(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, NULL); + + if (a->addr.sa_family == PJ_AF_INET6) + return (void *)&a->ipv6.sin6_addr; + else + return (void *)&a->ipv4.sin_addr; +} + +/* + * Check if sockaddr contains a non-zero address + */ +PJ_DEF(pj_bool_t) pj_sockaddr_has_addr(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + + /* It's probably not wise to raise assertion here if + * the address doesn't contain a valid address family, and + * just return PJ_FALSE instead. + * + * The reason is because application may need to distinguish + * these three conditions with sockaddr: + * a) sockaddr is not initialized. This is by convention + * indicated by sa_family==0. + * b) sockaddr is initialized with zero address. This is + * indicated with the address field having zero address. + * c) sockaddr is initialized with valid address/port. + * + * If we enable this assertion, then application will loose + * the capability to specify condition a), since it will be + * forced to always initialize sockaddr (even with zero address). + * This may break some parts of upper layer libraries. + */ + // PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + // a->addr.sa_family == PJ_AF_INET6, PJ_FALSE); + + if (a->addr.sa_family != PJ_AF_INET && a->addr.sa_family != PJ_AF_INET6) { + return PJ_FALSE; + } else if (a->addr.sa_family == PJ_AF_INET6) { + pj_uint8_t zero[24]; + pj_bzero(zero, sizeof(zero)); + return pj_memcmp(a->ipv6.sin6_addr.s6_addr, zero, sizeof(pj_in6_addr)) != 0; + } else + return a->ipv4.sin_addr.s_addr != PJ_INADDR_ANY; +} + +/* + * Get port number + */ +PJ_DEF(pj_uint16_t) pj_sockaddr_get_port(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, (pj_uint16_t)0xFFFF); + + return pj_ntohs((pj_uint16_t)(a->addr.sa_family == PJ_AF_INET6 ? a->ipv6.sin6_port : a->ipv4.sin_port)); +} + +/* + * Get the length of the address part. + */ +PJ_DEF(unsigned) pj_sockaddr_get_addr_len(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, 0); + return a->addr.sa_family == PJ_AF_INET6 ? sizeof(pj_in6_addr) : sizeof(pj_in_addr); +} + +/* + * Get socket address length. + */ +PJ_DEF(unsigned) pj_sockaddr_get_len(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, 0); + return a->addr.sa_family == PJ_AF_INET6 ? sizeof(pj_sockaddr_in6) : sizeof(pj_sockaddr_in); +} + +/* + * Copy only the address part (sin_addr/sin6_addr) of a socket address. + */ +PJ_DEF(void) pj_sockaddr_copy_addr(pj_sockaddr *dst, const pj_sockaddr *src) +{ + /* Destination sockaddr might not be initialized */ + const char *srcbuf = (char *)pj_sockaddr_get_addr(src); + char *dstbuf = ((char *)dst) + (srcbuf - (char *)src); + pj_memcpy(dstbuf, srcbuf, pj_sockaddr_get_addr_len(src)); +} + +/* + * Copy socket address. + */ +PJ_DEF(void) pj_sockaddr_cp(pj_sockaddr_t *dst, const pj_sockaddr_t *src) +{ + pj_memcpy(dst, src, pj_sockaddr_get_len(src)); +} + +/* + * Synthesize address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_synthesize(int dst_af, pj_sockaddr_t *dst, const pj_sockaddr_t *src) +{ + char ip_addr_buf[PJ_INET6_ADDRSTRLEN]; + unsigned int count = 1; + pj_addrinfo ai[1]; + pj_str_t ip_addr; + pj_status_t status; + + /* Validate arguments */ + PJ_ASSERT_RETURN(src && dst, PJ_EINVAL); + + if (dst_af == ((const pj_sockaddr *)src)->addr.sa_family) { + pj_sockaddr_cp(dst, src); + return PJ_SUCCESS; + } + + pj_sockaddr_print(src, ip_addr_buf, sizeof(ip_addr_buf), 0); + ip_addr = pj_str(ip_addr_buf); + + /* Try to synthesize address using pj_getaddrinfo(). */ + status = pj_getaddrinfo(dst_af, &ip_addr, &count, ai); + if (status == PJ_SUCCESS && count > 0) { + pj_sockaddr_cp(dst, &ai[0].ai_addr); + pj_sockaddr_set_port(dst, pj_sockaddr_get_port(src)); + } + + return status; +} + +/* + * Set port number of pj_sockaddr_in + */ +PJ_DEF(void) pj_sockaddr_in_set_port(pj_sockaddr_in *addr, pj_uint16_t hostport) +{ + addr->sin_port = pj_htons(hostport); +} + +/* + * Set port number of pj_sockaddr + */ +PJ_DEF(pj_status_t) pj_sockaddr_set_port(pj_sockaddr *addr, pj_uint16_t hostport) +{ + int af = addr->addr.sa_family; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + if (af == PJ_AF_INET6) + addr->ipv6.sin6_port = pj_htons(hostport); + else + addr->ipv4.sin_port = pj_htons(hostport); + + return PJ_SUCCESS; +} + +/* + * Get IPv4 address + */ +PJ_DEF(pj_in_addr) pj_sockaddr_in_get_addr(const pj_sockaddr_in *addr) +{ + pj_in_addr in_addr; + in_addr.s_addr = pj_ntohl(addr->sin_addr.s_addr); + return in_addr; +} + +/* + * Set IPv4 address + */ +PJ_DEF(void) pj_sockaddr_in_set_addr(pj_sockaddr_in *addr, pj_uint32_t hostaddr) +{ + addr->sin_addr.s_addr = pj_htonl(hostaddr); +} + +/* + * Parse address + */ +PJ_DEF(pj_status_t) +pj_sockaddr_parse2(int af, unsigned options, const pj_str_t *str, pj_str_t *p_hostpart, pj_uint16_t *p_port, int *raf) +{ + const char *end = str->ptr + str->slen; + const char *last_colon_pos = NULL; + unsigned colon_cnt = 0; + const char *p; + + PJ_ASSERT_RETURN((af == PJ_AF_INET || af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) && options == 0 && str != NULL, + PJ_EINVAL); + + /* Special handling for empty input */ + if (str->slen == 0 || str->ptr == NULL) { + if (p_hostpart) + p_hostpart->slen = 0; + if (p_port) + *p_port = 0; + if (raf) + *raf = PJ_AF_INET; + return PJ_SUCCESS; + } + + /* Count the colon and get the last colon */ + for (p = str->ptr; p != end; ++p) { + if (*p == ':') { + ++colon_cnt; + last_colon_pos = p; + } + } + + /* Deduce address family if it's not given */ + if (af == PJ_AF_UNSPEC) { + if (colon_cnt > 1) + af = PJ_AF_INET6; + else + af = PJ_AF_INET; + } else if (af == PJ_AF_INET && colon_cnt > 1) + return PJ_EINVAL; + + if (raf) + *raf = af; + + if (af == PJ_AF_INET) { + /* Parse as IPv4. Supported formats: + * - "10.0.0.1:80" + * - "10.0.0.1" + * - "10.0.0.1:" + * - ":80" + * - ":" + */ + pj_str_t hostpart; + unsigned long port; + + hostpart.ptr = (char *)str->ptr; + + if (last_colon_pos) { + pj_str_t port_part; + int i; + + hostpart.slen = last_colon_pos - str->ptr; + + port_part.ptr = (char *)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + + /* Make sure port number is valid */ + for (i = 0; i < port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + } else { + hostpart.slen = str->slen; + port = 0; + } + + if (p_hostpart) + *p_hostpart = hostpart; + if (p_port) + *p_port = (pj_uint16_t)port; + + return PJ_SUCCESS; + + } else if (af == PJ_AF_INET6) { + + /* Parse as IPv6. Supported formats: + * - "fe::01:80" ==> note: port number is zero in this case, not 80! + * - "[fe::01]:80" + * - "fe::01" + * - "fe::01:" + * - "[fe::01]" + * - "[fe::01]:" + * - "[::]:80" + * - ":::80" + * - "[::]" + * - "[::]:" + * - ":::" + * - "::" + */ + pj_str_t hostpart, port_part; + + if (*str->ptr == '[') { + char *end_bracket; + int i; + unsigned long port; + + if (last_colon_pos == NULL) + return PJ_EINVAL; + + end_bracket = pj_strchr(str, ']'); + if (end_bracket == NULL) + return PJ_EINVAL; + + hostpart.ptr = (char *)str->ptr + 1; + hostpart.slen = end_bracket - hostpart.ptr; + + if (last_colon_pos < end_bracket) { + port_part.ptr = NULL; + port_part.slen = 0; + } else { + port_part.ptr = (char *)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + } + + /* Make sure port number is valid */ + for (i = 0; i < port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + + if (p_hostpart) + *p_hostpart = hostpart; + if (p_port) + *p_port = (pj_uint16_t)port; + + return PJ_SUCCESS; + + } else { + /* Treat everything as part of the IPv6 IP address */ + if (p_hostpart) + *p_hostpart = *str; + if (p_port) + *p_port = 0; + + return PJ_SUCCESS; + } + + } else { + return PJ_EAFNOTSUP; + } +} + +/* + * Parse address + */ +PJ_DEF(pj_status_t) pj_sockaddr_parse(int af, unsigned options, const pj_str_t *str, pj_sockaddr *addr) +{ + pj_str_t hostpart; + pj_uint16_t port; + pj_status_t status; + + PJ_ASSERT_RETURN(addr, PJ_EINVAL); + PJ_ASSERT_RETURN(af == PJ_AF_UNSPEC || af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + status = pj_sockaddr_parse2(af, options, str, &hostpart, &port, &af); + if (status != PJ_SUCCESS) + return status; + +#if !defined(PJ_HAS_IPV6) || !PJ_HAS_IPV6 + if (af == PJ_AF_INET6) + return PJ_EIPV6NOTSUP; +#endif + + status = pj_sockaddr_init(af, addr, &hostpart, port); +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + if (status != PJ_SUCCESS && af == PJ_AF_INET6) { + /* Parsing does not yield valid address. Try to treat the last + * portion after the colon as port number. + */ + const char *last_colon_pos = NULL, *p; + const char *end = str->ptr + str->slen; + unsigned long long_port; + pj_str_t port_part; + int i; + + /* Parse as IPv6:port */ + for (p = str->ptr; p != end; ++p) { + if (*p == ':') + last_colon_pos = p; + } + + if (last_colon_pos == NULL) + return status; + + hostpart.ptr = (char *)str->ptr; + hostpart.slen = last_colon_pos - str->ptr; + + port_part.ptr = (char *)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + + /* Make sure port number is valid */ + for (i = 0; i < port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return status; + } + long_port = pj_strtoul(&port_part); + if (long_port > 65535) + return status; + + port = (pj_uint16_t)long_port; + + status = pj_sockaddr_init(PJ_AF_INET6, addr, &hostpart, port); + } +#endif + + return status; +} + +/* Resolve the IP address of local machine */ +PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr) +{ + unsigned i, count, cand_cnt; + enum { + CAND_CNT = 8, + + /* Weighting to be applied to found addresses */ + WEIGHT_HOSTNAME = 1, /* hostname IP is not always valid! */ + WEIGHT_DEF_ROUTE = 2, + WEIGHT_INTERFACE = 1, + WEIGHT_LOOPBACK = -5, + WEIGHT_LINK_LOCAL = -4, + WEIGHT_DISABLED = -50, + + MIN_WEIGHT = WEIGHT_DISABLED + 1 /* minimum weight to use */ + }; + /* candidates: */ + pj_sockaddr cand_addr[CAND_CNT]; + int cand_weight[CAND_CNT]; + int selected_cand; + char strip[PJ_INET6_ADDRSTRLEN + 10]; + /* Special IPv4 addresses. */ + struct spec_ipv4_t { + pj_uint32_t addr; + pj_uint32_t mask; + int weight; + } spec_ipv4[] = {/* 127.0.0.0/8, loopback addr will be used if there is no other + * addresses. + */ + {0x7f000000, 0xFF000000, WEIGHT_LOOPBACK}, + + /* 0.0.0.0/8, special IP that doesn't seem to be practically useful */ + {0x00000000, 0xFF000000, WEIGHT_DISABLED}, + + /* 169.254.0.0/16, a zeroconf/link-local address, which has higher + * priority than loopback and will be used if there is no other + * valid addresses. + */ + {0xa9fe0000, 0xFFFF0000, WEIGHT_LINK_LOCAL}}; + /* Special IPv6 addresses */ + struct spec_ipv6_t { + pj_uint8_t addr[16]; + pj_uint8_t mask[16]; + int weight; + } spec_ipv6[] = {/* Loopback address, ::1/128 */ + {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + WEIGHT_LOOPBACK}, + + /* Link local, fe80::/10 */ + {{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0xff, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + WEIGHT_LINK_LOCAL}, + + /* Disabled, ::/128 */ + {{0x0, 0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + WEIGHT_DISABLED}}; + pj_addrinfo ai; + pj_status_t status; + + /* May not be used if TRACE_ is disabled */ + PJ_UNUSED_ARG(strip); + +#ifdef _MSC_VER + /* Get rid of "uninitialized he variable" with MS compilers */ + pj_bzero(&ai, sizeof(ai)); +#endif + + cand_cnt = 0; + pj_bzero(cand_addr, sizeof(cand_addr)); + pj_bzero(cand_weight, sizeof(cand_weight)); + for (i = 0; i < PJ_ARRAY_SIZE(cand_addr); ++i) { + cand_addr[i].addr.sa_family = (pj_uint16_t)af; + PJ_SOCKADDR_RESET_LEN(&cand_addr[i]); + } + + addr->addr.sa_family = (pj_uint16_t)af; + PJ_SOCKADDR_RESET_LEN(addr); + +#if !defined(PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION) || PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION == 0 + /* Get hostname's IP address */ + { + const pj_str_t *hostname = pj_gethostname(); + count = 1; + + if (hostname->slen > 0) + status = pj_getaddrinfo(af, hostname, &count, &ai); + else + status = PJ_ERESOLVE; + + if (status == PJ_SUCCESS) { + pj_assert(ai.ai_addr.addr.sa_family == (pj_uint16_t)af); + pj_sockaddr_copy_addr(&cand_addr[cand_cnt], &ai.ai_addr); + pj_sockaddr_set_port(&cand_addr[cand_cnt], 0); + cand_weight[cand_cnt] += WEIGHT_HOSTNAME; + ++cand_cnt; + + TRACE_((THIS_FILE, "hostname IP is %s", pj_sockaddr_print(&ai.ai_addr, strip, sizeof(strip), 3))); + } + } +#else + PJ_UNUSED_ARG(ai); +#endif + + /* Get default interface (interface for default route) */ + if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { + status = pj_getdefaultipinterface(af, addr); + if (status == PJ_SUCCESS) { + TRACE_((THIS_FILE, "default IP is %s", pj_sockaddr_print(addr, strip, sizeof(strip), 3))); + + pj_sockaddr_set_port(addr, 0); + for (i = 0; i < cand_cnt; ++i) { + if (pj_sockaddr_cmp(&cand_addr[i], addr) == 0) + break; + } + + cand_weight[i] += WEIGHT_DEF_ROUTE; + if (i >= cand_cnt) { + pj_sockaddr_copy_addr(&cand_addr[i], addr); + ++cand_cnt; + } + } + } + + /* Enumerate IP interfaces */ + if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { + unsigned start_if = cand_cnt; + count = PJ_ARRAY_SIZE(cand_addr) - start_if; + + status = pj_enum_ip_interface(af, &count, &cand_addr[start_if]); + if (status == PJ_SUCCESS && count) { + /* Clear the port number */ + for (i = 0; i < count; ++i) + pj_sockaddr_set_port(&cand_addr[start_if + i], 0); + + /* For each candidate that we found so far (that is the hostname + * address and default interface address, check if they're found + * in the interface list. If found, add the weight, and if not, + * decrease the weight. + */ + for (i = 0; i < cand_cnt; ++i) { + unsigned j; + for (j = 0; j < count; ++j) { + if (pj_sockaddr_cmp(&cand_addr[i], &cand_addr[start_if + j]) == 0) + break; + } + + if (j == count) { + /* Not found */ + cand_weight[i] -= WEIGHT_INTERFACE; + } else { + cand_weight[i] += WEIGHT_INTERFACE; + } + } + + /* Add remaining interface to candidate list. */ + for (i = 0; i < count; ++i) { + unsigned j; + for (j = 0; j < cand_cnt; ++j) { + if (pj_sockaddr_cmp(&cand_addr[start_if + i], &cand_addr[j]) == 0) + break; + } + + if (j == cand_cnt) { + if (cand_cnt != (start_if + i)) { + pj_sockaddr_copy_addr(&cand_addr[cand_cnt], &cand_addr[start_if + i]); + } + cand_weight[cand_cnt] += WEIGHT_INTERFACE; + ++cand_cnt; + } + } + } + } + + /* Apply weight adjustment for special IPv4/IPv6 addresses + * See https://github.com/pjsip/pjproject/issues/1046 + */ + if (af == PJ_AF_INET) { + for (i = 0; i < cand_cnt; ++i) { + unsigned j; + for (j = 0; j < PJ_ARRAY_SIZE(spec_ipv4); ++j) { + pj_uint32_t a = pj_ntohl(cand_addr[i].ipv4.sin_addr.s_addr); + pj_uint32_t pa = spec_ipv4[j].addr; + pj_uint32_t pm = spec_ipv4[j].mask; + + if ((a & pm) == pa) { + cand_weight[i] += spec_ipv4[j].weight; + break; + } + } + } + } else if (af == PJ_AF_INET6) { + for (i = 0; i < PJ_ARRAY_SIZE(spec_ipv6); ++i) { + unsigned j; + for (j = 0; j < cand_cnt; ++j) { + pj_uint8_t *a = cand_addr[j].ipv6.sin6_addr.s6_addr; + pj_uint8_t am[16]; + pj_uint8_t *pa = spec_ipv6[i].addr; + pj_uint8_t *pm = spec_ipv6[i].mask; + unsigned k; + + for (k = 0; k < 16; ++k) { + am[k] = (pj_uint8_t)((a[k] & pm[k]) & 0xFF); + } + + if (pj_memcmp(am, pa, 16) == 0) { + cand_weight[j] += spec_ipv6[i].weight; + } + } + } + } else { + return PJ_EAFNOTSUP; + } + + /* Enumerate candidates to get the best IP address to choose */ + selected_cand = -1; + for (i = 0; i < cand_cnt; ++i) { + TRACE_((THIS_FILE, "Checking candidate IP %s, weight=%d", + pj_sockaddr_print(&cand_addr[i], strip, sizeof(strip), 3), cand_weight[i])); + + if (cand_weight[i] < MIN_WEIGHT) { + continue; + } + + if (selected_cand == -1) + selected_cand = i; + else if (cand_weight[i] > cand_weight[selected_cand]) + selected_cand = i; + } + + /* If else fails, returns loopback interface as the last resort */ + if (selected_cand == -1) { + if (af == PJ_AF_INET) { + addr->ipv4.sin_addr.s_addr = pj_htonl(0x7f000001); + } else { + pj_in6_addr *s6_addr_; + + s6_addr_ = (pj_in6_addr *)pj_sockaddr_get_addr(addr); + pj_bzero(s6_addr_, sizeof(pj_in6_addr)); + s6_addr_->s6_addr[15] = 1; + } + TRACE_((THIS_FILE, "Loopback IP %s returned", pj_sockaddr_print(addr, strip, sizeof(strip), 3))); + } else { + pj_sockaddr_copy_addr(addr, &cand_addr[selected_cand]); + TRACE_((THIS_FILE, "Candidate %s selected", pj_sockaddr_print(addr, strip, sizeof(strip), 3))); + } + + return PJ_SUCCESS; +} + +/* Get IP interface for sending to the specified destination */ +PJ_DEF(pj_status_t) +pj_getipinterface(int af, const pj_str_t *dst, pj_sockaddr *itf_addr, pj_bool_t allow_resolve, pj_sockaddr *p_dst_addr) +{ + pj_sockaddr dst_addr; + pj_sock_t fd; + int len; + pj_uint8_t zero[64]; + pj_status_t status; + + pj_sockaddr_init(af, &dst_addr, NULL, 53); + status = pj_inet_pton(af, dst, pj_sockaddr_get_addr(&dst_addr)); + if (status != PJ_SUCCESS) { + /* "dst" is not an IP address. */ + if (allow_resolve) { + status = pj_sockaddr_init(af, &dst_addr, dst, 53); + } else { + pj_str_t cp; + + if (af == PJ_AF_INET) { + cp = pj_str("1.1.1.1"); + } else { + cp = pj_str("1::1"); + } + status = pj_sockaddr_init(af, &dst_addr, &cp, 53); + } + + if (status != PJ_SUCCESS) + return status; + } + + /* Create UDP socket and connect() to the destination IP */ + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &fd); + if (status != PJ_SUCCESS) { + return status; + } + + status = pj_sock_connect(fd, &dst_addr, pj_sockaddr_get_len(&dst_addr)); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + len = sizeof(*itf_addr); + status = pj_sock_getsockname(fd, itf_addr, &len); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + pj_sock_close(fd); + + /* Check that the address returned is not zero */ + pj_bzero(zero, sizeof(zero)); + if (pj_memcmp(pj_sockaddr_get_addr(itf_addr), zero, pj_sockaddr_get_addr_len(itf_addr)) == 0) { + return PJ_ENOTFOUND; + } + + if (p_dst_addr) + *p_dst_addr = dst_addr; + + return PJ_SUCCESS; +} + +/* Get the default IP interface */ +PJ_DEF(pj_status_t) pj_getdefaultipinterface(int af, pj_sockaddr *addr) +{ + pj_str_t cp; + + if (af == PJ_AF_INET) { + cp = pj_str("1.1.1.1"); + } else { + cp = pj_str("1::1"); + } + + return pj_getipinterface(af, &cp, addr, PJ_FALSE, NULL); +} + +/* + * Bind socket at random port. + */ +PJ_DEF(pj_status_t) +pj_sock_bind_random(pj_sock_t sockfd, const pj_sockaddr_t *addr, pj_uint16_t port_range, pj_uint16_t max_try) +{ + pj_sockaddr bind_addr; + int addr_len; + pj_uint16_t base_port; + pj_status_t status = PJ_SUCCESS; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(addr, PJ_EINVAL); + + pj_sockaddr_cp(&bind_addr, addr); + addr_len = pj_sockaddr_get_len(addr); + base_port = pj_sockaddr_get_port(addr); + + if (base_port == 0 || port_range == 0) { + return pj_sock_bind(sockfd, &bind_addr, addr_len); + } + + for (; max_try; --max_try) { + pj_uint16_t port; + port = (pj_uint16_t)(base_port + pj_rand() % (port_range + 1)); + pj_sockaddr_set_port(&bind_addr, port); + status = pj_sock_bind(sockfd, &bind_addr, addr_len); + if (status == PJ_SUCCESS) + break; + } + + return status; +} + +/* + * Adjust socket send/receive buffer size. + */ +PJ_DEF(pj_status_t) +pj_sock_setsockopt_sobuf(pj_sock_t sockfd, pj_uint16_t optname, pj_bool_t auto_retry, unsigned *buf_size) +{ + pj_status_t status; + int try_size, cur_size, i, step, size_len; + enum { MAX_TRY = 20 }; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(sockfd != PJ_INVALID_SOCKET && buf_size && *buf_size > 0 && + (optname == pj_SO_RCVBUF() || optname == pj_SO_SNDBUF()), + PJ_EINVAL); + + size_len = sizeof(cur_size); + status = pj_sock_getsockopt(sockfd, pj_SOL_SOCKET(), optname, &cur_size, &size_len); + if (status != PJ_SUCCESS) + return status; + + try_size = *buf_size; + step = (try_size - cur_size) / MAX_TRY; + if (step < 4096) + step = 4096; + + for (i = 0; i < (MAX_TRY - 1); ++i) { + if (try_size <= cur_size) { + /* Done, return current size */ + *buf_size = cur_size; + break; + } + + status = pj_sock_setsockopt(sockfd, pj_SOL_SOCKET(), optname, &try_size, sizeof(try_size)); + if (status == PJ_SUCCESS) { + status = pj_sock_getsockopt(sockfd, pj_SOL_SOCKET(), optname, &cur_size, &size_len); + if (status != PJ_SUCCESS) { + /* Ops! No info about current size, just return last try size + * and quit. + */ + *buf_size = try_size; + break; + } + } + + if (!auto_retry) + break; + + try_size -= step; + } + + return status; +} + +PJ_DEF(char *) pj_addr_str_print(const pj_str_t *host_str, int port, char *buf, int size, unsigned flag) +{ + enum { WITH_PORT = 1 }; + char *bquote, *equote; + int af = pj_AF_UNSPEC(); + pj_in6_addr dummy6; + + /* Check if this is an IPv6 address */ + if (pj_inet_pton(pj_AF_INET6(), host_str, &dummy6) == PJ_SUCCESS) + af = pj_AF_INET6(); + + if (af == pj_AF_INET6()) { + bquote = "["; + equote = "]"; + } else { + bquote = ""; + equote = ""; + } + + if (flag & WITH_PORT) { + pj_ansi_snprintf(buf, size, "%s%.*s%s:%d", bquote, (int)host_str->slen, host_str->ptr, equote, port); + } else { + pj_ansi_snprintf(buf, size, "%s%.*s%s", bquote, (int)host_str->slen, host_str->ptr, equote); + } + return buf; +} + +/* + * Simulate unix-like socketpair() + * Use a pair of IPv4/IPv6 local sockets (listening on 127.0.0.1/::1) + */ +static pj_status_t socketpair_imp(int family, int type, int protocol, pj_sock_t sv[2]) +{ + pj_status_t status; + pj_sock_t lfd = PJ_INVALID_SOCKET; + pj_sock_t cfd = PJ_INVALID_SOCKET; + pj_str_t loopback; + pj_sockaddr sa; + int salen; + +#if PJ_HAS_TCP + PJ_ASSERT_RETURN(type == pj_SOCK_DGRAM() || type == pj_SOCK_STREAM(), PJ_EINVAL); +#else + PJ_ASSERT_RETURN(type == pj_SOCK_DGRAM(), PJ_EINVAL); +#endif + + PJ_ASSERT_RETURN(family == pj_AF_INET() || family == pj_AF_INET6(), PJ_EINVAL); + + loopback = family == pj_AF_INET() ? pj_str("127.0.0.1") : pj_str("::1"); + + /* listen */ + status = pj_sock_socket(family, type, protocol, &lfd); + if (status != PJ_SUCCESS) + goto on_error; + + pj_sockaddr_init(family, &sa, &loopback, 0); + salen = pj_sockaddr_get_len(&sa); + status = pj_sock_bind(lfd, &sa, salen); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_sock_getsockname(lfd, &sa, &salen); + if (status != PJ_SUCCESS) + goto on_error; + +#if PJ_HAS_TCP + if (type == pj_SOCK_STREAM()) { + status = pj_sock_listen(lfd, 1); + if (status != PJ_SUCCESS) + goto on_error; + } +#endif + + /* connect to listen fd */ + status = pj_sock_socket(family, type, protocol, &cfd); + if (status != PJ_SUCCESS) + goto on_error; + status = pj_sock_connect(cfd, &sa, salen); + if (status != PJ_SUCCESS) + goto on_error; + + if (type == pj_SOCK_DGRAM()) { + status = pj_sock_getsockname(cfd, &sa, &salen); + if (status != PJ_SUCCESS) + goto on_error; + status = pj_sock_connect(lfd, &sa, salen); + if (status != PJ_SUCCESS) + goto on_error; + sv[0] = lfd; + sv[1] = cfd; + } +#if PJ_HAS_TCP + else if (type == pj_SOCK_STREAM()) { + pj_sock_t newfd = PJ_INVALID_SOCKET; + status = pj_sock_accept(lfd, &newfd, NULL, NULL); + if (status != PJ_SUCCESS) + goto on_error; + pj_sock_close(lfd); + sv[0] = newfd; + sv[1] = cfd; + } +#endif + + return PJ_SUCCESS; + +on_error: + if (lfd != PJ_INVALID_SOCKET) + pj_sock_close(lfd); + if (cfd != PJ_INVALID_SOCKET) + pj_sock_close(cfd); + return status; +} + +#if defined(PJ_SOCK_HAS_SOCKETPAIR) && PJ_SOCK_HAS_SOCKETPAIR != 0 +PJ_DEF(pj_status_t) pj_sock_socketpair(int family, int type, int protocol, pj_sock_t sv[2]) +{ + int status; + int tmp_sv[2]; + + PJ_CHECK_STACK(); + + status = socketpair(family, type, protocol, tmp_sv); + if (status != PJ_SUCCESS) { + if (errno == EOPNOTSUPP) { + return socketpair_imp(family, type, protocol, sv); + } + status = PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + return status; + } + sv[0] = tmp_sv[0]; + sv[1] = tmp_sv[1]; + return PJ_SUCCESS; +} +#else +PJ_DEF(pj_status_t) pj_sock_socketpair(int family, int type, int protocol, pj_sock_t sv[2]) +{ + PJ_CHECK_STACK(); + return socketpair_imp(family, type, protocol, sv); +} +#endif + +/* Only need to implement these in DLL build */ +#if defined(PJ_DLL) + +PJ_DEF(pj_uint16_t) pj_AF_UNSPEC(void) +{ + return PJ_AF_UNSPEC; +} + +PJ_DEF(pj_uint16_t) pj_AF_UNIX(void) +{ + return PJ_AF_UNIX; +} + +PJ_DEF(pj_uint16_t) pj_AF_INET(void) +{ + return PJ_AF_INET; +} + +PJ_DEF(pj_uint16_t) pj_AF_INET6(void) +{ + return PJ_AF_INET6; +} + +PJ_DEF(pj_uint16_t) pj_AF_PACKET(void) +{ + return PJ_AF_PACKET; +} + +PJ_DEF(pj_uint16_t) pj_AF_IRDA(void) +{ + return PJ_AF_IRDA; +} + +PJ_DEF(int) pj_SOCK_STREAM(void) +{ + return PJ_SOCK_STREAM; +} + +PJ_DEF(int) pj_SOCK_DGRAM(void) +{ + return PJ_SOCK_DGRAM; +} + +PJ_DEF(int) pj_SOCK_RAW(void) +{ + return PJ_SOCK_RAW; +} + +PJ_DEF(int) pj_SOCK_RDM(void) +{ + return PJ_SOCK_RDM; +} + +PJ_DEF(pj_uint16_t) pj_SOL_SOCKET(void) +{ + return PJ_SOL_SOCKET; +} + +PJ_DEF(pj_uint16_t) pj_SOL_IP(void) +{ + return PJ_SOL_IP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_TCP(void) +{ + return PJ_SOL_TCP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_UDP(void) +{ + return PJ_SOL_UDP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_IPV6(void) +{ + return PJ_SOL_IPV6; +} + +PJ_DEF(int) pj_IP_TOS(void) +{ + return PJ_IP_TOS; +} + +PJ_DEF(int) pj_IPTOS_LOWDELAY(void) +{ + return PJ_IPTOS_LOWDELAY; +} + +PJ_DEF(int) pj_IPTOS_THROUGHPUT(void) +{ + return PJ_IPTOS_THROUGHPUT; +} + +PJ_DEF(int) pj_IPTOS_RELIABILITY(void) +{ + return PJ_IPTOS_RELIABILITY; +} + +PJ_DEF(int) pj_IPTOS_MINCOST(void) +{ + return PJ_IPTOS_MINCOST; +} + +PJ_DEF(int) pj_IPV6_TCLASS(void) +{ + return PJ_IPV6_TCLASS; +} + +PJ_DEF(pj_uint16_t) pj_SO_TYPE(void) +{ + return PJ_SO_TYPE; +} + +PJ_DEF(pj_uint16_t) pj_SO_RCVBUF(void) +{ + return PJ_SO_RCVBUF; +} + +PJ_DEF(pj_uint16_t) pj_SO_SNDBUF(void) +{ + return PJ_SO_SNDBUF; +} + +PJ_DEF(pj_uint16_t) pj_TCP_NODELAY(void) +{ + return PJ_TCP_NODELAY; +} + +PJ_DEF(pj_uint16_t) pj_SO_REUSEADDR(void) +{ + return PJ_SO_REUSEADDR; +} + +PJ_DEF(pj_uint16_t) pj_SO_NOSIGPIPE(void) +{ + return PJ_SO_NOSIGPIPE; +} + +PJ_DEF(pj_uint16_t) pj_SO_PRIORITY(void) +{ + return PJ_SO_PRIORITY; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_IF(void) +{ + return PJ_IP_MULTICAST_IF; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_TTL(void) +{ + return PJ_IP_MULTICAST_TTL; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_LOOP(void) +{ + return PJ_IP_MULTICAST_LOOP; +} + +PJ_DEF(pj_uint16_t) pj_IP_ADD_MEMBERSHIP(void) +{ + return PJ_IP_ADD_MEMBERSHIP; +} + +PJ_DEF(pj_uint16_t) pj_IP_DROP_MEMBERSHIP(void) +{ + return PJ_IP_DROP_MEMBERSHIP; +} + +PJ_DEF(int) pj_MSG_OOB(void) +{ + return PJ_MSG_OOB; +} + +PJ_DEF(int) pj_MSG_PEEK(void) +{ + return PJ_MSG_PEEK; +} + +PJ_DEF(int) pj_MSG_DONTROUTE(void) +{ + return PJ_MSG_DONTROUTE; +} + +#endif /* PJ_DLL */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c new file mode 100755 index 000000000..36458cf5c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* This is the implementation of QoS with BSD socket's setsockopt(), + * using IP_TOS/IPV6_TCLASS and SO_PRIORITY + */ +#if !defined(PJ_QOS_IMPLEMENTATION) || PJ_QOS_IMPLEMENTATION == PJ_QOS_BSD + +PJ_DEF(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, pj_qos_params *param) +{ + pj_status_t last_err = PJ_ENOTSUP; + pj_status_t status; + + /* No op? */ + if (!param->flags) + return PJ_SUCCESS; + + /* Clear WMM field since we don't support it */ + param->flags &= ~(PJ_QOS_PARAM_HAS_WMM); + + /* Set TOS/DSCP */ + if (param->flags & PJ_QOS_PARAM_HAS_DSCP) { + /* We need to know if the socket is IPv4 or IPv6 */ + pj_sockaddr sa; + int salen = sizeof(salen); + + /* Value is dscp_val << 2 */ + int val = (param->dscp_val << 2); + + status = pj_sock_getsockname(sock, &sa, &salen); + if (status != PJ_SUCCESS) + return status; + + if (sa.addr.sa_family == pj_AF_INET()) { + /* In IPv4, the DS field goes in the TOS field */ + status = pj_sock_setsockopt(sock, pj_SOL_IP(), pj_IP_TOS(), &val, sizeof(val)); + } else if (sa.addr.sa_family == pj_AF_INET6()) { + /* In IPv6, the DS field goes in the Traffic Class field */ + status = pj_sock_setsockopt(sock, pj_SOL_IPV6(), pj_IPV6_TCLASS(), &val, sizeof(val)); + } else { + status = PJ_EINVAL; + } + + if (status != PJ_SUCCESS) { + param->flags &= ~(PJ_QOS_PARAM_HAS_DSCP); + last_err = status; + } + } + + /* Set SO_PRIORITY */ + if (param->flags & PJ_QOS_PARAM_HAS_SO_PRIO) { + int val = param->so_prio; + status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_PRIORITY(), &val, sizeof(val)); + if (status != PJ_SUCCESS) { + param->flags &= ~(PJ_QOS_PARAM_HAS_SO_PRIO); + last_err = status; + } + } + + return param->flags ? PJ_SUCCESS : last_err; +} + +PJ_DEF(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, pj_qos_type type) +{ + pj_qos_params param; + pj_status_t status; + + status = pj_qos_get_params(type, ¶m); + if (status != PJ_SUCCESS) + return status; + + return pj_sock_set_qos_params(sock, ¶m); +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, pj_qos_params *p_param) +{ + pj_status_t last_err = PJ_ENOTSUP; + int val = 0, optlen; + pj_sockaddr sa; + int salen = sizeof(salen); + pj_status_t status; + + pj_bzero(p_param, sizeof(*p_param)); + + /* Get DSCP/TOS value */ + status = pj_sock_getsockname(sock, &sa, &salen); + if (status == PJ_SUCCESS) { + optlen = sizeof(val); + if (sa.addr.sa_family == pj_AF_INET()) { + status = pj_sock_getsockopt(sock, pj_SOL_IP(), pj_IP_TOS(), &val, &optlen); + } else if (sa.addr.sa_family == pj_AF_INET6()) { + status = pj_sock_getsockopt(sock, pj_SOL_IPV6(), pj_IPV6_TCLASS(), &val, &optlen); + } else { + status = PJ_EINVAL; + } + + if (status == PJ_SUCCESS) { + p_param->flags |= PJ_QOS_PARAM_HAS_DSCP; + p_param->dscp_val = (pj_uint8_t)(val >> 2); + } else { + last_err = status; + } + } else { + last_err = status; + } + + /* Get SO_PRIORITY */ + optlen = sizeof(val); + status = pj_sock_getsockopt(sock, pj_SOL_SOCKET(), pj_SO_PRIORITY(), &val, &optlen); + if (status == PJ_SUCCESS) { + p_param->flags |= PJ_QOS_PARAM_HAS_SO_PRIO; + p_param->so_prio = (pj_uint8_t)val; + } else { + last_err = status; + } + + /* WMM is not supported */ + + return p_param->flags ? PJ_SUCCESS : last_err; +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, pj_qos_type *p_type) +{ + pj_qos_params param; + pj_status_t status; + + status = pj_sock_get_qos_params(sock, ¶m); + if (status != PJ_SUCCESS) + return status; + + return pj_qos_get_type(¶m, p_type); +} + +#endif /* PJ_QOS_IMPLEMENTATION */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c new file mode 100755 index 000000000..aec3724e4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#define THIS_FILE "sock_qos_common.c" +#define ALL_FLAGS (PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_SO_PRIO | PJ_QOS_PARAM_HAS_WMM) + +/* "Standard" mapping between traffic type and QoS params */ +static const pj_qos_params qos_map[] = { + /* flags dscp prio wmm_prio */ + {ALL_FLAGS, 0x00, 0, PJ_QOS_WMM_PRIO_BULK_EFFORT}, /* BE */ + {ALL_FLAGS, 0x08, 2, PJ_QOS_WMM_PRIO_BULK}, /* BK */ + {ALL_FLAGS, 0x28, 5, PJ_QOS_WMM_PRIO_VIDEO}, /* VI */ + {ALL_FLAGS, 0x30, 6, PJ_QOS_WMM_PRIO_VOICE}, /* VO */ + {ALL_FLAGS, 0x38, 7, PJ_QOS_WMM_PRIO_VOICE}, /* CO */ + {ALL_FLAGS, 0x28, 5, PJ_QOS_WMM_PRIO_VIDEO} /* SIG */ +}; + +/* Retrieve the mapping for the specified type */ +PJ_DEF(pj_status_t) pj_qos_get_params(pj_qos_type type, pj_qos_params *p_param) +{ + PJ_ASSERT_RETURN(type <= PJ_QOS_TYPE_SIGNALLING && p_param, PJ_EINVAL); + pj_memcpy(p_param, &qos_map[type], sizeof(*p_param)); + return PJ_SUCCESS; +} + +/* Get the matching traffic type */ +PJ_DEF(pj_status_t) pj_qos_get_type(const pj_qos_params *param, pj_qos_type *p_type) +{ + unsigned dscp_type = PJ_QOS_TYPE_BEST_EFFORT, prio_type = PJ_QOS_TYPE_BEST_EFFORT, + wmm_type = PJ_QOS_TYPE_BEST_EFFORT; + unsigned i, count = 0; + + PJ_ASSERT_RETURN(param && p_type, PJ_EINVAL); + + if (param->flags & PJ_QOS_PARAM_HAS_DSCP) { + for (i = 0; i <= PJ_QOS_TYPE_CONTROL; ++i) { + if (param->dscp_val >= qos_map[i].dscp_val) + dscp_type = (pj_qos_type)i; + } + ++count; + } + + if (param->flags & PJ_QOS_PARAM_HAS_SO_PRIO) { + for (i = 0; i <= PJ_QOS_TYPE_CONTROL; ++i) { + if (param->so_prio >= qos_map[i].so_prio) + prio_type = (pj_qos_type)i; + } + ++count; + } + + if (param->flags & PJ_QOS_PARAM_HAS_WMM) { + for (i = 0; i <= PJ_QOS_TYPE_CONTROL; ++i) { + if (param->wmm_prio >= qos_map[i].wmm_prio) + wmm_type = (pj_qos_type)i; + } + ++count; + } + + if (count) + *p_type = (pj_qos_type)((dscp_type + prio_type + wmm_type) / count); + else + *p_type = PJ_QOS_TYPE_BEST_EFFORT; + + return PJ_SUCCESS; +} + +/* Apply QoS */ +PJ_DEF(pj_status_t) +pj_sock_apply_qos(pj_sock_t sock, pj_qos_type qos_type, pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name) +{ + pj_status_t qos_type_rc = PJ_SUCCESS, qos_params_rc = PJ_SUCCESS; + + if (!log_sender) + log_sender = THIS_FILE; + if (!sock_name) + sock_name = "socket"; + + if (qos_type != PJ_QOS_TYPE_BEST_EFFORT) { + qos_type_rc = pj_sock_set_qos_type(sock, qos_type); + + if (qos_type_rc != PJ_SUCCESS) { + pj_perror(log_level, log_sender, qos_type_rc, "Error setting QoS type %d to %s", qos_type, sock_name); + } + } + + if (qos_params && qos_params->flags) { + qos_params_rc = pj_sock_set_qos_params(sock, qos_params); + if (qos_params_rc != PJ_SUCCESS) { + pj_perror(log_level, log_sender, qos_params_rc, "Error setting QoS params (flags=%d) to %s", + qos_params->flags, sock_name); + if (qos_type_rc != PJ_SUCCESS) + return qos_params_rc; + } + } else if (qos_type_rc != PJ_SUCCESS) + return qos_type_rc; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_sock_apply_qos2(pj_sock_t sock, pj_qos_type qos_type, const pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name) +{ + pj_qos_params qos_params_buf, *qos_params_copy = NULL; + + if (qos_params) { + pj_memcpy(&qos_params_buf, qos_params, sizeof(*qos_params)); + qos_params_copy = &qos_params_buf; + } + + return pj_sock_apply_qos(sock, qos_type, qos_params_copy, log_level, log_sender, sock_name); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c new file mode 100755 index 000000000..3bfeeb583 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +/* Dummy implementation of QoS API. + * (this is controlled by pjlib's config.h) + */ +#if defined(PJ_QOS_IMPLEMENTATION) && PJ_QOS_IMPLEMENTATION == PJ_QOS_DUMMY + +#define THIS_FILE "sock_qos_dummy.c" + +PJ_DEF(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, pj_qos_params *param) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(param); + + PJ_LOG(4, (THIS_FILE, "pj_sock_set_qos_params() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +PJ_DEF(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, pj_qos_type type) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(type); + + PJ_LOG(4, (THIS_FILE, "pj_sock_set_qos_type() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, pj_qos_params *p_param) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(p_param); + + PJ_LOG(4, (THIS_FILE, "pj_sock_get_qos_params() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, pj_qos_type *p_type) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(p_type); + + PJ_LOG(4, (THIS_FILE, "pj_sock_get_qos_type() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +#endif /* PJ_QOS_DUMMY */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c new file mode 100755 index 000000000..caff07dfd --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if defined(PJ_HAS_STRING_H) && PJ_HAS_STRING_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_TIME_H) && PJ_HAS_SYS_TIME_H != 0 +#include +#endif + +#ifdef _MSC_VER +#pragma warning(disable : 4018) // Signed/unsigned mismatch in FD_* +#pragma warning(disable : 4389) // Signed/unsigned mismatch in FD_* +#endif + +#define PART_FDSET(ps) ((fd_set *)&ps->data[1]) +#define PART_FDSET_OR_NULL(ps) (ps ? PART_FDSET(ps) : NULL) +#define PART_COUNT(ps) (ps->data[0]) + +PJ_DEF(void) PJ_FD_ZERO(pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + pj_assert(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set)); + + FD_ZERO(PART_FDSET(fdsetp)); + PART_COUNT(fdsetp) = 0; +} + +PJ_DEF(void) PJ_FD_SET(pj_sock_t fd, pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + pj_assert(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set)); + + if (!PJ_FD_ISSET(fd, fdsetp)) + ++PART_COUNT(fdsetp); + FD_SET(fd, PART_FDSET(fdsetp)); +} + +PJ_DEF(void) PJ_FD_CLR(pj_sock_t fd, pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + pj_assert(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set)); + + if (PJ_FD_ISSET(fd, fdsetp)) + --PART_COUNT(fdsetp); + FD_CLR(fd, PART_FDSET(fdsetp)); +} + +PJ_DEF(pj_bool_t) PJ_FD_ISSET(pj_sock_t fd, const pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set), 0); + + return FD_ISSET(fd, PART_FDSET(fdsetp)); +} + +PJ_DEF(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp) +{ + return PART_COUNT(fdsetp); +} + +PJ_DEF(int) +pj_sock_select(int n, pj_fd_set_t *readfds, pj_fd_set_t *writefds, pj_fd_set_t *exceptfds, const pj_time_val *timeout) +{ + struct timeval os_timeout, *p_os_timeout; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set), PJ_EBUG); + + if (timeout) { + os_timeout.tv_sec = timeout->sec; + os_timeout.tv_usec = timeout->msec * 1000; + p_os_timeout = &os_timeout; + } else { + p_os_timeout = NULL; + } + + return select(n, PART_FDSET_OR_NULL(readfds), PART_FDSET_OR_NULL(writefds), PART_FDSET_OR_NULL(exceptfds), + p_os_timeout); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m new file mode 100755 index 000000000..c5251681d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m @@ -0,0 +1,2211 @@ +/* + * Copyright (C) 2019-2020 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK and the implementation is Apple SSL. */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ + (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) + +#define THIS_FILE "ssl_sock_apple.m" + +/* Set to 1 to enable debugging messages. */ +#define SSL_DEBUG 0 + +#define SSL_SOCK_IMP_USE_CIRC_BUF +#define SSL_SOCK_IMP_USE_OWN_NETWORK + +#include "ssl_sock_imp_common.h" +#include "ssl_sock_imp_common.c" + +#include "TargetConditionals.h" + +#include +#include +#include +#include +#include +#include + +/* IMPORTANT note from Apple's Concurrency Programming Guide doc: + * "Because Grand Central Dispatch manages the relationship between the tasks + * you provide and the threads on which those tasks run, you should generally + * avoid calling POSIX thread routines from your task code" + * + * Since network events happen in a dispatch function block, we need to make + * sure not to call any PJLIB functions there (not even pj_pool_alloc() nor + * pj_log()). Instead, we will post those events to a singleton event manager + * to be polled by ioqueue polling thread(s). + */ + +/* Secure socket structure definition. */ +typedef struct applessl_sock_t { + pj_ssl_sock_t base; + + nw_listener_t listener; + nw_listener_state_t lis_state; + nw_connection_t connection; + nw_connection_state_t con_state; + dispatch_queue_t queue; + dispatch_semaphore_t ev_semaphore; + + SecTrustRef trust; + tls_ciphersuite_t cipher; + sec_identity_t identity; +} applessl_sock_t; + + +/* + ******************************************************************* + * Event manager + ******************************************************************* + */ + + typedef enum event_id +{ + EVENT_ACCEPT, + EVENT_CONNECT, + EVENT_VERIFY_CERT, + EVENT_HANDSHAKE_COMPLETE, + EVENT_DATA_READ, + EVENT_DATA_SENT, + EVENT_DISCARD +} event_id; + +typedef struct event_t +{ + PJ_DECL_LIST_MEMBER(struct event_t); + + event_id type; + pj_ssl_sock_t *ssock; + pj_bool_t async; + + union + { + struct + { + nw_connection_t newconn; + pj_sockaddr src_addr; + int src_addr_len; + pj_status_t status; + } accept_ev; + + struct + { + pj_status_t status; + } connect_ev; + + struct + { + pj_status_t status; + } handshake_ev; + + struct + { + pj_ioqueue_op_key_t *send_key; + pj_ssize_t sent; + } data_sent_ev; + + struct + { + void *data; + pj_size_t size; + pj_status_t status; + pj_size_t remainder; + } data_read_ev; + + } body; +} event_t; + +typedef struct event_manager +{ + NSLock *lock; + event_t event_list; + event_t free_event_list; +} event_manager; + +static event_manager *event_mgr = NULL; + +#if SSL_DEBUG +static pj_thread_desc queue_th_desc; +static pj_thread_t *queue_th; +#endif + +/* + ******************************************************************* + * Event manager's functions + ******************************************************************* + */ + +static pj_status_t verify_cert(applessl_sock_t *assock, pj_ssl_cert_t *cert); + +static void event_manager_destroy() +{ + event_manager *mgr = event_mgr; + + event_mgr = NULL; + + while (!pj_list_empty(&mgr->free_event_list)) { + event_t *event = mgr->free_event_list.next; + pj_list_erase(event); + free(event); + } + + while (!pj_list_empty(&mgr->event_list)) { + event_t *event = mgr->event_list.next; + pj_list_erase(event); + free(event); + } + + [mgr->lock release]; + + free(mgr); +} + +static pj_status_t event_manager_create() +{ + event_manager *mgr; + + if (event_mgr) + return PJ_SUCCESS; + + mgr = malloc(sizeof(event_manager)); + if (!mgr) return PJ_ENOMEM; + + mgr->lock = [[NSLock alloc]init]; + pj_list_init(&mgr->event_list); + pj_list_init(&mgr->free_event_list); + + event_mgr = mgr; + pj_atexit(&event_manager_destroy); + + return PJ_SUCCESS; +} + +/* Post event to the event manager. If the event is posted + * synchronously, the function will wait until the event is processed. + */ +static pj_status_t event_manager_post_event(pj_ssl_sock_t *ssock, + event_t *event_item, + pj_bool_t async) +{ + event_manager *mgr = event_mgr; + event_t *event; + +#if SSL_DEBUG + if (!pj_thread_is_registered()) { + pj_bzero(queue_th_desc, sizeof(pj_thread_desc)); + pj_thread_register("sslq", queue_th_desc, &queue_th); + } + PJ_LOG(3, (THIS_FILE, "Posting event %p %d", ssock, event_item->type)); +#endif + + if (ssock->is_closing || !ssock->pool || !mgr) + return PJ_EGONE; + +#if SSL_DEBUG + PJ_LOG(3,(THIS_FILE, "Post event success %p %d",ssock, event_item->type)); +#endif + + [mgr->lock lock]; + + if (pj_list_empty(&mgr->free_event_list)) { + event = malloc(sizeof(event_t)); + } else { + event = mgr->free_event_list.next; + pj_list_erase(event); + } + + pj_memcpy(event, event_item, sizeof(event_t)); + event->ssock = ssock; + event->async = async; + pj_list_push_back(&mgr->event_list, event); + + [mgr->lock unlock]; + + if (!async) { + dispatch_semaphore_wait(((applessl_sock_t *)ssock)->ev_semaphore, + DISPATCH_TIME_FOREVER); + } + + return PJ_SUCCESS; +} + +/* Remove all events associated with the socket. */ +static void event_manager_remove_events(pj_ssl_sock_t *ssock) +{ + event_t *event; + + [event_mgr->lock lock]; + event = event_mgr->event_list.next; + while (event != &event_mgr->event_list) { + event_t *event_ = event; + + event = event->next; + if (event_->ssock == ssock) { + pj_list_erase(event_); + /* If not async, signal the waiting socket */ + if (!event_->async) { + applessl_sock_t * assock; + assock = (applessl_sock_t *)event_->ssock; + dispatch_semaphore_signal(assock->ev_semaphore); + } + } + } + [event_mgr->lock unlock]; +} + +pj_status_t ssl_network_event_poll() +{ + if (!event_mgr) + return PJ_SUCCESS; + + while (!pj_list_empty(&event_mgr->event_list)) { + pj_ssl_sock_t *ssock; + applessl_sock_t * assock; + event_t *event; + pj_bool_t ret = PJ_TRUE, add_ref = PJ_FALSE; + + [event_mgr->lock lock]; + /* Check again, this time by holding the lock */ + if (pj_list_empty(&event_mgr->event_list)) { + [event_mgr->lock unlock]; + break; + } + event = event_mgr->event_list.next; + ssock = event->ssock; + assock = (applessl_sock_t *)ssock; + pj_list_erase(event); + + if (ssock->is_closing || !ssock->pool || + (!ssock->is_server && !assock->connection) || + (ssock->is_server && !assock->listener)) + { + PJ_LOG(3, (THIS_FILE, "Warning: Discarding SSL event type %d of " + "a closing socket %p", event->type, ssock)); + event->type = EVENT_DISCARD; + } else if (ssock->param.grp_lock) { + if (pj_grp_lock_get_ref(ssock->param.grp_lock) > 0) { + /* Prevent ssock from being destroyed while + * we are calling the callback. + */ + add_ref = PJ_TRUE; + pj_grp_lock_add_ref(ssock->param.grp_lock); + } else { + PJ_LOG(3, (THIS_FILE, "Warning: Discarding SSL event type %d " + " of a destroyed socket %p", event->type, ssock)); + event->type = EVENT_DISCARD; + } + } + + [event_mgr->lock unlock]; + + switch (event->type) { + case EVENT_ACCEPT: + ret = ssock_on_accept_complete(event->ssock, + PJ_INVALID_SOCKET, + event->body.accept_ev.newconn, + &event->body.accept_ev.src_addr, + event->body.accept_ev.src_addr_len, + event->body.accept_ev.status); + break; + case EVENT_CONNECT: + ret = ssock_on_connect_complete(event->ssock, + event->body.connect_ev.status); + break; + case EVENT_VERIFY_CERT: + verify_cert(assock, event->ssock->cert); + break; + case EVENT_HANDSHAKE_COMPLETE: + event->ssock->ssl_state = SSL_STATE_ESTABLISHED; + ret = on_handshake_complete(event->ssock, + event->body.handshake_ev.status); + break; + case EVENT_DATA_SENT: + ret = ssock_on_data_sent(event->ssock, + event->body.data_sent_ev.send_key, + event->body.data_sent_ev.sent); + break; + case EVENT_DATA_READ: + ret = ssock_on_data_read(event->ssock, + event->body.data_read_ev.data, + event->body.data_read_ev.size, + event->body.data_read_ev.status, + &event->body.data_read_ev.remainder); + break; + default: + break; + } + + /* If not async and not destroyed, signal the waiting socket */ + if (event->type != EVENT_DISCARD && ret && !event->async && ret) { + dispatch_semaphore_signal(assock->ev_semaphore); + } + + /* Put the event into the free list to be reused */ + [event_mgr->lock lock]; + if (add_ref) { + pj_grp_lock_dec_ref(ssock->param.grp_lock); + } + pj_list_push_back(&event_mgr->free_event_list, event); + [event_mgr->lock unlock]; + } + + return 0; +} + +/* + ******************************************************************* + * Static/internal functions. + ******************************************************************* + */ + +#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \ + PJ_ERRNO_SPACE_SIZE*6) + +#define PJ_SSL_ERRNO_SPACE_SIZE PJ_ERRNO_SPACE_SIZE + +/* Convert from Apple SSL error to pj_status_t. */ +static pj_status_t pj_status_from_err(applessl_sock_t *assock, + const char *msg, + OSStatus err) +{ + pj_status_t status = (pj_status_t)-err; + CFStringRef errmsg; + + errmsg = SecCopyErrorMessageString(err, NULL); + PJ_LOG(3, (THIS_FILE, "Apple SSL error %s [%d]: %s", + (msg? msg: ""), err, + CFStringGetCStringPtr(errmsg, kCFStringEncodingUTF8))); + CFRelease(errmsg); + + if (status > PJ_SSL_ERRNO_SPACE_SIZE) + status = PJ_SSL_ERRNO_SPACE_SIZE; + status += PJ_SSL_ERRNO_START; + + if (assock) + assock->base.last_err = err; + + return status; +} + +/* Read cert or key file */ +static pj_status_t create_data_from_file(CFDataRef *data, + pj_str_t *fname, pj_str_t *path) +{ + CFURLRef file; + CFReadStreamRef read_stream; + UInt8 data_buf[8192]; + CFIndex nbytes = 0; + + if (path) { + CFURLRef filepath; + CFStringRef path_str; + + path_str = CFStringCreateWithBytes(NULL, (const UInt8 *)path->ptr, + path->slen, + kCFStringEncodingUTF8, false); + if (!path_str) return PJ_ENOMEM; + + filepath = CFURLCreateWithFileSystemPath(NULL, path_str, + kCFURLPOSIXPathStyle, true); + CFRelease(path_str); + if (!filepath) return PJ_ENOMEM; + + path_str = CFStringCreateWithBytes(NULL, (const UInt8 *)fname->ptr, + fname->slen, + kCFStringEncodingUTF8, false); + if (!path_str) { + CFRelease(filepath); + return PJ_ENOMEM; + } + + file = CFURLCreateCopyAppendingPathComponent(NULL, filepath, + path_str, false); + CFRelease(path_str); + CFRelease(filepath); + } else { + file = CFURLCreateFromFileSystemRepresentation(NULL, + (const UInt8 *)fname->ptr, fname->slen, false); + } + + if (!file) + return PJ_ENOMEM; + + read_stream = CFReadStreamCreateWithFile(NULL, file); + CFRelease(file); + + if (!read_stream) + return PJ_ENOTFOUND; + + if (!CFReadStreamOpen(read_stream)) { + PJ_LOG(2, (THIS_FILE, "Failed opening file")); + CFRelease(read_stream); + return PJ_EINVAL; + } + + nbytes = CFReadStreamRead(read_stream, data_buf, + sizeof(data_buf)); + if (nbytes > 0) + *data = CFDataCreate(NULL, data_buf, nbytes); + else + *data = NULL; + + CFReadStreamClose(read_stream); + CFRelease(read_stream); + + return (*data? PJ_SUCCESS: PJ_EINVAL); +} + +static pj_status_t create_identity_from_cert(applessl_sock_t *assock, + pj_ssl_cert_t *cert, + sec_identity_t *p_identity) +{ + CFStringRef password = NULL; + CFDataRef cert_data = NULL; + void *keys[1] = {NULL}; + void *values[1] = {NULL}; + CFDictionaryRef options; + CFArrayRef items; + CFIndex i, count; + SecIdentityRef identity = NULL; + OSStatus err; + pj_status_t status; + + /* Init */ + *p_identity = NULL; + + if (cert->privkey_file.slen || cert->privkey_buf.slen || + cert->privkey_pass.slen) + { + PJ_LOG(5, (THIS_FILE, "Ignoring supplied private key. Private key " + "must be placed in the keychain instead.")); + } + + if (cert->cert_file.slen) { + status = create_data_from_file(&cert_data, &cert->cert_file, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(2, (THIS_FILE, status, "Failed reading cert file")); + return status; + } + } else if (cert->cert_buf.slen) { + cert_data = CFDataCreate(NULL, (const UInt8 *)cert->cert_buf.ptr, + cert->cert_buf.slen); + if (!cert_data) + return PJ_ENOMEM; + } + + if (cert_data) { + if (cert->privkey_pass.slen) { + password = CFStringCreateWithBytes(NULL, + (const UInt8 *)cert->privkey_pass.ptr, + cert->privkey_pass.slen, + kCFStringEncodingUTF8, + false); + keys[0] = (void *)kSecImportExportPassphrase; + values[0] = (void *)password; + } + + options = CFDictionaryCreate(NULL, (const void **)keys, + (const void **)values, + (password? 1: 0), NULL, NULL); + if (!options) + return PJ_ENOMEM; + +#if TARGET_OS_IPHONE + err = SecPKCS12Import(cert_data, options, &items); +#else + { + SecExternalFormat ext_format[3] = {kSecFormatPKCS12, + kSecFormatPEMSequence, + kSecFormatX509Cert/* DER */}; + SecExternalItemType ext_type = kSecItemTypeCertificate; + SecItemImportExportKeyParameters key_params; + + pj_bzero(&key_params, sizeof(key_params)); + key_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + key_params.passphrase = password; + + for (i = 0; i < PJ_ARRAY_SIZE(ext_format); i++) { + items = NULL; + err = SecItemImport(cert_data, NULL, &ext_format[i], + &ext_type, 0, &key_params, NULL, &items); + if (err == noErr && items) { + break; + } + } + } +#endif + + CFRelease(options); + if (password) + CFRelease(password); + CFRelease(cert_data); + if (err != noErr || !items) { + return pj_status_from_err(assock, "SecItemImport", err); + } + + count = CFArrayGetCount(items); + + for (i = 0; i < count; i++) { + CFTypeRef item; + CFTypeID item_id; + + item = (CFTypeRef) CFArrayGetValueAtIndex(items, i); + item_id = CFGetTypeID(item); + + if (item_id == CFDictionaryGetTypeID()) { + identity = (SecIdentityRef) + CFDictionaryGetValue((CFDictionaryRef) item, + kSecImportItemIdentity); + break; + } +#if !TARGET_OS_IPHONE + else if (item_id == SecCertificateGetTypeID()) { + err = SecIdentityCreateWithCertificate(NULL, + (SecCertificateRef) item, &identity); + if (err != noErr) { + pj_status_from_err(assock, "SecIdentityCreate", err); + if (err == errSecItemNotFound) { + PJ_LOG(2, (THIS_FILE, "Private key must be placed in " + "the keychain")); + } + } else { + break; + } + } +#endif + } + + CFRelease(items); + + if (!identity) { + PJ_LOG(2, (THIS_FILE, "Failed extracting identity from " + "the cert file")); + return PJ_EINVAL; + } + + *p_identity = sec_identity_create(identity); + + CFRelease(identity); + } + + return PJ_SUCCESS; +} + +static pj_status_t verify_cert(applessl_sock_t *assock, pj_ssl_cert_t *cert) +{ + CFDataRef ca_data = NULL; + SecTrustRef trust = assock->trust; + bool result; + CFErrorRef error; + pj_status_t status = PJ_SUCCESS; + OSStatus err = noErr; + + if (trust && cert && cert->CA_file.slen) { + status = create_data_from_file(&ca_data, &cert->CA_file, + (cert->CA_path.slen? &cert->CA_path: + NULL)); + if (status != PJ_SUCCESS) + PJ_LOG(2, (THIS_FILE, "Failed reading CA file")); + } else if (trust && cert && cert->CA_buf.slen) { + ca_data = CFDataCreate(NULL, (const UInt8 *)cert->CA_buf.ptr, + cert->CA_buf.slen); + if (!ca_data) + PJ_LOG(2, (THIS_FILE, "Not enough memory for CA buffer")); + } + + if (ca_data) { + SecCertificateRef ca_cert; + CFMutableArrayRef ca_array; + + ca_cert = SecCertificateCreateWithData(NULL, ca_data); + CFRelease(ca_data); + if (!ca_cert) { + PJ_LOG(2, (THIS_FILE, "Failed creating certificate from " + "CA file/buffer. It has to be " + "in DER format.")); + status = PJ_EINVAL; + goto on_return; + } + + ca_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (!ca_array) { + PJ_LOG(2, (THIS_FILE, "Not enough memory for CA array")); + CFRelease(ca_cert); + status = PJ_ENOMEM; + goto on_return; + } + + CFArrayAppendValue(ca_array, ca_cert); + CFRelease(ca_cert); + + err = SecTrustSetAnchorCertificates(trust, ca_array); + CFRelease(ca_array); + if (err != noErr) + pj_status_from_err(assock, "SetAnchorCerts", err); + + err = SecTrustSetAnchorCertificatesOnly(trust, true); + if (err != noErr) + pj_status_from_err(assock, "SetAnchorCertsOnly", err); + } + + result = SecTrustEvaluateWithError(trust, &error); + if (!result) { + pj_ssl_sock_t *ssock = &assock->base; + SecTrustResultType trust_result; + + err = SecTrustGetTrustResult(trust, &trust_result); + if (err == noErr) { +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL trust evaluation: %d", trust_result)); +#endif + switch (trust_result) { + case kSecTrustResultInvalid: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + break; + + case kSecTrustResultDeny: + case kSecTrustResultFatalTrustFailure: + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + break; + + case kSecTrustResultRecoverableTrustFailure: + /* Doc: "If you receive this result, you can retry + * after changing settings. For example, if trust is + * denied because the certificate has expired, ..." + * But this error can also mean another (recoverable) + * failure, though. + */ + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + break; + + case kSecTrustResultOtherError: + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + break; + + default: + break; + } + } + + if (error) + CFRelease(error); + + /* Evaluation failed */ + status = PJ_EEOF; + } + +on_return: + if (status != PJ_SUCCESS && assock->base.verify_status == 0) + assock->base.verify_status |= PJ_SSL_CERT_EUNKNOWN; + + return status; +} + + +/* + ******************************************************************* + * Network functions. + ******************************************************************* + */ + +/* Send data. */ +static pj_status_t network_send(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + const void *data, + pj_ssize_t *size, + unsigned flags) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + dispatch_data_t content; + + if (!assock->connection) + return PJ_EGONE; + + content = dispatch_data_create(data, *size, assock->queue, + DISPATCH_DATA_DESTRUCTOR_DEFAULT); + if (!content) + return PJ_ENOMEM; + + nw_connection_send(assock->connection, content, + NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, + ^(nw_error_t error) + { + event_t event; + + if (error != NULL) { + errno = nw_error_get_error_code(error); + if (errno == 89) { + /* Error 89 is network cancelled, not a send error. */ + return; + } else { + warn("Send error"); + } + } + + event.type = EVENT_DATA_SENT; + event.body.data_sent_ev.send_key = send_key; + if (error != NULL) { + event.body.data_sent_ev.sent = (errno > 0)? -errno: errno; + } else { + event.body.data_sent_ev.sent = dispatch_data_get_size(content); + } + + event_manager_post_event(ssock, &event, PJ_TRUE); + }); + dispatch_release(content); + + return PJ_EPENDING; +} + +static pj_status_t network_start_read(pj_ssl_sock_t *ssock, + unsigned async_count, + unsigned buff_size, + void *readbuf[], + pj_uint32_t flags) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + unsigned i; + + if (!assock->connection) + return PJ_EGONE; + + for (i = 0; i < async_count; i++) { + nw_connection_receive(assock->connection, 1, buff_size, + ^(dispatch_data_t content, nw_content_context_t context, + bool is_complete, nw_error_t error) + { + pj_status_t status = PJ_SUCCESS; + + /* If the context is marked as complete, and is the final context, + * we're read-closed. + */ + if (is_complete && + (context == NULL || nw_content_context_get_is_final(context))) + { + return; + } + + if (error != NULL) { + errno = nw_error_get_error_code(error); + if (errno == 89) { + /* Since error 89 is network intentionally cancelled by + * us, we immediately return. + */ + return; + } else { + warn("Read error, stopping further receives"); + status = PJ_EEOF; + } + } + + dispatch_block_t schedule_next_receive = + ^{ + /* If there was no error in receiving, request more data. */ + if (!error && !is_complete && assock->connection) { + network_start_read(ssock, async_count, buff_size, + readbuf, flags); + } + }; + + if (content) { + dispatch_data_apply(content, + ^(dispatch_data_t region, size_t offset, + const void *buffer, size_t inSize) + { + /* This block can be invoked multiple times, + * each for every contiguous memory region in the content. + */ + event_t event; + + memcpy(ssock->asock_rbuf[i], buffer, inSize); + + event.type = EVENT_DATA_READ; + event.body.data_read_ev.data = ssock->asock_rbuf[i]; + event.body.data_read_ev.size = inSize; + event.body.data_read_ev.status = status; + event.body.data_read_ev.remainder = 0; + + event_manager_post_event(ssock, &event, PJ_FALSE); + + return (bool)true; + }); + + schedule_next_receive(); + + } else { + if (status != PJ_SUCCESS) { + event_t event; + + /* Report read error to application */ + event.type = EVENT_DATA_READ; + event.body.data_read_ev.data = NULL; + event.body.data_read_ev.size = 0; + event.body.data_read_ev.status = status; + event.body.data_read_ev.remainder = 0; + + event_manager_post_event(ssock, &event, PJ_TRUE); + } + + schedule_next_receive(); + } + }); + } + + return PJ_SUCCESS; +} + +/* Get address of local endpoint */ +static pj_status_t network_get_localaddr(pj_ssl_sock_t *ssock, + pj_sockaddr_t *addr, + int *namelen) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + nw_path_t path; + nw_endpoint_t endpoint; + const struct sockaddr *address; + + path = nw_connection_copy_current_path(assock->connection); + if (!path) + return PJ_EINVALIDOP; + + endpoint = nw_path_copy_effective_local_endpoint(path); + nw_release(path); + if (!endpoint) + return PJ_EINVALIDOP; + + address = nw_endpoint_get_address(endpoint); + if (address) { + pj_sockaddr_cp(addr, address); + *namelen = pj_sockaddr_get_addr_len(addr); + } + nw_release(endpoint); + + return PJ_SUCCESS; +} + +static pj_status_t network_create_params(pj_ssl_sock_t * ssock, + const pj_sockaddr_t *localaddr, + pj_uint16_t port_range, + nw_parameters_t *p_params) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + char ip_addr[PJ_INET6_ADDRSTRLEN]; + unsigned port; + char port_str[PJ_INET6_ADDRSTRLEN]; + nw_endpoint_t local_endpoint; + nw_parameters_t parameters; + nw_parameters_configure_protocol_block_t configure_tls; + nw_protocol_stack_t protocol_stack; + nw_protocol_options_t ip_options; + tls_protocol_version_t min_proto = tls_protocol_version_TLSv10; + tls_protocol_version_t max_proto = tls_protocol_version_TLSv13; + + /* Set min and max protocol version */ + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) { + ssock->param.proto = PJ_SSL_SOCK_PROTO_TLS1 | + PJ_SSL_SOCK_PROTO_TLS1_1 | + PJ_SSL_SOCK_PROTO_TLS1_2 | + PJ_SSL_SOCK_PROTO_TLS1_3; + } + + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) { + max_proto = tls_protocol_version_TLSv13; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) { + max_proto = tls_protocol_version_TLSv12; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) { + max_proto = tls_protocol_version_TLSv11; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) { + max_proto = tls_protocol_version_TLSv10; + } else { + PJ_LOG(3, (THIS_FILE, "Unsupported TLS protocol")); + return PJ_EINVAL; + } + + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) { + min_proto = tls_protocol_version_TLSv10; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) { + min_proto = tls_protocol_version_TLSv11; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) { + min_proto = tls_protocol_version_TLSv12; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) { + min_proto = tls_protocol_version_TLSv13; + } + + /* Set certificate */ + if (ssock->cert) { + pj_status_t status = create_identity_from_cert(assock, ssock->cert, + &assock->identity); + if (status != PJ_SUCCESS) + return status; + } + + configure_tls = ^(nw_protocol_options_t tls_options) + { + sec_protocol_options_t sec_options; + + sec_options = nw_tls_copy_sec_protocol_options(tls_options); + + /* Set identity */ + if (ssock->cert && assock->identity) { + sec_protocol_options_set_local_identity(sec_options, + assock->identity); + } + + sec_protocol_options_set_min_tls_protocol_version(sec_options, + min_proto); + sec_protocol_options_set_max_tls_protocol_version(sec_options, + max_proto); + + /* Set cipher list */ + if (ssock->param.ciphers_num > 0) { + unsigned i; + for (i = 0; i < ssock->param.ciphers_num; i++) { + sec_protocol_options_append_tls_ciphersuite(sec_options, + (tls_ciphersuite_t)ssock->param.ciphers[i]); + } + } + + if (!ssock->is_server && ssock->param.server_name.slen) { + sec_protocol_options_set_tls_server_name(sec_options, + ssock->param.server_name.ptr); + } + + sec_protocol_options_set_tls_renegotiation_enabled(sec_options, + true); + /* This must be disabled, otherwise server may think this is + * a resumption of a previously closed connection, and our + * verify block may never be invoked! + */ + sec_protocol_options_set_tls_resumption_enabled(sec_options, false); + + /* SSL verification options */ + sec_protocol_options_set_peer_authentication_required(sec_options, + true); + + /* Handshake flow: + * 1. Server's challenge block, provide server's trust + * 2. Client's verify block, to verify server's trust + * 3. Client's challenge block, provide client's trust + * 4. Only if client's trust is not NULL, server's verify block, + * to verify client's trust. + */ + sec_protocol_options_set_challenge_block(sec_options, + ^(sec_protocol_metadata_t metadata, + sec_protocol_challenge_complete_t complete) + { + complete(assock->identity); + }, assock->queue); + + sec_protocol_options_set_verify_block(sec_options, + ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, + sec_protocol_verify_complete_t complete) + { + event_t event; + pj_status_t status; + bool result = true; + + assock->trust = trust_ref? sec_trust_copy_ref(trust_ref): nil; + + assock->cipher = + sec_protocol_metadata_get_negotiated_tls_ciphersuite(metadata); + + /* For client, call on_connect_complete() callback first. */ + if (!ssock->is_server && ssock->ssl_state == SSL_STATE_NULL) { + if (!assock->connection) + complete(false); + + event.type = EVENT_CONNECT; + event.body.connect_ev.status = PJ_SUCCESS; + status = event_manager_post_event(ssock, &event, PJ_FALSE); + if (status == PJ_EGONE) + complete(false); + } + + event.type = EVENT_VERIFY_CERT; + status = event_manager_post_event(ssock, &event, PJ_FALSE); + if (status == PJ_EGONE) + complete(false); + + /* Check the result of cert verification. */ + if (ssock->verify_status != PJ_SSL_CERT_ESUCCESS) { + if (ssock->param.verify_peer) { + /* Verification failed. */ + result = false; + } else { + /* When verification is not requested just return ok here, + * however application can still get the verification status. + */ + result = true; + } + } + + complete(result); + }, assock->queue); + + nw_release(sec_options); + }; + + parameters = nw_parameters_create_secure_tcp(configure_tls, + NW_PARAMETERS_DEFAULT_CONFIGURATION); + + protocol_stack = nw_parameters_copy_default_protocol_stack(parameters); + ip_options = nw_protocol_stack_copy_internet_protocol(protocol_stack); + if (ssock->param.sock_af == pj_AF_INET()) { + nw_ip_options_set_version(ip_options, nw_ip_version_4); + } else if (ssock->param.sock_af == pj_AF_INET6()) { + nw_ip_options_set_version(ip_options, nw_ip_version_6); + } + nw_release(ip_options); + nw_release(protocol_stack); + + if (ssock->is_server && ssock->param.reuse_addr) { + nw_parameters_set_reuse_local_address(parameters, true); + } + + /* Create local endpoint. + * Currently we ignore QoS and socket options. + */ + pj_sockaddr_print(localaddr, ip_addr,sizeof(ip_addr),0); + + if (port_range) { + pj_uint16_t max_try = MAX_BIND_RETRY; + + if (port_range && port_range < max_try) { + max_try = port_range; + } + for (; max_try; --max_try) { + pj_uint16_t base_port; + + base_port = pj_sockaddr_get_port(localaddr); + port = (pj_uint16_t)(base_port + pj_rand() % (port_range + 1)); + pj_utoa(port, port_str); + + local_endpoint = nw_endpoint_create_host(ip_addr, port_str); + if (local_endpoint) + break; + } + } else { + port = pj_sockaddr_get_port(localaddr); + pj_utoa(port, port_str); + + local_endpoint = nw_endpoint_create_host(ip_addr, port_str); + } + + if (!local_endpoint) { + PJ_LOG(2, (THIS_FILE, "Failed creating local endpoint")); + return PJ_EINVALIDOP; + } + + nw_parameters_set_local_endpoint(parameters, local_endpoint); + nw_release(local_endpoint); + + *p_params = parameters; + return PJ_SUCCESS; +} + +/* Setup assock's connection state callback and start the connection */ +static pj_status_t network_setup_connection(pj_ssl_sock_t *ssock, + void *connection) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + assock->connection = (nw_connection_t)connection; + pj_status_t status; + + /* Initialize input circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 8192); + if (status != PJ_SUCCESS) + return status; + + /* Initialize output circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 8192); + if (status != PJ_SUCCESS) + return status; + + nw_connection_set_queue(assock->connection, assock->queue); + + assock->con_state = nw_connection_state_invalid; + nw_connection_set_state_changed_handler(assock->connection, + ^(nw_connection_state_t state, nw_error_t error) + { + pj_status_t status = PJ_SUCCESS; + pj_bool_t call_cb = PJ_FALSE; +#if SSL_DEBUG + if (!pj_thread_is_registered()) { + pj_bzero(queue_th_desc, sizeof(pj_thread_desc)); + pj_thread_register("sslq", queue_th_desc, &queue_th); + } + PJ_LOG(3, (THIS_FILE, "SSL state change %p %d", assock, state)); +#endif + + if (error && state != nw_connection_state_cancelled) { + errno = nw_error_get_error_code(error); + warn("Connection failed %p", assock); + status = PJ_STATUS_FROM_OS(errno); +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL state and errno %d %d", state, errno)); +#endif + call_cb = PJ_TRUE; + } + + if (state == nw_connection_state_ready) { + if (ssock->is_server) { + nw_protocol_definition_t tls_def; + nw_protocol_metadata_t prot_meta; + sec_protocol_metadata_t meta; + + tls_def = nw_protocol_copy_tls_definition(); + prot_meta = nw_connection_copy_protocol_metadata(connection, + tls_def); + meta = nw_tls_copy_sec_protocol_metadata(prot_meta); + assock->cipher = + sec_protocol_metadata_get_negotiated_tls_ciphersuite(meta); + + if (ssock->param.require_client_cert && + !sec_protocol_metadata_access_peer_certificate_chain( + meta, ^(sec_certificate_t certificate) {} )) + { + status = PJ_EEOF; + } + nw_release(tls_def); + nw_release(prot_meta); + nw_release(meta); + } + call_cb = PJ_TRUE; + } else if (state == nw_connection_state_cancelled) { + /* We release the reference in ssl_destroy() */ + // nw_release(assock->connection); + // assock->connection = nil; + } + + if (call_cb) { + event_t event; + + event.type = EVENT_HANDSHAKE_COMPLETE; + event.body.handshake_ev.status = status; + event_manager_post_event(ssock, &event, PJ_TRUE); + + if (ssock->is_server && status == PJ_SUCCESS) { + status = network_start_read(ssock, ssock->param.async_cnt, + (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, 0); + } + } + + assock->con_state = state; + }); + + nw_connection_start(assock->connection); + + return PJ_SUCCESS; +} + +static pj_status_t network_start_accept(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_sockaddr_t *localaddr, + int addr_len, + const pj_ssl_sock_param *newsock_param) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + pj_status_t status; + nw_parameters_t parameters = NULL; + + status = network_create_params(ssock, localaddr, 0, ¶meters); + if (status != PJ_SUCCESS) + return status; + + /* Create listener */ + assock->listener = nw_listener_create(parameters); + nw_release(parameters); + if (!assock->listener) { + PJ_LOG(2, (THIS_FILE, "Failed creating listener")); + return PJ_EINVALIDOP; + } + + nw_listener_set_queue(assock->listener, assock->queue); + /* Hold a reference until cancelled */ + nw_retain(assock->listener); + + assock->lis_state = nw_listener_state_invalid; + nw_listener_set_state_changed_handler(assock->listener, + ^(nw_listener_state_t state, nw_error_t error) + { + errno = error ? nw_error_get_error_code(error) : 0; + + if (state == nw_listener_state_failed) { + warn("listener failed\n"); + pj_sockaddr_set_port(&ssock->local_addr, 0); + dispatch_semaphore_signal(assock->ev_semaphore); + } else if (state == nw_listener_state_ready) { + /* Update local port */ + pj_sockaddr_set_port(&ssock->local_addr, + nw_listener_get_port(assock->listener)); + dispatch_semaphore_signal(assock->ev_semaphore); + } else if (state == nw_listener_state_cancelled) { + /* We release the reference in ssl_destroy() */ + // nw_release(assock->listener); + // assock->listener = nil; + } + assock->lis_state = state; + }); + + nw_listener_set_new_connection_handler(assock->listener, + ^(nw_connection_t connection) + { + nw_endpoint_t endpoint = nw_connection_copy_endpoint(connection); + const struct sockaddr *address; + event_t event; + + address = nw_endpoint_get_address(endpoint); + + event.type = EVENT_ACCEPT; + event.body.accept_ev.newconn = connection; + pj_sockaddr_cp(&event.body.accept_ev.src_addr, address); + event.body.accept_ev.src_addr_len = pj_sockaddr_get_addr_len(address); + event.body.accept_ev.status = PJ_SUCCESS; + + nw_retain(connection); + event_manager_post_event(ssock, &event, PJ_TRUE); + + nw_release(endpoint); + }); + + /* Update local address */ + ssock->addr_len = addr_len; + pj_sockaddr_cp(&ssock->local_addr, localaddr); + + /* Start accepting */ + pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param); + ssock->newsock_param.grp_lock = NULL; + + /* Start listening to the address */ + nw_listener_start(assock->listener); + /* Wait until it's ready */ + dispatch_semaphore_wait(assock->ev_semaphore, DISPATCH_TIME_FOREVER); + + if (pj_sockaddr_get_port(&ssock->local_addr) == 0) { + /* Failed. */ + status = PJ_EEOF; + goto on_error; + } + + return PJ_SUCCESS; + +on_error: + ssl_reset_sock_state(ssock); + return status; +} + + +static pj_status_t network_start_connect(pj_ssl_sock_t *ssock, + pj_ssl_start_connect_param *connect_param) +{ + char ip_addr[PJ_INET6_ADDRSTRLEN]; + unsigned port; + char port_str[PJ_INET6_ADDRSTRLEN]; + nw_endpoint_t endpoint; + nw_parameters_t parameters; + nw_connection_t connection; + pj_status_t status; + + pj_pool_t *pool = connect_param->pool; + const pj_sockaddr_t *localaddr = connect_param->localaddr; + pj_uint16_t port_range = connect_param->local_port_range; + const pj_sockaddr_t *remaddr = connect_param->remaddr; + int addr_len = connect_param->addr_len; + + PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, + PJ_EINVAL); + + status = network_create_params(ssock, localaddr, port_range, + ¶meters); + if (status != PJ_SUCCESS) + return status; + + /* Create remote endpoint */ + pj_sockaddr_print(remaddr, ip_addr,sizeof(ip_addr),0); + port = pj_sockaddr_get_port(remaddr); + pj_utoa(port, port_str); + + endpoint = nw_endpoint_create_host(ip_addr, port_str); + if (!endpoint) { + PJ_LOG(2, (THIS_FILE, "Failed creating remote endpoint")); + nw_release(parameters); + return PJ_EINVALIDOP; + } + + connection = nw_connection_create(endpoint, parameters); + nw_release(endpoint); + nw_release(parameters); + if (!connection) { + PJ_LOG(2, (THIS_FILE, "Failed creating connection")); + return PJ_EINVALIDOP; + } + + /* Hold a reference until cancelled */ + nw_retain(connection); + + status = network_setup_connection(ssock, connection); + if (status != PJ_SUCCESS) + return status; + + /* Save remote address */ + pj_sockaddr_cp(&ssock->rem_addr, remaddr); + + /* Update local address */ + ssock->addr_len = addr_len; + pj_sockaddr_cp(&ssock->local_addr, localaddr); + + return PJ_EPENDING; +} + + +/* + ******************************************************************* + * SSL functions. + ******************************************************************* + */ + +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + applessl_sock_t *assock; + + /* Create event manager */ + if (event_manager_create() != PJ_SUCCESS) + return NULL; + + assock = PJ_POOL_ZALLOC_T(pool, applessl_sock_t); + + assock->queue = dispatch_queue_create("ssl_queue", DISPATCH_QUEUE_SERIAL); + assock->ev_semaphore = dispatch_semaphore_create(0); + if (!assock->queue || !assock->ev_semaphore) { + ssl_destroy(&assock->base); + return NULL; + } + + return (pj_ssl_sock_t *)assock; +} + +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + /* Nothing to do here. SSL has been configured before connection + * is started. + */ + return PJ_SUCCESS; +} + +static void close_connection(applessl_sock_t *assock) +{ + if (assock->connection) { + unsigned i; + nw_connection_t conn = assock->connection; + + assock->connection = nil; + nw_connection_force_cancel(conn); + nw_release(conn); + + /* We need to wait until the connection is at cancelled state, + * otherwise events will still be delivered even though we + * already force cancel and release the connection. + */ + for (i = 0; i < 40; i++) { + if (assock->con_state == nw_connection_state_cancelled) break; + pj_thread_sleep(50); + } + + event_manager_remove_events(&assock->base); + + if (assock->con_state != nw_connection_state_cancelled) { + PJ_LOG(3, (THIS_FILE, "Warning: Failed to cancel SSL connection " + "%p %d", assock, assock->con_state)); + } + +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL connection %p closed", assock)); +#endif + + } +} + +/* Close sockets */ +static void ssl_close_sockets(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + + if (assock->identity) { + nw_release(assock->identity); + assock->identity = nil; + } + + if (assock->trust) { + nw_release(assock->trust); + assock->trust = nil; + } + + /* This can happen when pj_ssl_sock_create() fails. */ + if (!ssock->write_mutex) + return; + + pj_lock_acquire(ssock->write_mutex); + close_connection(assock); + pj_lock_release(ssock->write_mutex); +} + +/* Destroy Apple SSL. */ +static void ssl_destroy(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + + close_connection(assock); + + if (assock->listener) { + unsigned i; + + nw_listener_set_new_connection_handler(assock->listener, nil); + nw_listener_cancel(assock->listener); + + for (i = 0; i < 20; i++) { + if (assock->lis_state == nw_listener_state_cancelled) break; + pj_thread_sleep(50); + } + if (assock->lis_state != nw_listener_state_cancelled) { + PJ_LOG(3, (THIS_FILE, "Warning: Failed to cancel SSL listener " + "%p %d", assock, assock->lis_state)); + } + nw_release(assock->listener); + assock->listener = nil; + } + + event_manager_remove_events(ssock); + + /* Important: if we are called from a blocking dispatch block, + * we need to signal it before destroying ourselves. + */ + if (assock->ev_semaphore) { + dispatch_semaphore_signal(assock->ev_semaphore); + } + + if (assock->queue) { + dispatch_release(assock->queue); + assock->queue = NULL; + } + + if (assock->ev_semaphore) { + dispatch_release(assock->ev_semaphore); + assock->ev_semaphore = nil; + } + + /* Destroy circular buffers */ + circ_deinit(&ssock->circ_buf_input); + circ_deinit(&ssock->circ_buf_output); + + PJ_LOG(4, (THIS_FILE, "SSL %p destroyed", ssock)); +} + + +/* Reset socket state. */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock) +{ + pj_lock_acquire(ssock->circ_buf_output_mutex); + ssock->ssl_state = SSL_STATE_NULL; + pj_lock_release(ssock->circ_buf_output_mutex); + +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL reset sock state %p", ssock)); +#endif + + ssl_close_sockets(ssock); +} + + +/* This function is taken from Apple's sslAppUtils.cpp (version 58286.41.2), + * with some modifications. + */ +const char *sslGetCipherSuiteString(SSLCipherSuite cs) +{ + switch (cs) { + /* TLS addenda using AES-CBC, RFC 3268 */ + case TLS_RSA_WITH_AES_128_CBC_SHA: + return "TLS_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA: + return "TLS_DH_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA: + return "TLS_DH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA: + return "TLS_DH_anon_WITH_AES_128_CBC_SHA"; + case TLS_RSA_WITH_AES_256_CBC_SHA: + return "TLS_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA: + return "TLS_DH_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA: + return "TLS_DH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA: + return "TLS_DH_anon_WITH_AES_256_CBC_SHA"; + + /* ECDSA addenda, RFC 4492 */ + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + return "TLS_ECDH_ECDSA_WITH_NULL_SHA"; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return "TLS_ECDH_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + return "TLS_ECDHE_ECDSA_WITH_NULL_SHA"; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_RSA_WITH_NULL_SHA: + return "TLS_ECDH_RSA_WITH_NULL_SHA"; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + return "TLS_ECDH_RSA_WITH_RC4_128_SHA"; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + return "TLS_ECDHE_RSA_WITH_NULL_SHA"; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return "TLS_ECDHE_RSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_anon_WITH_NULL_SHA: + return "TLS_ECDH_anon_WITH_NULL_SHA"; + case TLS_ECDH_anon_WITH_RC4_128_SHA: + return "TLS_ECDH_anon_WITH_RC4_128_SHA"; + case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + return "TLS_ECDH_anon_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + return "TLS_ECDH_anon_WITH_AES_256_CBC_SHA"; + + /* TLS 1.2 addenda, RFC 5246 */ + case TLS_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + return "TLS_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + return "TLS_DH_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_DH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + return "TLS_DH_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + return "TLS_DH_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA256: + return "TLS_DH_anon_WITH_AES_128_CBC_SHA256"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA256: + return "TLS_DH_anon_WITH_AES_256_CBC_SHA256"; + + /* TLS addenda using AES-GCM, RFC 5288 */ + case TLS_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_DH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_DH_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + return "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + return "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + return "TLS_DH_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + return "TLS_DH_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_anon_WITH_AES_128_GCM_SHA256: + return "TLS_DH_anon_WITH_AES_128_GCM_SHA256"; + case TLS_DH_anon_WITH_AES_256_GCM_SHA384: + return "TLS_DH_anon_WITH_AES_256_GCM_SHA384"; + + /* ECDSA addenda, RFC 5289 */ + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384"; + + case TLS_AES_128_GCM_SHA256: + return "TLS_AES_128_GCM_SHA256"; + case TLS_AES_256_GCM_SHA384: + return "TLS_AES_256_GCM_SHA384"; + case TLS_CHACHA20_POLY1305_SHA256: + return "TLS_CHACHA20_POLY1305_SHA256"; + case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"; + case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"; + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"; + + default: + return "TLS_CIPHER_STRING_UNKNOWN"; + } +} + +static void ssl_ciphers_populate(void) +{ + /* SSLGetSupportedCiphers() is deprecated and we can't find + * the replacement API, so we just list the valid ciphers here + * taken from the tls_ciphersuite_t doc. + */ + tls_ciphersuite_t ciphers[] = { + tls_ciphersuite_AES_128_GCM_SHA256, + tls_ciphersuite_AES_256_GCM_SHA384, + tls_ciphersuite_CHACHA20_POLY1305_SHA256, + tls_ciphersuite_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls_ciphersuite_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls_ciphersuite_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls_ciphersuite_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls_ciphersuite_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + tls_ciphersuite_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls_ciphersuite_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls_ciphersuite_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + tls_ciphersuite_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls_ciphersuite_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + tls_ciphersuite_RSA_WITH_3DES_EDE_CBC_SHA, + tls_ciphersuite_RSA_WITH_AES_128_CBC_SHA, + tls_ciphersuite_RSA_WITH_AES_128_CBC_SHA256, + tls_ciphersuite_RSA_WITH_AES_128_GCM_SHA256, + tls_ciphersuite_RSA_WITH_AES_256_CBC_SHA, + tls_ciphersuite_RSA_WITH_AES_256_CBC_SHA256, + tls_ciphersuite_RSA_WITH_AES_256_GCM_SHA384 + }; + if (!ssl_cipher_num) { + unsigned i; + + ssl_cipher_num = sizeof(ciphers)/sizeof(ciphers[0]); + for (i = 0; i < ssl_cipher_num; i++) { + ssl_ciphers[i].id = (pj_ssl_cipher)ciphers[i]; + ssl_ciphers[i].name = sslGetCipherSuiteString(ciphers[i]); + } + } +} + + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + + return (pj_ssl_cipher) assock->cipher; +} + + +#if !TARGET_OS_IPHONE +static void get_info_and_cn(CFArrayRef array, CFMutableStringRef info, + CFStringRef *cn) +{ + const void *keys[] = {kSecOIDOrganizationalUnitName, kSecOIDCountryName, + kSecOIDStateProvinceName, kSecOIDLocalityName, + kSecOIDOrganizationName, kSecOIDCommonName}; + const char *labels[] = { "OU=", "C=", "ST=", "L=", "O=", "CN="}; + pj_bool_t add_separator = PJ_FALSE; + int i, n; + + *cn = NULL; + for(i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) { + for (n = 0 ; n < CFArrayGetCount(array); n++) { + CFDictionaryRef dict; + CFTypeRef dictkey; + CFStringRef str; + + dict = CFArrayGetValueAtIndex(array, n); + if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) + continue; + dictkey = CFDictionaryGetValue(dict, kSecPropertyKeyLabel); + if (!CFEqual(dictkey, keys[i])) + continue; + str = (CFStringRef) CFDictionaryGetValue(dict, + kSecPropertyKeyValue); + + if (CFStringGetLength(str) > 0) { + if (add_separator) { + CFStringAppendCString(info, "/", kCFStringEncodingUTF8); + } + CFStringAppendCString(info, labels[i], kCFStringEncodingUTF8); + CFStringAppend(info, str); + add_separator = PJ_TRUE; + + if (CFEqual(keys[i], kSecOIDCommonName)) + *cn = str; + } + } + } +} + +static CFDictionaryRef get_cert_oid(SecCertificateRef cert, CFStringRef oid, + CFTypeRef *value) +{ + void *key[1]; + CFArrayRef key_arr; + CFDictionaryRef vals, dict; + + key[0] = (void *)oid; + key_arr = CFArrayCreate(NULL, (const void **)key, 1, + &kCFTypeArrayCallBacks); + + vals = SecCertificateCopyValues(cert, key_arr, NULL); + dict = CFDictionaryGetValue(vals, key[0]); + if (!dict) { + CFRelease(key_arr); + CFRelease(vals); + return NULL; + } + + *value = CFDictionaryGetValue(dict, kSecPropertyKeyValue); + + CFRelease(key_arr); + + return vals; +} + +#endif + +/* Get certificate info; in case the certificate info is already populated, + * this function will check if the contents need updating by inspecting the + * issuer and the serial number. + */ +static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, + SecCertificateRef cert) +{ + pj_bool_t update_needed; + char buf[512]; + size_t bufsize = sizeof(buf); + const pj_uint8_t *serial_no = NULL; + size_t serialsize = 0; + CFMutableStringRef issuer_info; + CFStringRef str; + CFDataRef serial = NULL; +#if !TARGET_OS_IPHONE + CFStringRef issuer_cn = NULL; + CFDictionaryRef dict; +#endif + + pj_assert(pool && ci && cert); + + /* Get issuer */ + issuer_info = CFStringCreateMutable(NULL, 0); +#if !TARGET_OS_IPHONE +{ + /* Unfortunately, unlike on Mac, on iOS we don't have these APIs + * to query the certificate info such as the issuer, version, + * validity, and alt names. + */ + CFArrayRef issuer_vals; + + dict = get_cert_oid(cert, kSecOIDX509V1IssuerName, + (CFTypeRef *)&issuer_vals); + if (dict) { + get_info_and_cn(issuer_vals, issuer_info, &issuer_cn); + if (issuer_cn) + issuer_cn = CFStringCreateCopy(NULL, issuer_cn); + CFRelease(dict); + } +} +#endif + CFStringGetCString(issuer_info, buf, bufsize, kCFStringEncodingUTF8); + + /* Get serial no */ + if (__builtin_available(macOS 10.13, iOS 11.0, *)) { + serial = SecCertificateCopySerialNumberData(cert, NULL); + if (serial) { + serial_no = CFDataGetBytePtr(serial); + serialsize = CFDataGetLength(serial); + } + } + + /* Check if the contents need to be updated */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || + pj_memcmp(ci->serial_no, serial_no, serialsize); + if (!update_needed) { + CFRelease(issuer_info); + return; + } + + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ +#if !TARGET_OS_IPHONE +{ + CFStringRef version; + + dict = get_cert_oid(cert, kSecOIDX509V1Version, + (CFTypeRef *)&version); + if (dict) { + ci->version = CFStringGetIntValue(version); + CFRelease(dict); + } +} +#endif + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); +#if !TARGET_OS_IPHONE + if (issuer_cn) { + CFStringGetCString(issuer_cn, buf, bufsize, kCFStringEncodingUTF8); + pj_strdup2(pool, &ci->issuer.cn, buf); + CFRelease(issuer_cn); + } +#endif + CFRelease(issuer_info); + + /* Serial number */ + if (serial) { + if (serialsize > sizeof(ci->serial_no)) + serialsize = sizeof(ci->serial_no); + pj_memcpy(ci->serial_no, serial_no, serialsize); + CFRelease(serial); + } + + /* Subject */ + str = SecCertificateCopySubjectSummary(cert); + CFStringGetCString(str, buf, bufsize, kCFStringEncodingUTF8); + pj_strdup2(pool, &ci->subject.cn, buf); + CFRelease(str); +#if !TARGET_OS_IPHONE +{ + CFArrayRef subject; + CFMutableStringRef subject_info; + + dict = get_cert_oid(cert, kSecOIDX509V1SubjectName, + (CFTypeRef *)&subject); + if (dict) { + subject_info = CFStringCreateMutable(NULL, 0); + + get_info_and_cn(subject, subject_info, &str); + + CFStringGetCString(subject_info, buf, bufsize, kCFStringEncodingUTF8); + pj_strdup2(pool, &ci->subject.info, buf); + + CFRelease(dict); + CFRelease(subject_info); + } +} +#endif + + /* Validity */ +#if !TARGET_OS_IPHONE +{ + CFNumberRef validity; + double interval; + + dict = get_cert_oid(cert, kSecOIDX509V1ValidityNotBefore, + (CFTypeRef *)&validity); + if (dict) { + if (CFNumberGetValue(validity, CFNumberGetType(validity), + &interval)) + { + /* Darwin's absolute reference date is 1 Jan 2001 00:00:00 GMT */ + ci->validity.start.sec = (unsigned long)interval + 978278400L; + } + CFRelease(dict); + } + + dict = get_cert_oid(cert, kSecOIDX509V1ValidityNotAfter, + (CFTypeRef *)&validity); + if (dict) { + if (CFNumberGetValue(validity, CFNumberGetType(validity), + &interval)) + { + ci->validity.end.sec = (unsigned long)interval + 978278400L; + } + CFRelease(dict); + } +} +#endif + + /* Subject Alternative Name extension */ +#if !TARGET_OS_IPHONE +{ + CFArrayRef altname; + CFIndex i; + + dict = get_cert_oid(cert, kSecOIDSubjectAltName, (CFTypeRef *)&altname); + if (!dict || !CFArrayGetCount(altname)) + return; + + ci->subj_alt_name.entry = pj_pool_calloc(pool, CFArrayGetCount(altname), + sizeof(*ci->subj_alt_name.entry)); + + for (i = 0; i < CFArrayGetCount(altname); ++i) { + CFDictionaryRef item; + CFStringRef label, value; + pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN; + + item = CFArrayGetValueAtIndex(altname, i); + if (CFGetTypeID(item) != CFDictionaryGetTypeID()) + continue; + + label = (CFStringRef)CFDictionaryGetValue(item, kSecPropertyKeyLabel); + if (CFGetTypeID(label) != CFStringGetTypeID()) + continue; + + value = (CFStringRef)CFDictionaryGetValue(item, kSecPropertyKeyValue); + + if (!CFStringCompare(label, CFSTR("DNS Name"), + kCFCompareCaseInsensitive)) + { + if (CFGetTypeID(value) != CFStringGetTypeID()) + continue; + CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_DNS; + } else if (!CFStringCompare(label, CFSTR("IP Address"), + kCFCompareCaseInsensitive)) + { + if (CFGetTypeID(value) != CFStringGetTypeID()) + continue; + CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_IP; + } else if (!CFStringCompare(label, CFSTR("Email Address"), + kCFCompareCaseInsensitive)) + { + if (CFGetTypeID(value) != CFStringGetTypeID()) + continue; + CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_RFC822; + } else if (!CFStringCompare(label, CFSTR("URI"), + kCFCompareCaseInsensitive)) + { + CFStringRef uri; + + if (CFGetTypeID(value) != CFURLGetTypeID()) + continue; + uri = CFURLGetString((CFURLRef)value); + CFStringGetCString(uri, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_URI; + } + + if (type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + if (type == PJ_SSL_CERT_NAME_IP) { + char ip_buf[PJ_INET6_ADDRSTRLEN+10]; + int len = CFStringGetLength(value); + int af = pj_AF_INET(); + + if (len == sizeof(pj_in6_addr)) af = pj_AF_INET6(); + pj_inet_ntop2(af, buf, ip_buf, sizeof(ip_buf)); + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + ip_buf); + } else { + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + buf); + } + ci->subj_alt_name.cnt++; + } + } + + CFRelease(dict); +} +#endif +} + +/* Update local & remote certificates info. This function should be + * called after handshake successfully completed. + */ +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + SecTrustRef trust = assock->trust; + CFIndex count; + SecCertificateRef cert; + + pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + + /* Get active local certificate */ + if (assock->identity) { + CFArrayRef cert_arr; + + cert_arr = sec_identity_copy_certificates_ref(assock->identity); + if (cert_arr) { + count = CFArrayGetCount(cert_arr); + if (count > 0) { + CFTypeRef elmt; + + elmt = (CFTypeRef) CFArrayGetValueAtIndex(cert_arr, 0); + if (CFGetTypeID(elmt) == SecCertificateGetTypeID()) { + cert = (SecCertificateRef)elmt; + get_cert_info(ssock->pool, &ssock->local_cert_info, cert); + } + } + CFRelease(cert_arr); + } + } + + /* Get active remote certificate */ + if (trust) { + count = SecTrustGetCertificateCount(trust); + if (count > 0) { + cert = SecTrustGetCertificateAtIndex(trust, 0); + get_cert_info(ssock->pool, &ssock->remote_cert_info, cert); + } + } +} + +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(is_server); +} + +static void ssl_set_peer_name(pj_ssl_sock_t *ssock) +{ + /* Setting server name is done when configuring tls before connection + * is started. + */ + PJ_UNUSED_ARG(ssock); +} + +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock) +{ + /* Nothing to do here, just return EPENDING. Handshake has + * automatically been performed when starting a connection. + */ + + return PJ_EPENDING; +} + +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size) +{ + pj_size_t circ_buf_size, read_size; + + pj_lock_acquire(ssock->circ_buf_input_mutex); + + if (circ_empty(&ssock->circ_buf_input)) { + pj_lock_release(ssock->circ_buf_input_mutex); + *size = 0; + return PJ_SUCCESS; + } + + circ_buf_size = circ_size(&ssock->circ_buf_input); + read_size = PJ_MIN(circ_buf_size, *size); + + circ_read(&ssock->circ_buf_input, data, read_size); + + pj_lock_release(ssock->circ_buf_input_mutex); + + *size = read_size; + + return PJ_SUCCESS; +} + +/* + * Write the plain data to buffer. It will be encrypted later during + * sending. + */ +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, + pj_ssize_t size, int *nwritten) +{ + pj_status_t status; + + status = circ_write(&ssock->circ_buf_output, data, size); + *nwritten = (status == PJ_SUCCESS)? (int)size: 0; + + return status; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + PJ_UNUSED_ARG(ssock); + + /* According to the doc, + * sec_protocol_options_set_tls_renegotiation_enabled() should + * enable TLS session renegotiation for versions 1.2 and earlier. + * But we can't trigger renegotiation manually, or can we? + */ + return PJ_ENOTSUP; +} + + +#endif /* PJ_SSL_SOCK_IMP_APPLE */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c new file mode 100755 index 000000000..10aac98e4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +/* + * Initialize the SSL socket configuration with the default values. + */ +PJ_DEF(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param) +{ + pj_bzero(param, sizeof(*param)); + + /* Socket config */ + param->sock_af = PJ_AF_INET; + param->sock_type = pj_SOCK_STREAM(); + param->async_cnt = 1; + param->concurrency = -1; + param->whole_data = PJ_TRUE; +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_GNUTLS) + /* GnuTLS is allowed to send bigger chunks.*/ + param->send_buffer_size = 65536; +#else + param->send_buffer_size = 8192; +#endif +#if !defined(PJ_SYMBIAN) || PJ_SYMBIAN == 0 + param->read_buffer_size = 1500; +#endif + param->qos_type = PJ_QOS_TYPE_BEST_EFFORT; + param->qos_ignore_error = PJ_TRUE; + + param->sockopt_ignore_error = PJ_TRUE; + + /* Security config */ + param->proto = PJ_SSL_SOCK_PROTO_DEFAULT; +} + +/* + * Duplicate SSL socket parameter. + */ +PJ_DEF(void) pj_ssl_sock_param_copy(pj_pool_t *pool, pj_ssl_sock_param *dst, const pj_ssl_sock_param *src) +{ + /* Init secure socket param */ + pj_memcpy(dst, src, sizeof(*dst)); + if (src->ciphers_num > 0) { + unsigned i; + dst->ciphers = (pj_ssl_cipher *)pj_pool_calloc(pool, src->ciphers_num, sizeof(pj_ssl_cipher)); + for (i = 0; i < src->ciphers_num; ++i) + dst->ciphers[i] = src->ciphers[i]; + } + + if (src->curves_num > 0) { + unsigned i; + dst->curves = (pj_ssl_curve *)pj_pool_calloc(pool, src->curves_num, sizeof(pj_ssl_curve)); + for (i = 0; i < src->curves_num; ++i) + dst->curves[i] = src->curves[i]; + } + + if (src->server_name.slen) { + /* Server name must be null-terminated */ + pj_strdup_with_null(pool, &dst->server_name, &src->server_name); + } + + if (src->sigalgs.slen) { + /* Sigalgs name must be null-terminated */ + pj_strdup_with_null(pool, &dst->sigalgs, &src->sigalgs); + } + + if (src->entropy_path.slen) { + /* Path name must be null-terminated */ + pj_strdup_with_null(pool, &dst->entropy_path, &src->entropy_path); + } +} + +PJ_DEF(pj_status_t) +pj_ssl_cert_get_verify_status_strings(pj_uint32_t verify_status, const char *error_strings[], unsigned *count) +{ + unsigned i = 0, shift_idx = 0; + unsigned unknown = 0; + pj_uint32_t errs; + + PJ_ASSERT_RETURN(error_strings && count, PJ_EINVAL); + + if (verify_status == PJ_SSL_CERT_ESUCCESS && *count) { + error_strings[0] = "OK"; + *count = 1; + return PJ_SUCCESS; + } + + errs = verify_status; + + while (errs && i < *count) { + pj_uint32_t err; + const char *p = NULL; + + if ((errs & 1) == 0) { + shift_idx++; + errs >>= 1; + continue; + } + + err = (1 << shift_idx); + + switch (err) { + case PJ_SSL_CERT_EISSUER_NOT_FOUND: + p = "The issuer certificate cannot be found"; + break; + case PJ_SSL_CERT_EUNTRUSTED: + p = "The certificate is untrusted"; + break; + case PJ_SSL_CERT_EVALIDITY_PERIOD: + p = "The certificate has expired or not yet valid"; + break; + case PJ_SSL_CERT_EINVALID_FORMAT: + p = "One or more fields of the certificate cannot be decoded " + "due to invalid format"; + break; + case PJ_SSL_CERT_EISSUER_MISMATCH: + p = "The issuer info in the certificate does not match to the " + "(candidate) issuer certificate"; + break; + case PJ_SSL_CERT_ECRL_FAILURE: + p = "The CRL certificate cannot be found or cannot be read " + "properly"; + break; + case PJ_SSL_CERT_EREVOKED: + p = "The certificate has been revoked"; + break; + case PJ_SSL_CERT_EINVALID_PURPOSE: + p = "The certificate or CA certificate cannot be used for the " + "specified purpose"; + break; + case PJ_SSL_CERT_ECHAIN_TOO_LONG: + p = "The certificate chain length is too long"; + break; + case PJ_SSL_CERT_EIDENTITY_NOT_MATCH: + p = "The server identity does not match to any identities " + "specified in the certificate"; + break; + case PJ_SSL_CERT_EUNKNOWN: + default: + unknown++; + break; + } + + /* Set error string */ + if (p) + error_strings[i++] = p; + + /* Next */ + shift_idx++; + errs >>= 1; + } + + /* Unknown error */ + if (unknown && i < *count) + error_strings[i++] = "Unknown verification error"; + + *count = i; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c new file mode 100755 index 000000000..f031d856c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK is enabled */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 + +#define THIS_FILE "ssl_sock_dump.c" + +#define CHECK_BUF_LEN() \ + if ((len < 0) || (len >= end - p)) { \ + *p = '\0'; \ + return -1; \ + } \ + p += len; + +PJ_DEF(pj_ssize_t) pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci, const char *indent, char *buf, pj_size_t buf_size) +{ + const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + pj_parsed_time pt1; + pj_parsed_time pt2; + unsigned i; + int len = 0; + char *p, *end; + + p = buf; + end = buf + buf_size; + + pj_time_decode(&ci->validity.start, &pt1); + pj_time_decode(&ci->validity.end, &pt2); + + /* Version */ + len = pj_ansi_snprintf(p, end - p, "%sVersion : v%d\n", indent, ci->version); + CHECK_BUF_LEN(); + + /* Serial number */ + len = pj_ansi_snprintf(p, end - p, "%sSerial : ", indent); + CHECK_BUF_LEN(); + + for (i = 0; i < sizeof(ci->serial_no) && !ci->serial_no[i]; ++i) + ; + for (; i < sizeof(ci->serial_no); ++i) { + len = pj_ansi_snprintf(p, end - p, "%02X ", ci->serial_no[i] & 0xFF); + CHECK_BUF_LEN(); + } + *(p - 1) = '\n'; + + /* Subject */ + len = pj_ansi_snprintf(p, end - p, "%sSubject : %.*s\n", indent, (int)ci->subject.cn.slen, ci->subject.cn.ptr); + CHECK_BUF_LEN(); + len = + pj_ansi_snprintf(p, end - p, "%s %.*s\n", indent, (int)ci->subject.info.slen, ci->subject.info.ptr); + CHECK_BUF_LEN(); + + /* Issuer */ + len = pj_ansi_snprintf(p, end - p, "%sIssuer : %.*s\n", indent, (int)ci->issuer.cn.slen, ci->issuer.cn.ptr); + CHECK_BUF_LEN(); + len = pj_ansi_snprintf(p, end - p, "%s %.*s\n", indent, (int)ci->issuer.info.slen, ci->issuer.info.ptr); + CHECK_BUF_LEN(); + + /* Validity period */ + len = pj_ansi_snprintf(p, end - p, + "%sValid from : %s %4d-%02d-%02d " + "%02d:%02d:%02d.%03d %s\n", + indent, wdays[pt1.wday], pt1.year, pt1.mon + 1, pt1.day, pt1.hour, pt1.min, pt1.sec, + pt1.msec, (ci->validity.gmt ? "GMT" : "")); + CHECK_BUF_LEN(); + + len = pj_ansi_snprintf(p, end - p, + "%sValid to : %s %4d-%02d-%02d " + "%02d:%02d:%02d.%03d %s\n", + indent, wdays[pt2.wday], pt2.year, pt2.mon + 1, pt2.day, pt2.hour, pt2.min, pt2.sec, + pt2.msec, (ci->validity.gmt ? "GMT" : "")); + CHECK_BUF_LEN(); + + /* Subject alternative name extension */ + if (ci->subj_alt_name.cnt) { + len = pj_ansi_snprintf(p, end - p, "%ssubjectAltName extension\n", indent); + CHECK_BUF_LEN(); + + for (i = 0; i < ci->subj_alt_name.cnt; ++i) { + const char *type = NULL; + + switch (ci->subj_alt_name.entry[i].type) { + case PJ_SSL_CERT_NAME_RFC822: + type = "MAIL"; + break; + case PJ_SSL_CERT_NAME_DNS: + type = " DNS"; + break; + case PJ_SSL_CERT_NAME_URI: + type = " URI"; + break; + case PJ_SSL_CERT_NAME_IP: + type = " IP"; + break; + default: + break; + } + if (type) { + len = pj_ansi_snprintf(p, end - p, "%s %s : %.*s\n", indent, type, + (int)ci->subj_alt_name.entry[i].name.slen, ci->subj_alt_name.entry[i].name.ptr); + CHECK_BUF_LEN(); + } + } + } + + return (p - buf); +} + +#endif /* PJ_HAS_SSL_SOCK */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c new file mode 100755 index 000000000..20a7ba858 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c @@ -0,0 +1,1143 @@ +/* + * Copyright (C) 2018-2018 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2014-2017 Savoir-faire Linux. + * (https://www.savoirfairelinux.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER) +#include +#endif + +#include + +/* Only build when PJ_HAS_SSL_SOCK and the implementation is GnuTLS. */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_GNUTLS) + +#define SSL_SOCK_IMP_USE_CIRC_BUF + +#include "ssl_sock_imp_common.h" +#include "ssl_sock_imp_common.c" + +#define THIS_FILE "ssl_sock_gtls.c" + +/* Maximum ciphers */ +#define MAX_CIPHERS 100 + +/* Standard trust locations */ +#define TRUST_STORE_FILE1 "/etc/ssl/certs/ca-certificates.crt" +#define TRUST_STORE_FILE2 "/etc/ssl/certs/ca-bundle.crt" + +/* Debugging output level for GnuTLS only */ +#define GNUTLS_LOG_LEVEL 0 + +/* GnuTLS includes */ +#include +#include +#include + +#ifdef _MSC_VER +#pragma comment(lib, "libgnutls") +#endif + +/* Secure socket structure definition. */ +typedef struct gnutls_sock_t { + pj_ssl_sock_t base; + + gnutls_session_t session; + gnutls_certificate_credentials_t xcred; + + int tls_init_count; /* library initialization counter */ +} gnutls_sock_t; + +/* Last error reported somehow */ +static int tls_last_error; + +/* + ******************************************************************* + * Static/internal functions. + ******************************************************************* + */ + +/* Convert from GnuTLS error to pj_status_t. */ +static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err) +{ + pj_status_t status; + + switch (err) { + case GNUTLS_E_SUCCESS: + status = PJ_SUCCESS; + break; + case GNUTLS_E_MEMORY_ERROR: + status = PJ_ENOMEM; + break; + case GNUTLS_E_LARGE_PACKET: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_NO_CERTIFICATE_FOUND: + status = PJ_ENOTFOUND; + break; + case GNUTLS_E_SESSION_EOF: + status = PJ_EEOF; + break; + case GNUTLS_E_HANDSHAKE_TOO_LARGE: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_EXPIRED: + status = PJ_EGONE; + break; + case GNUTLS_E_TIMEDOUT: + status = PJ_ETIMEDOUT; + break; + case GNUTLS_E_PREMATURE_TERMINATION: + status = PJ_ECANCELLED; + break; + case GNUTLS_E_INTERNAL_ERROR: + case GNUTLS_E_UNIMPLEMENTED_FEATURE: + status = PJ_EBUG; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_REHANDSHAKE: + status = PJ_EPENDING; + break; + case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: + case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: + case GNUTLS_E_RECORD_LIMIT_REACHED: + status = PJ_ETOOMANY; + break; + case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: + case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: + case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: + case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: + case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: + case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: + status = PJ_ENOTSUP; + break; + case GNUTLS_E_INVALID_SESSION: + case GNUTLS_E_INVALID_REQUEST: + case GNUTLS_E_INVALID_PASSWORD: + case GNUTLS_E_ILLEGAL_PARAMETER: + case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: + case GNUTLS_E_UNEXPECTED_PACKET: + case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: + case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: + case GNUTLS_E_UNWANTED_ALGORITHM: + case GNUTLS_E_USER_ERROR: + status = PJ_EINVAL; + break; + default: + status = PJ_EUNKNOWN; + break; + } + + /* Not thread safe */ + tls_last_error = err; + if (ssock) + ssock->last_err = err; + return status; +} + +/* Get error string from GnuTLS using tls_last_error */ +static pj_str_t tls_strerror(pj_status_t status, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + const char *tmp = gnutls_strerror(tls_last_error); + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + if (tmp) { + pj_ansi_strncpy(buf, tmp, bufsize); + errstr = pj_str(buf); + return errstr; + } +#endif /* PJ_HAS_ERROR_STRING */ + + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s", tls_last_error, tmp); + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + + return errstr; +} + +/* GnuTLS way of reporting internal operations. */ +static void tls_print_logs(int level, const char *msg) +{ + PJ_LOG(3, (THIS_FILE, "GnuTLS [%d]: %s", level, msg)); +} + +/* Initialize GnuTLS. */ +static pj_status_t tls_init(void) +{ + /* Register error subsystem */ + pj_status_t status = + pj_register_strerror(PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 6, PJ_ERRNO_SPACE_SIZE, &tls_strerror); + pj_assert(status == PJ_SUCCESS); + + /* Init GnuTLS library */ + int ret = gnutls_global_init(); + if (ret < 0) + return tls_status_from_err(NULL, ret); + + gnutls_global_set_log_level(GNUTLS_LOG_LEVEL); + gnutls_global_set_log_function(tls_print_logs); + + /* Init available ciphers */ + if (!ssl_cipher_num) { + unsigned int i; + + for (i = 0; i < PJ_ARRAY_SIZE(ssl_ciphers); i++) { + unsigned char id[2]; + const char *suite; + + suite = gnutls_cipher_suite_info(i, (unsigned char *)id, NULL, NULL, NULL, NULL); + ssl_ciphers[i].id = 0; + /* usually the array size is bigger than the number of available + * ciphers anyway, so by checking here we can exit the loop as soon + * as either all ciphers have been added or the array is full */ + if (suite) { + ssl_ciphers[i].id = (pj_ssl_cipher)(pj_uint32_t)((id[0] << 8) | id[1]); + ssl_ciphers[i].name = suite; + } else + break; + } + + ssl_cipher_num = i; + } + + return PJ_SUCCESS; +} + +/* Shutdown GnuTLS */ +static void tls_deinit(void) +{ + gnutls_global_deinit(); +} + +/* Callback invoked every time a certificate has to be validated. */ +static int tls_cert_verify_cb(gnutls_session_t session) +{ + pj_ssl_sock_t *ssock; + unsigned int status; + int ret; + + /* Get SSL socket instance */ + ssock = (pj_ssl_sock_t *)gnutls_session_get_ptr(session); + pj_assert(ssock); + + /* Support only x509 format */ + ret = gnutls_certificate_type_get(session) != GNUTLS_CRT_X509; + if (ret < 0) { + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* Store verification status */ + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret < 0) { + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + return GNUTLS_E_CERTIFICATE_ERROR; + } + if (ssock->param.verify_peer) { + if (status & GNUTLS_CERT_INVALID) { + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND; + else if (status & GNUTLS_CERT_EXPIRED || status & GNUTLS_CERT_NOT_ACTIVATED) + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + else if (status & GNUTLS_CERT_SIGNER_NOT_CA || status & GNUTLS_CERT_INSECURE_ALGORITHM) + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + else if (status & GNUTLS_CERT_UNEXPECTED_OWNER || status & GNUTLS_CERT_MISMATCH) + ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH; + else if (status & GNUTLS_CERT_REVOKED) + ssock->verify_status |= PJ_SSL_CERT_EREVOKED; + else + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* When verification is not requested just return ok here, however + * applications can still get the verification status. */ + gnutls_x509_crt_t cert; + unsigned int cert_list_size; + const gnutls_datum_t *cert_list; + int ret; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto out; + + cert_list = gnutls_certificate_get_peers(session, &cert_list_size); + if (cert_list == NULL) { + ret = GNUTLS_E_NO_CERTIFICATE_FOUND; + goto out; + } + + /* TODO: verify whole chain perhaps? */ + ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_PEM); + if (ret < 0) { + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + goto out; + } + ret = gnutls_x509_crt_check_hostname(cert, ssock->param.server_name.ptr); + if (ret < 0) + goto out; + + gnutls_x509_crt_deinit(cert); + + /* notify GnuTLS to continue handshake normally */ + return GNUTLS_E_SUCCESS; + + out: + tls_last_error = ret; + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + return GNUTLS_E_SUCCESS; +} + +/* gnutls_handshake() and gnutls_record_send() will call this function to + * send/write (encrypted) data */ +static ssize_t tls_data_push(gnutls_transport_ptr_t ptr, const void *data, size_t len) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr; + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + pj_lock_acquire(ssock->circ_buf_output_mutex); + if (circ_write(&ssock->circ_buf_output, data, len) != PJ_SUCCESS) { + pj_lock_release(ssock->circ_buf_output_mutex); + + gnutls_transport_set_errno(gssock->session, ENOMEM); + return -1; + } + + pj_lock_release(ssock->circ_buf_output_mutex); + + return len; +} + +/* gnutls_handshake() and gnutls_record_recv() will call this function to + * receive/read (encrypted) data */ +static ssize_t tls_data_pull(gnutls_transport_ptr_t ptr, void *data, pj_size_t len) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr; + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + pj_lock_acquire(ssock->circ_buf_input_mutex); + + if (circ_empty(&ssock->circ_buf_input)) { + pj_lock_release(ssock->circ_buf_input_mutex); + + /* Data buffers not yet filled */ + gnutls_transport_set_errno(gssock->session, EAGAIN); + return -1; + } + + pj_size_t circ_buf_size = circ_size(&ssock->circ_buf_input); + pj_size_t read_size = PJ_MIN(circ_buf_size, len); + + circ_read(&ssock->circ_buf_input, data, read_size); + + pj_lock_release(ssock->circ_buf_input_mutex); + + return read_size; +} + +/* Append a string to the priority string, only once. */ +static pj_status_t tls_str_append_once(pj_str_t *dst, pj_str_t *src) +{ + if (pj_strstr(dst, src) == NULL) { + /* Check buffer size */ + if (dst->slen + src->slen + 3 > 1024) + return PJ_ETOOMANY; + + pj_strcat2(dst, ":+"); + pj_strcat(dst, src); + } + return PJ_SUCCESS; +} + +/* Generate priority string with user preference order. */ +static pj_status_t tls_priorities_set(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + char buf[1024]; + char priority_buf[256]; + pj_str_t cipher_list; + pj_str_t compression = pj_str("COMP-NULL"); + pj_str_t server = pj_str(":%SERVER_PRECEDENCE"); + int i, j, ret; + pj_str_t priority; + const char *err; + + pj_strset(&cipher_list, buf, 0); + pj_strset(&priority, priority_buf, 0); + + /* For each level, enable only the requested protocol */ + pj_strcat2(&priority, "NORMAL:"); + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) { + pj_strcat2(&priority, "+VERS-TLS1.2:"); + } + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) { + pj_strcat2(&priority, "+VERS-TLS1.1:"); + } + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) { + pj_strcat2(&priority, "+VERS-TLS1.0:"); + } + pj_strcat2(&priority, "-VERS-SSL3.0:"); + pj_strcat2(&priority, "%LATEST_RECORD_VERSION"); + + pj_strcat(&cipher_list, &priority); + for (i = 0; i < ssock->param.ciphers_num; i++) { + for (j = 0;; j++) { + pj_ssl_cipher c; + const char *suite; + unsigned char id[2]; + gnutls_protocol_t proto; + gnutls_kx_algorithm_t kx; + gnutls_mac_algorithm_t mac; + gnutls_cipher_algorithm_t algo; + + suite = gnutls_cipher_suite_info(j, (unsigned char *)id, &kx, &algo, &mac, &proto); + if (!suite) + break; + + c = (pj_ssl_cipher)(pj_uint32_t)((id[0] << 8) | id[1]); + if (ssock->param.ciphers[i] == c) { + char temp[256]; + pj_str_t cipher_entry; + + /* Protocol version */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, "VERS-"); + pj_strcat2(&cipher_entry, gnutls_protocol_get_name(proto)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Cipher */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, gnutls_cipher_get_name(algo)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Mac */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, gnutls_mac_get_name(mac)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Key exchange */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, gnutls_kx_get_name(kx)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Compression is always disabled */ + /* Signature is level-default */ + break; + } + } + } + + /* Disable compression, it's a TLS-only extension after all */ + tls_str_append_once(&cipher_list, &compression); + + /* Server will be the one deciding which crypto to use */ + if (ssock->is_server) { + if (cipher_list.slen + server.slen + 1 > sizeof(buf)) + return PJ_ETOOMANY; + else + pj_strcat(&cipher_list, &server); + } + + /* End the string and print it */ + cipher_list.ptr[cipher_list.slen] = '\0'; + PJ_LOG(5, (ssock->pool->obj_name, "Priority string: %s", cipher_list.ptr)); + + /* Set our priority string */ + ret = gnutls_priority_set_direct(gssock->session, cipher_list.ptr, &err); + if (ret < 0) { + tls_last_error = GNUTLS_E_INVALID_REQUEST; + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + +/* Load root CA file or load the installed ones. */ +static pj_status_t tls_trust_set(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int ntrusts = 0; + int err; + + err = gnutls_certificate_set_x509_system_trust(gssock->xcred); + if (err > 0) + ntrusts += err; + err = gnutls_certificate_set_x509_trust_file(gssock->xcred, TRUST_STORE_FILE1, GNUTLS_X509_FMT_PEM); + if (err > 0) + ntrusts += err; + + err = gnutls_certificate_set_x509_trust_file(gssock->xcred, TRUST_STORE_FILE2, GNUTLS_X509_FMT_PEM); + if (err > 0) + ntrusts += err; + + if (ntrusts > 0) + return PJ_SUCCESS; + else if (!ntrusts) + return PJ_ENOTFOUND; + else + return PJ_EINVAL; +} + +#if GNUTLS_VERSION_NUMBER < 0x030306 + +#ifdef _POSIX_PATH_MAX +#define GNUTLS_PATH_MAX _POSIX_PATH_MAX +#else +#define GNUTLS_PATH_MAX 256 +#endif + +static int gnutls_certificate_set_x509_trust_dir(gnutls_certificate_credentials_t cred, const char *dirname, + unsigned type) +{ + DIR *dirp; + struct dirent *d; + int ret; + int r = 0; + char path[GNUTLS_PATH_MAX]; +#ifndef _WIN32 + struct dirent e; +#endif + + dirp = opendir(dirname); + if (dirp != NULL) { + do { +#ifdef _WIN32 + d = readdir(dirp); + if (d != NULL) { +#else + ret = readdir_r(dirp, &e, &d); + if (ret == 0 && d != NULL +#ifdef _DIRENT_HAVE_D_TYPE + && (d->d_type == DT_REG || d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) +#endif + ) { +#endif + snprintf(path, sizeof(path), "%s/%s", dirname, d->d_name); + + ret = gnutls_certificate_set_x509_trust_file(cred, path, type); + if (ret >= 0) + r += ret; + } + } while (d != NULL); + closedir(dirp); + } + + return r; +} + +#endif + +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + return (pj_ssl_sock_t *)PJ_POOL_ZALLOC_T(pool, gnutls_sock_t); +} + +/* Create and initialize new GnuTLS context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + pj_ssl_cert_t *cert; + pj_status_t status; + int ret; + + pj_assert(ssock); + + cert = ssock->cert; + + /* Even if reopening is harmless, having one instance only simplifies + * deallocating it later on */ + if (!gssock->tls_init_count) { + gssock->tls_init_count++; + ret = tls_init(); + if (ret < 0) + return ret; + } else + return PJ_SUCCESS; + + /* Start this socket session */ + ret = gnutls_init(&gssock->session, ssock->is_server ? GNUTLS_SERVER : GNUTLS_CLIENT); + if (ret < 0) + goto out; + + /* Set the ssock object to be retrieved by transport (send/recv) and by + * user data from this session */ + gnutls_transport_set_ptr(gssock->session, (gnutls_transport_ptr_t)(uintptr_t)ssock); + gnutls_session_set_ptr(gssock->session, (gnutls_transport_ptr_t)(uintptr_t)ssock); + + /* Initialize input circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 512); + if (status != PJ_SUCCESS) + return status; + + /* Initialize output circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 512); + if (status != PJ_SUCCESS) + return status; + + /* Set the callback that allows GnuTLS to PUSH and PULL data + * TO and FROM the transport layer */ + gnutls_transport_set_push_function(gssock->session, tls_data_push); + gnutls_transport_set_pull_function(gssock->session, tls_data_pull); + + /* Determine which cipher suite to support */ + status = tls_priorities_set(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Allocate credentials for handshaking and transmission */ + ret = gnutls_certificate_allocate_credentials(&gssock->xcred); + if (ret < 0) + goto out; + gnutls_certificate_set_verify_function(gssock->xcred, tls_cert_verify_cb); + + /* Load system trust file(s) */ + status = tls_trust_set(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Load user-provided CA, certificate and key if available */ + if (cert) { + /* Load CA if one is specified. */ + if (cert->CA_file.slen) { + ret = gnutls_certificate_set_x509_trust_file(gssock->xcred, cert->CA_file.ptr, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_file(gssock->xcred, cert->CA_file.ptr, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto out; + } + if (cert->CA_path.slen) { + ret = gnutls_certificate_set_x509_trust_dir(gssock->xcred, cert->CA_path.ptr, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_dir(gssock->xcred, cert->CA_path.ptr, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto out; + } + + /* Load certificate, key and pass if one is specified */ + if (cert->cert_file.slen && cert->privkey_file.slen) { + const char *prikey_file = cert->privkey_file.ptr; + const char *prikey_pass = cert->privkey_pass.slen ? cert->privkey_pass.ptr : NULL; + ret = gnutls_certificate_set_x509_key_file2(gssock->xcred, cert->cert_file.ptr, prikey_file, + GNUTLS_X509_FMT_PEM, prikey_pass, 0); + if (ret != GNUTLS_E_SUCCESS) + ret = gnutls_certificate_set_x509_key_file2(gssock->xcred, cert->cert_file.ptr, prikey_file, + GNUTLS_X509_FMT_DER, prikey_pass, 0); + if (ret < 0) + goto out; + } + + if (cert->CA_buf.slen) { + gnutls_datum_t ca; + ca.data = (unsigned char *)cert->CA_buf.ptr; + ca.size = cert->CA_buf.slen; + ret = gnutls_certificate_set_x509_trust_mem(gssock->xcred, &ca, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_mem(gssock->xcred, &ca, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto out; + } + + if (cert->cert_buf.slen && cert->privkey_buf.slen) { + gnutls_datum_t cert_buf; + gnutls_datum_t privkey_buf; + + cert_buf.data = (unsigned char *)cert->CA_buf.ptr; + cert_buf.size = cert->CA_buf.slen; + privkey_buf.data = (unsigned char *)cert->privkey_buf.ptr; + privkey_buf.size = cert->privkey_buf.slen; + + const char *prikey_pass = cert->privkey_pass.slen ? cert->privkey_pass.ptr : NULL; + ret = gnutls_certificate_set_x509_key_mem2(gssock->xcred, &cert_buf, &privkey_buf, GNUTLS_X509_FMT_PEM, + prikey_pass, 0); + /* Load DER format */ + /* + if (ret != GNUTLS_E_SUCCESS) + ret = gnutls_certificate_set_x509_key_mem2(gssock->xcred, + &cert_buf, + &privkey_buf, + GNUTLS_X509_FMT_DER, + prikey_pass, + 0); + */ + if (ret < 0) + goto out; + } + } + + /* Require client certificate if asked */ + if (ssock->is_server && ssock->param.require_client_cert) + gnutls_certificate_server_set_request(gssock->session, GNUTLS_CERT_REQUIRE); + + /* Finally set credentials for this session */ + ret = gnutls_credentials_set(gssock->session, GNUTLS_CRD_CERTIFICATE, gssock->xcred); + if (ret < 0) + goto out; + + ret = GNUTLS_E_SUCCESS; +out: + return tls_status_from_err(ssock, ret); +} + +/* Destroy GnuTLS credentials and session. */ +static void ssl_destroy(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + if (gssock->session) { + gnutls_bye(gssock->session, GNUTLS_SHUT_RDWR); + gnutls_deinit(gssock->session); + gssock->session = NULL; + } + + if (gssock->xcred) { + gnutls_certificate_free_credentials(gssock->xcred); + gssock->xcred = NULL; + } + + /* Free GnuTLS library */ + if (gssock->tls_init_count) { + gssock->tls_init_count--; + tls_deinit(); + } + + /* Destroy circular buffers */ + circ_deinit(&ssock->circ_buf_input); + circ_deinit(&ssock->circ_buf_output); +} + +/* Reset socket state. */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock) +{ + pj_lock_acquire(ssock->circ_buf_output_mutex); + ssock->ssl_state = SSL_STATE_NULL; + pj_lock_release(ssock->circ_buf_output_mutex); + + ssl_close_sockets(ssock); + + ssock->last_err = tls_last_error = GNUTLS_E_SUCCESS; +} + +static void ssl_ciphers_populate(void) +{ + if (!ssl_cipher_num) { + tls_init(); + tls_deinit(); + } +} + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int i; + gnutls_cipher_algorithm_t lookup; + gnutls_cipher_algorithm_t cipher; + + /* Current cipher */ + cipher = gnutls_cipher_get(gssock->session); + for (i = 0;; i++) { + unsigned char id[2]; + const char *suite; + + suite = gnutls_cipher_suite_info(i, (unsigned char *)id, NULL, &lookup, NULL, NULL); + if (suite) { + if (lookup == cipher) { + return (pj_uint32_t)((id[0] << 8) | id[1]); + } + } else { + break; + } + } + + return PJ_TLS_UNKNOWN_CIPHER; +} + +/* Get Common Name field string from a general name string */ +static void tls_cert_get_cn(const pj_str_t *gen_name, pj_str_t *cn) +{ + pj_str_t CN_sign = {"CN=", 3}; + char *p, *q; + + pj_bzero(cn, sizeof(cn)); + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 3; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, ','); + if (q) + cn->slen = q - p; +} + +/* Get certificate info; in case the certificate info is already populated, + * this function will check if the contents need updating by inspecting the + * issuer and the serial number. */ +static void tls_cert_get_info(pj_pool_t *pool, pj_ssl_cert_info *ci, gnutls_x509_crt_t cert) +{ + pj_bool_t update_needed; + char buf[512] = {0}; + size_t bufsize = sizeof(buf); + pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */ + size_t serialsize = sizeof(serial_no); + size_t len = sizeof(buf); + int i, ret, seq = 0; + pj_ssl_cert_name_type type; + + pj_assert(pool && ci && cert); + + /* Get issuer */ + gnutls_x509_crt_get_issuer_dn(cert, buf, &bufsize); + + /* Get serial no */ + gnutls_x509_crt_get_serial(cert, serial_no, &serialsize); + + /* Check if the contents need to be updated */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || pj_memcmp(ci->serial_no, serial_no, serialsize); + if (!update_needed) + return; + + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = gnutls_x509_crt_get_version(cert); + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + tls_cert_get_cn(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); + + /* Subject */ + bufsize = sizeof(buf); + gnutls_x509_crt_get_dn(cert, buf, &bufsize); + pj_strdup2(pool, &ci->subject.info, buf); + tls_cert_get_cn(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(cert); + ci->validity.start.sec = gnutls_x509_crt_get_activation_time(cert); + ci->validity.gmt = 0; + + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + char out[256] = {0}; + /* Get the number of all alternate names so that we can allocate + * the correct number of bytes in subj_alt_name */ + while (gnutls_x509_crt_get_subject_alt_name(cert, seq, out, &len, NULL) != + GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + seq++; + } + + ci->subj_alt_name.entry = pj_pool_calloc(pool, seq, sizeof(*ci->subj_alt_name.entry)); + if (!ci->subj_alt_name.entry) { + tls_last_error = GNUTLS_E_MEMORY_ERROR; + return; + } + + /* Now populate the alternative names */ + for (i = 0; i < seq; i++) { + len = sizeof(out) - 1; + ret = gnutls_x509_crt_get_subject_alt_name(cert, i, out, &len, NULL); + + switch (ret) { + case GNUTLS_SAN_IPADDRESS: + type = PJ_SSL_CERT_NAME_IP; + pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6() : pj_AF_INET(), out, buf, sizeof(buf)); + break; + case GNUTLS_SAN_URI: + type = PJ_SSL_CERT_NAME_URI; + break; + case GNUTLS_SAN_RFC822NAME: + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GNUTLS_SAN_DNSNAME: + type = PJ_SSL_CERT_NAME_DNS; + break; + default: + type = PJ_SSL_CERT_NAME_UNKNOWN; + break; + } + + if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + pj_strdup2(pool, &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + type == PJ_SSL_CERT_NAME_IP ? buf : out); + ci->subj_alt_name.cnt++; + } + } + /* TODO: if no DNS alt. names were found, we could check against + * the commonName as per RFC3280. */ + } +} + +static void tls_cert_get_chain_raw(pj_pool_t *pool, pj_ssl_cert_info *ci, const gnutls_datum_t *certs, size_t certs_num) +{ + size_t i = 0; + ci->raw_chain.cert_raw = pj_pool_calloc(pool, certs_num, sizeof(*ci->raw_chain.cert_raw)); + ci->raw_chain.cnt = certs_num; + for (i = 0; i < certs_num; ++i) { + const pj_str_t crt_raw = {(char *)certs[i].data, (pj_ssize_t)certs[i].size}; + pj_strdup(pool, ci->raw_chain.cert_raw + i, &crt_raw); + } +} + +/* Update local & remote certificates info. This function should be + * called after handshake or renegotiation successfully completed. */ +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + gnutls_x509_crt_t cert = NULL; + const gnutls_datum_t *us; + const gnutls_datum_t *certs; + unsigned int certslen = 0; + int ret = GNUTLS_CERT_INVALID; + + pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + + /* Get active local certificate */ + us = gnutls_certificate_get_ours(gssock->session); + if (!us) + goto us_out; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto us_out; + ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_DER); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_PEM); + if (ret < 0) + goto us_out; + + tls_cert_get_info(ssock->pool, &ssock->local_cert_info, cert); + pj_pool_reset(ssock->info_pool); + tls_cert_get_chain_raw(ssock->info_pool, &ssock->local_cert_info, us, 1); + +us_out: + tls_last_error = ret; + if (cert) + gnutls_x509_crt_deinit(cert); + else + pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); + + cert = NULL; + + /* Get active remote certificate */ + certs = gnutls_certificate_get_peers(gssock->session, &certslen); + if (certs == NULL || certslen == 0) + goto peer_out; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto peer_out; + + ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto peer_out; + + tls_cert_get_info(ssock->pool, &ssock->remote_cert_info, cert); + pj_pool_reset(ssock->info_pool); + tls_cert_get_chain_raw(ssock->info_pool, &ssock->remote_cert_info, certs, certslen); + +peer_out: + tls_last_error = ret; + if (cert) + gnutls_x509_crt_deinit(cert); + else + pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); +} + +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(is_server); +} + +static void ssl_set_peer_name(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + /* Set server name to connect */ + if (ssock->param.server_name.slen && get_ip_addr_ver(&ssock->param.server_name) == 0) { + int ret; + /* Server name is null terminated already */ + ret = gnutls_server_name_set(gssock->session, GNUTLS_NAME_DNS, ssock->param.server_name.ptr, + ssock->param.server_name.slen); + if (ret < 0) { + PJ_LOG(3, (ssock->pool->obj_name, "gnutls_server_name_set() failed: %s", gnutls_strerror(ret))); + } + } +} + +/* Try to perform an asynchronous handshake */ +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int ret; + pj_status_t status; + + /* Perform SSL handshake */ + ret = gnutls_handshake(gssock->session); + + status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status != PJ_SUCCESS) + return status; + + if (ret == GNUTLS_E_SUCCESS) { + /* System are GO */ + ssock->ssl_state = SSL_STATE_ESTABLISHED; + status = PJ_SUCCESS; + } else if (!gnutls_error_is_fatal(ret)) { + /* Non fatal error, retry later (busy or again) */ + status = PJ_EPENDING; + } else { + /* Fatal error invalidates session, no fallback */ + status = PJ_EINVAL; + } + + tls_last_error = ret; + + return status; +} + +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int decrypted_size; + + /* Decrypt received data using GnuTLS (will read our input + * circular buffer) */ + decrypted_size = gnutls_record_recv(gssock->session, data, *size); + *size = 0; + if (decrypted_size > 0) { + *size = decrypted_size; + return PJ_SUCCESS; + } else if (decrypted_size == 0) { + /* Nothing more to read */ + return PJ_SUCCESS; + } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) { + return PJ_EEOF; + } else if (decrypted_size == GNUTLS_E_AGAIN || decrypted_size == GNUTLS_E_INTERRUPTED || + !gnutls_error_is_fatal(decrypted_size)) { + /* non-fatal error, let's just continue */ + return PJ_SUCCESS; + } else { + return PJ_ECANCELLED; + } +} + +/* + * Write the plain data to GnuTLS, it will be encrypted by gnutls_record_send() + * and sent via tls_data_push. Note that re-negotitation may be on progress, so + * sending data should be delayed until re-negotiation is completed. + */ +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, pj_ssize_t size, int *nwritten) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int nwritten_; + pj_ssize_t total_written = 0; + + /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push + * callback to actually write the encrypted bytes into our output circular + * buffer. GnuTLS may refuse to "send" everything at once, but since we are + * not really sending now, we will just call it again now until it succeeds + * (or fails in a fatal way). */ + while (total_written < size) { + /* Try encrypting using GnuTLS */ + nwritten_ = gnutls_record_send(gssock->session, ((read_data_t *)data) + total_written, size - total_written); + + if (nwritten_ > 0) { + /* Good, some data was encrypted and written */ + total_written += nwritten_; + } else { + /* Normally we would have to retry record_send but our internal + * state has not changed, so we have to ask for more data first. + * We will just try again later, although this should never happen. + */ + *nwritten = nwritten_; + return tls_status_from_err(ssock, nwritten_); + } + } + + /* All encrypted data is written to the output circular buffer; + * now send it on the socket (or notify problem). */ + *nwritten = total_written; + return PJ_SUCCESS; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int status; + + /* First call gnutls_rehandshake() to see if this is even possible */ + status = gnutls_rehandshake(gssock->session); + + if (status == GNUTLS_E_SUCCESS) { + /* Rehandshake is possible, so try a GnuTLS handshake now. The eventual + * gnutls_record_recv() calls could return a few specific values during + * this state: + * + * - GNUTLS_E_REHANDSHAKE: rehandshake message processing + * - GNUTLS_E_WARNING_ALERT_RECEIVED: client does not wish to + * renegotiate + */ + return PJ_SUCCESS; + } else { + return tls_status_from_err(ssock, status); + } +} + +#endif /* PJ_HAS_SSL_SOCK */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c new file mode 100755 index 000000000..7491182c1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c @@ -0,0 +1,2081 @@ +/* + * Copyright (C) 2019-2019 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#include "ssl_sock_imp_common.h" + +/* Workaround for ticket #985 and #1930 */ +#ifndef PJ_SSL_SOCK_DELAYED_CLOSE_TIMEOUT +#define PJ_SSL_SOCK_DELAYED_CLOSE_TIMEOUT 500 +#endif + +enum { MAX_BIND_RETRY = 100 }; + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK +static pj_bool_t asock_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); + +static pj_bool_t asock_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +#endif + +#ifdef SSL_SOCK_IMP_USE_CIRC_BUF +/* + ******************************************************************* + * Circular buffer functions. + ******************************************************************* + */ + +static pj_status_t circ_init(pj_pool_factory *factory, circ_buf_t *cb, pj_size_t cap) +{ + cb->cap = cap; + cb->readp = 0; + cb->writep = 0; + cb->size = 0; + + /* Initial pool holding the buffer elements */ + cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL); + if (!cb->pool) + return PJ_ENOMEM; + + /* Allocate circular buffer */ + cb->buf = pj_pool_alloc(cb->pool, cap); + if (!cb->buf) { + pj_pool_release(cb->pool); + return PJ_ENOMEM; + } + + return PJ_SUCCESS; +} + +static void circ_deinit(circ_buf_t *cb) +{ + if (cb->pool) { + pj_pool_release(cb->pool); + cb->pool = NULL; + } +} + +static pj_bool_t circ_empty(const circ_buf_t *cb) +{ + return cb->size == 0; +} + +static pj_size_t circ_size(const circ_buf_t *cb) +{ + return cb->size; +} + +static pj_size_t circ_avail(const circ_buf_t *cb) +{ + return cb->cap - cb->size; +} + +static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) +{ + pj_size_t size_after = cb->cap - cb->readp; + pj_size_t tbc = PJ_MIN(size_after, len); + pj_size_t rem = len - tbc; + + pj_memcpy(dst, cb->buf + cb->readp, tbc); + pj_memcpy(dst + tbc, cb->buf, rem); + + cb->readp += len; + cb->readp &= (cb->cap - 1); + + cb->size -= len; +} + +static pj_status_t circ_write(circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) +{ + /* Overflow condition: resize */ + if (len > circ_avail(cb)) { + /* Minimum required capacity */ + pj_size_t min_cap = len + cb->size; + + /* Next 32-bit power of two */ + min_cap--; + min_cap |= min_cap >> 1; + min_cap |= min_cap >> 2; + min_cap |= min_cap >> 4; + min_cap |= min_cap >> 8; + min_cap |= min_cap >> 16; + min_cap++; + + /* Create a new pool to hold a bigger buffer, using the same factory */ + pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p", min_cap, min_cap, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Allocate our new buffer */ + pj_uint8_t *buf = pj_pool_alloc(pool, min_cap); + if (!buf) { + pj_pool_release(pool); + return PJ_ENOMEM; + } + + /* Save old size, which we shall restore after the next read */ + pj_size_t old_size = cb->size; + + /* Copy old data into beginning of new buffer */ + circ_read(cb, buf, cb->size); + + /* Restore old size now */ + cb->size = old_size; + + /* Release the previous pool */ + pj_pool_release(cb->pool); + + /* Update circular buffer members */ + cb->pool = pool; + cb->buf = buf; + cb->readp = 0; + cb->writep = cb->size; + cb->cap = min_cap; + } + + pj_size_t size_after = cb->cap - cb->writep; + pj_size_t tbc = PJ_MIN(size_after, len); + pj_size_t rem = len - tbc; + + pj_memcpy(cb->buf + cb->writep, src, tbc); + pj_memcpy(cb->buf, src + tbc, rem); + + cb->writep += len; + cb->writep &= (cb->cap - 1); + + cb->size += len; + + return PJ_SUCCESS; +} +#endif + +/* + ******************************************************************* + * Helper functions. + ******************************************************************* + */ + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + +/* Check IP address version. */ +static int get_ip_addr_ver(const pj_str_t *host) +{ + pj_in_addr dummy; + pj_in6_addr dummy6; + + /* First check if this is an IPv4 address */ + if (pj_inet_pton(pj_AF_INET(), host, &dummy) == PJ_SUCCESS) + return 4; + + /* Then check if this is an IPv6 address */ + if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS) + return 6; + + /* Not an IP address */ + return 0; +} + +/* Close sockets */ +static void ssl_close_sockets(pj_ssl_sock_t *ssock) +{ + pj_activesock_t *asock; + pj_sock_t sock; + + /* This can happen when pj_ssl_sock_create() fails. */ + if (!ssock->write_mutex) + return; + + pj_lock_acquire(ssock->write_mutex); + asock = ssock->asock; + if (asock) { + // Don't set ssock->asock to NULL, as it may trigger assertion in + // send operation. This should be safe as active socket will simply + // return PJ_EINVALIDOP on any operation if it is already closed. + // ssock->asock = NULL; + ssock->sock = PJ_INVALID_SOCKET; + } + sock = ssock->sock; + if (sock != PJ_INVALID_SOCKET) + ssock->sock = PJ_INVALID_SOCKET; + pj_lock_release(ssock->write_mutex); + + if (asock) + pj_activesock_close(asock); + + if (sock != PJ_INVALID_SOCKET) + pj_sock_close(sock); +} +#endif + +/* When handshake completed: + * - notify application + * - if handshake failed, reset SSL state + * - return PJ_FALSE when SSL socket instance is destroyed by application. + */ +static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock, pj_status_t status) +{ + ssock->handshake_status = status; + /* Cancel handshake timer */ + if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) { + pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); + ssock->timer.id = TIMER_NONE; + } + + /* Update certificates info on successful handshake */ + if (status == PJ_SUCCESS) + ssl_update_certs_info(ssock); + + /* Accepting */ + if (ssock->is_server) { + pj_bool_t ret = PJ_TRUE; + + if (status != PJ_SUCCESS) { + /* Handshake failed in accepting, destroy our self silently. */ + + char buf[PJ_INET6_ADDRSTRLEN + 10]; + + if (pj_sockaddr_has_addr(&ssock->rem_addr)) { + PJ_PERROR(3, (ssock->pool->obj_name, status, "Handshake failed in accepting %s", + pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3))); + } + + if (ssock->param.cb.on_accept_complete2) { + (*ssock->param.cb.on_accept_complete2)(ssock->parent, ssock, (pj_sockaddr_t *)&ssock->rem_addr, + pj_sockaddr_get_len((pj_sockaddr_t *)&ssock->rem_addr), status); + } + + /* Decrement ref count of parent */ + if (ssock->parent->param.grp_lock) { + pj_grp_lock_dec_ref(ssock->parent->param.grp_lock); + ssock->parent = NULL; + } + + /* Originally, this is a workaround for ticket #985. However, + * a race condition may occur in multiple worker threads + * environment when we are destroying SSL objects while other + * threads are still accessing them. + * Please see ticket #1930 for more info. + */ +#if 1 //(defined(PJ_WIN32) && PJ_WIN32!=0)||(defined(PJ_WIN64) && PJ_WIN64!=0) + if (ssock->param.timer_heap) { + pj_time_val interval = {0, PJ_SSL_SOCK_DELAYED_CLOSE_TIMEOUT}; + pj_status_t status1; + + ssock->ssl_state = SSL_STATE_NULL; + ssl_close_sockets(ssock); + + if (ssock->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); + } + pj_time_val_normalize(&interval); + status1 = pj_timer_heap_schedule_w_grp_lock(ssock->param.timer_heap, &ssock->timer, &interval, + TIMER_CLOSE, ssock->param.grp_lock); + if (status1 != PJ_SUCCESS) { + PJ_PERROR(3, (ssock->pool->obj_name, status, + "Failed to schedule a delayed close. " + "Race condition may occur.")); + ssock->timer.id = TIMER_NONE; + pj_ssl_sock_close(ssock); + } + } else { + pj_ssl_sock_close(ssock); + } +#else + { + pj_ssl_sock_close(ssock); + } +#endif + + return PJ_FALSE; + } + + /* Notify application the newly accepted SSL socket */ + if (ssock->param.cb.on_accept_complete2) { + ret = + (*ssock->param.cb.on_accept_complete2)(ssock->parent, ssock, (pj_sockaddr_t *)&ssock->rem_addr, + pj_sockaddr_get_len((pj_sockaddr_t *)&ssock->rem_addr), status); + } else if (ssock->param.cb.on_accept_complete) { + ret = (*ssock->param.cb.on_accept_complete)(ssock->parent, ssock, (pj_sockaddr_t *)&ssock->rem_addr, + pj_sockaddr_get_len((pj_sockaddr_t *)&ssock->rem_addr)); + } + + /* Decrement ref count of parent and reset parent (we don't need it + * anymore, right?). + */ + if (ssock->parent->param.grp_lock) { + pj_grp_lock_dec_ref(ssock->parent->param.grp_lock); + ssock->parent = NULL; + } + + if (ret == PJ_FALSE) + return PJ_FALSE; + } + + /* Connecting */ + else { + /* On failure, reset SSL socket state first, as app may try to + * reconnect in the callback. + */ + if (status != PJ_SUCCESS) { + /* Server disconnected us, possibly due to SSL nego failure */ + ssl_reset_sock_state(ssock); + } + if (ssock->param.cb.on_connect_complete) { + pj_bool_t ret; + ret = (*ssock->param.cb.on_connect_complete)(ssock, status); + if (ret == PJ_FALSE) + return PJ_FALSE; + } + } + + return PJ_TRUE; +} + +static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len) +{ + send_buf_t *send_buf = &ssock->send_buf; + pj_size_t avail_len, skipped_len = 0; + char *reg1, *reg2; + pj_size_t reg1_len, reg2_len; + write_data_t *p; + + /* Check buffer availability */ + avail_len = send_buf->max_len - send_buf->len; + if (avail_len < len) + return NULL; + + /* If buffer empty, reset start pointer and return it */ + if (send_buf->len == 0) { + send_buf->start = send_buf->buf; + send_buf->len = len; + p = (write_data_t *)send_buf->start; + goto init_send_data; + } + + /* Free space may be wrapped/splitted into two regions, so let's + * analyze them if any region can hold the write data. + */ + reg1 = send_buf->start + send_buf->len; + if (reg1 >= send_buf->buf + send_buf->max_len) + reg1 -= send_buf->max_len; + reg1_len = send_buf->max_len - send_buf->len; + if (reg1 + reg1_len > send_buf->buf + send_buf->max_len) { + reg1_len = send_buf->buf + send_buf->max_len - reg1; + reg2 = send_buf->buf; + reg2_len = send_buf->start - send_buf->buf; + } else { + reg2 = NULL; + reg2_len = 0; + } + + /* More buffer availability check, note that the write data must be in + * a contigue buffer. + */ + avail_len = PJ_MAX(reg1_len, reg2_len); + if (avail_len < len) + return NULL; + + /* Get the data slot */ + if (reg1_len >= len) { + p = (write_data_t *)reg1; + } else { + p = (write_data_t *)reg2; + skipped_len = reg1_len; + } + + /* Update buffer length */ + send_buf->len += len + skipped_len; + +init_send_data: + /* Init the new send data */ + pj_bzero(p, sizeof(*p)); + pj_list_init(p); + pj_list_push_back(&ssock->send_pending, p); + + return p; +} + +static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata) +{ + send_buf_t *buf = &ssock->send_buf; + write_data_t *spl = &ssock->send_pending; + + pj_assert(!pj_list_empty(&ssock->send_pending)); + + /* Free slot from the buffer */ + if (spl->next == wdata && spl->prev == wdata) { + /* This is the only data, reset the buffer */ + buf->start = buf->buf; + buf->len = 0; + } else if (spl->next == wdata) { + /* This is the first data, shift start pointer of the buffer and + * adjust the buffer length. + */ + buf->start = (char *)wdata->next; + if (wdata->next > wdata) { + buf->len -= ((char *)wdata->next - buf->start); + } else { + /* Overlapped */ + pj_size_t right_len, left_len; + right_len = buf->buf + buf->max_len - (char *)wdata; + left_len = (char *)wdata->next - buf->buf; + buf->len -= (right_len + left_len); + } + } else if (spl->prev == wdata) { + /* This is the last data, just adjust the buffer length */ + if (wdata->prev < wdata) { + pj_size_t jump_len; + jump_len = (char *)wdata - ((char *)wdata->prev + wdata->prev->record_len); + buf->len -= (wdata->record_len + jump_len); + } else { + /* Overlapped */ + pj_size_t right_len, left_len; + right_len = buf->buf + buf->max_len - ((char *)wdata->prev + wdata->prev->record_len); + left_len = (char *)wdata + wdata->record_len - buf->buf; + buf->len -= (right_len + left_len); + } + } + /* For data in the middle buffer, just do nothing on the buffer. The slot + * will be freed later when freeing the first/last data. + */ + + /* Remove the data from send pending list */ + pj_list_erase(wdata); +} + +/* Flush write circular buffer to network socket. */ +static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_size_t orig_len, + unsigned flags) +{ + pj_ssize_t len; + write_data_t *wdata; + pj_size_t needed_len; + pj_status_t status; + + pj_lock_acquire(ssock->write_mutex); + + /* Check if there is data in the circular buffer, flush it if any */ + if (io_empty(ssock, &ssock->circ_buf_output)) { + pj_lock_release(ssock->write_mutex); + return PJ_SUCCESS; + } + + /* Get data and its length */ + len = io_size(ssock, &ssock->circ_buf_output); + if (len == 0) { + pj_lock_release(ssock->write_mutex); + return PJ_SUCCESS; + } + + /* Calculate buffer size needed, and align it to 8 */ + needed_len = len + sizeof(write_data_t); + needed_len = ((needed_len + 7) >> 3) << 3; + + /* Allocate buffer for send data */ + wdata = alloc_send_data(ssock, needed_len); + if (wdata == NULL) { + /* Oops, the send buffer is full, let's just + * queue it for sending and return PJ_EPENDING. + */ + ssock->send_buf_pending.data_len = needed_len; + ssock->send_buf_pending.app_key = send_key; + ssock->send_buf_pending.flags = flags; + ssock->send_buf_pending.plain_data_len = orig_len; + pj_lock_release(ssock->write_mutex); + return PJ_EPENDING; + } + + /* Copy the data and set its properties into the send data */ + pj_ioqueue_op_key_init(&wdata->key, sizeof(pj_ioqueue_op_key_t)); + wdata->key.user_data = wdata; + wdata->app_key = send_key; + wdata->record_len = needed_len; + wdata->data_len = len; + wdata->plain_data_len = orig_len; + wdata->flags = flags; + io_read(ssock, &ssock->circ_buf_output, (pj_uint8_t *)&wdata->data, len); + + /* Ticket #1573: Don't hold mutex while calling PJLIB socket send(). */ + pj_lock_release(ssock->write_mutex); + + /* Send it */ +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_send(ssock, &wdata->key, wdata->data.content, &len, flags); +#else + if (ssock->param.sock_type == pj_SOCK_STREAM()) { + status = pj_activesock_send(ssock->asock, &wdata->key, wdata->data.content, &len, flags); + } else { + status = pj_activesock_sendto(ssock->asock, &wdata->key, wdata->data.content, &len, flags, + (pj_sockaddr_t *)&ssock->rem_addr, ssock->addr_len); + } +#endif + + if (status != PJ_EPENDING) { + /* When the sending is not pending, remove the wdata from send + * pending list. + */ + pj_lock_acquire(ssock->write_mutex); + free_send_data(ssock, wdata); + pj_lock_release(ssock->write_mutex); + } + + return status; +} + +#if 0 +/* Just for testing send buffer alloc/free */ +#include +pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool) +{ + enum { MAX_CHUNK_NUM = 20 }; + unsigned chunk_size, chunk_cnt, i; + write_data_t *wdata[MAX_CHUNK_NUM] = {0}; + pj_time_val now; + pj_ssl_sock_t *ssock = NULL; + pj_ssl_sock_param param; + pj_status_t status; + + pj_gettimeofday(&now); + pj_srand((unsigned)now.sec); + + pj_ssl_sock_param_default(¶m); + status = pj_ssl_sock_create(pool, ¶m, &ssock); + if (status != PJ_SUCCESS) { + return status; + } + + if (ssock->send_buf.max_len == 0) { + ssock->send_buf.buf = (char*) + pj_pool_alloc(ssock->pool, + ssock->param.send_buffer_size); + ssock->send_buf.max_len = ssock->param.send_buffer_size; + ssock->send_buf.start = ssock->send_buf.buf; + ssock->send_buf.len = 0; + } + + chunk_size = ssock->param.send_buffer_size / MAX_CHUNK_NUM / 2; + chunk_cnt = 0; + for (i = 0; i < MAX_CHUNK_NUM; i++) { + wdata[i] = alloc_send_data(ssock, pj_rand() % chunk_size + 321); + if (wdata[i]) + chunk_cnt++; + else + break; + } + + while (chunk_cnt) { + i = pj_rand() % MAX_CHUNK_NUM; + if (wdata[i]) { + free_send_data(ssock, wdata[i]); + wdata[i] = NULL; + chunk_cnt--; + } + } + + if (ssock->send_buf.len != 0) + status = PJ_EBUG; + + pj_ssl_sock_close(ssock); + return status; +} +#endif + +static void on_timer(pj_timer_heap_t *th, struct pj_timer_entry *te) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)te->user_data; + int timer_id = te->id; + + te->id = TIMER_NONE; + + PJ_UNUSED_ARG(th); + + switch (timer_id) { + case TIMER_HANDSHAKE_TIMEOUT: + PJ_LOG(1, (ssock->pool->obj_name, "SSL timeout after %d.%ds", ssock->param.timeout.sec, + ssock->param.timeout.msec)); + + on_handshake_complete(ssock, PJ_ETIMEDOUT); + break; + case TIMER_CLOSE: + pj_ssl_sock_close(ssock); + break; + default: + pj_assert(!"Unknown timer"); + break; + } +} + +static void ssl_on_destroy(void *arg) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)arg; + + ssl_destroy(ssock); + + if (ssock->circ_buf_input_mutex) { + pj_lock_destroy(ssock->circ_buf_input_mutex); + ssock->circ_buf_input_mutex = NULL; + } + + if (ssock->circ_buf_output_mutex) { + pj_lock_destroy(ssock->circ_buf_output_mutex); + ssock->circ_buf_output_mutex = NULL; + ssock->write_mutex = NULL; + } + + /* Secure release pool, i.e: all memory blocks will be zeroed first */ + pj_pool_secure_release(&ssock->info_pool); + pj_pool_secure_release(&ssock->pool); +} + +/* + ******************************************************************* + * Network callbacks. + ******************************************************************* + */ + +/* + * Get the offset of pointer to read-buffer of SSL socket from read-buffer + * of active socket. Note that both SSL socket and active socket employ + * different but correlated read-buffers (as much as async_cnt for each), + * and to make it easier/faster to find corresponding SSL socket's read-buffer + * from known active socket's read-buffer, the pointer of corresponding + * SSL socket's read-buffer is stored right after the end of active socket's + * read-buffer. + */ +#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \ + (read_data_t **)((pj_int8_t *)(asock_rbuf) + ssock->param.read_buffer_size) + +static pj_bool_t ssock_on_data_read(pj_ssl_sock_t *ssock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + if (status != PJ_SUCCESS) + goto on_error; + + if (data && size > 0) { + pj_status_t status_; + + /* Consume the whole data */ + if (ssock->circ_buf_input_mutex) + pj_lock_acquire(ssock->circ_buf_input_mutex); + status_ = io_write(ssock, &ssock->circ_buf_input, data, size); + if (ssock->circ_buf_input_mutex) + pj_lock_release(ssock->circ_buf_input_mutex); + if (status_ != PJ_SUCCESS) { + status = status_; + goto on_error; + } + } + + /* Check if SSL handshake hasn't finished yet */ + if (ssock->ssl_state == SSL_STATE_HANDSHAKING) { + pj_bool_t ret = PJ_TRUE; + + if (status == PJ_SUCCESS) + status = ssl_do_handshake(ssock); + + /* Not pending is either success or failed */ + if (status != PJ_EPENDING) + ret = on_handshake_complete(ssock, status); + + return ret; + } + + /* See if there is any decrypted data for the application */ + if (ssock->read_started) { + do { + read_data_t *buf = *(OFFSET_OF_READ_DATA_PTR(ssock, data)); + void *data_ = (pj_int8_t *)buf->data + buf->len; + int size_ = (int)(ssock->read_size - buf->len); + pj_status_t status_; + + status_ = ssl_read(ssock, data_, &size_); + + if (size_ > 0 || status != PJ_SUCCESS) { + if (ssock->param.cb.on_data_read) { + pj_bool_t ret; + pj_size_t remainder_ = 0; + + if (size_ > 0) + buf->len += size_; + + if (status != PJ_SUCCESS) { + ssock->ssl_state = SSL_STATE_ERROR; + } + + ret = (*ssock->param.cb.on_data_read)(ssock, buf->data, buf->len, status, &remainder_); + if (!ret) { + /* We've been destroyed */ + return PJ_FALSE; + } + + /* Application may have left some data to be consumed + * later. + */ + buf->len = remainder_; + } + + /* Active socket signalled connection closed/error, this has + * been signalled to the application along with any remaining + * buffer. So, let's just reset SSL socket now. + */ + if (status != PJ_SUCCESS) { + ssl_reset_sock_state(ssock); + return PJ_FALSE; + } + + } else if (status_ == PJ_SUCCESS) { + break; + } else if (status_ == PJ_EEOF) { + status = ssl_do_handshake(ssock); + if (status == PJ_SUCCESS) { + /* Renegotiation completed */ + + /* Update certificates */ + ssl_update_certs_info(ssock); + + // Ticket #1573: Don't hold mutex while calling + // PJLIB socket send(). + // pj_lock_acquire(ssock->write_mutex); + status = flush_delayed_send(ssock); + // pj_lock_release(ssock->write_mutex); + + /* If flushing is ongoing, treat it as success */ + if (status == PJ_EBUSY) + status = PJ_SUCCESS; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Failed to flush delayed send")); + goto on_error; + } + } else if (status != PJ_EPENDING) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Renegotiation failed")); + goto on_error; + } + + break; + } else { + /* Error */ + status = status_; + goto on_error; + } + + } while (1); + } + + return PJ_TRUE; + +on_error: + if (ssock->ssl_state == SSL_STATE_HANDSHAKING) + return on_handshake_complete(ssock, status); + + if (ssock->read_started && ssock->param.cb.on_data_read) { + pj_bool_t ret; + ret = (*ssock->param.cb.on_data_read)(ssock, NULL, 0, status, remainder); + if (!ret) { + /* We've been destroyed */ + return PJ_FALSE; + } + } + + ssl_reset_sock_state(ssock); + return PJ_FALSE; +} + +static pj_bool_t ssock_on_data_sent(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + write_data_t *wdata = (write_data_t *)send_key->user_data; + pj_ioqueue_op_key_t *app_key = wdata->app_key; + pj_ssize_t sent_len; + + sent_len = (sent > 0) ? wdata->plain_data_len : sent; + + /* Update write buffer state */ + pj_lock_acquire(ssock->write_mutex); + free_send_data(ssock, wdata); + pj_lock_release(ssock->write_mutex); + wdata = NULL; + + if (ssock->ssl_state == SSL_STATE_HANDSHAKING) { + /* Initial handshaking */ + pj_status_t status; + + status = ssl_do_handshake(ssock); + /* Not pending is either success or failed */ + if (status != PJ_EPENDING) + return on_handshake_complete(ssock, status); + + } else if (send_key != &ssock->handshake_op_key) { + /* Some data has been sent, notify application */ + if (ssock->param.cb.on_data_sent) { + pj_bool_t ret; + ret = (*ssock->param.cb.on_data_sent)(ssock, app_key, sent_len); + if (!ret) { + /* We've been destroyed */ + return PJ_FALSE; + } + } + } else { + /* SSL re-negotiation is on-progress, just do nothing */ + } + + /* Send buffer has been updated, let's try to send any pending data */ + if (ssock->send_buf_pending.data_len) { + pj_status_t status; + status = flush_circ_buf_output(ssock, ssock->send_buf_pending.app_key, ssock->send_buf_pending.plain_data_len, + ssock->send_buf_pending.flags); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + ssock->send_buf_pending.data_len = 0; + } + } + + return PJ_TRUE; +} + +static pj_status_t get_localaddr(pj_ssl_sock_t *ssock, pj_sockaddr_t *addr, int *namelen) +{ + PJ_UNUSED_ARG(addr); + PJ_UNUSED_ARG(namelen); + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + return network_get_localaddr(ssock, &ssock->local_addr, &ssock->addr_len); +#else + return pj_sock_getsockname(ssock->sock, &ssock->local_addr, &ssock->addr_len); +#endif +} + +static pj_bool_t ssock_on_accept_complete(pj_ssl_sock_t *ssock_parent, pj_sock_t newsock, void *newconn, + const pj_sockaddr_t *src_addr, int src_addr_len, pj_status_t accept_status) +{ + pj_ssl_sock_t *ssock; +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + pj_activesock_cb asock_cb; + pj_activesock_cfg asock_cfg; +#endif + unsigned i; + pj_status_t status; + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + PJ_UNUSED_ARG(newconn); +#endif + + if (accept_status != PJ_SUCCESS) { + if (ssock_parent->param.cb.on_accept_complete2) { + (*ssock_parent->param.cb.on_accept_complete2)(ssock_parent, NULL, src_addr, src_addr_len, accept_status); + } + return PJ_TRUE; + } + + /* Create new SSL socket instance */ + status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->newsock_param, &ssock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Set parent and add ref count (avoid parent destroy during handshake) */ + ssock->parent = ssock_parent; + if (ssock->parent->param.grp_lock) + pj_grp_lock_add_ref(ssock->parent->param.grp_lock); + + /* Update new SSL socket attributes */ + ssock->sock = newsock; + ssock->is_server = PJ_TRUE; + if (ssock_parent->cert) { + status = pj_ssl_sock_set_certificate(ssock, ssock->pool, ssock_parent->cert); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Set local address */ + ssock->addr_len = src_addr_len; + pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr); + + /* Set remote address */ + pj_sockaddr_cp(&ssock->rem_addr, src_addr); + + /* Create SSL context */ + status = ssl_create(ssock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Set peer name */ + ssl_set_peer_name(ssock); + + /* Prepare read buffer */ + ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool, ssock->param.async_cnt, sizeof(void *)); + if (!ssock->asock_rbuf) { + status = PJ_ENOMEM; + goto on_return; + } + + for (i = 0; i < ssock->param.async_cnt; ++i) { + ssock->asock_rbuf[i] = + (void *)pj_pool_alloc(ssock->pool, ssock->param.read_buffer_size + sizeof(read_data_t *)); + if (!ssock->asock_rbuf[i]) { + status = PJ_ENOMEM; + goto on_return; + } + } + + /* If listener socket has group lock, automatically create group lock + * for the new socket. + */ + if (ssock_parent->param.grp_lock) { + pj_grp_lock_t *glock; + + status = pj_grp_lock_create(ssock->pool, NULL, &glock); + if (status != PJ_SUCCESS) + goto on_return; + + pj_grp_lock_add_ref(glock); + ssock->param.grp_lock = glock; + pj_grp_lock_add_handler(ssock->param.grp_lock, ssock->pool, ssock, ssl_on_destroy); + } + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_setup_connection(ssock, newconn); + if (status != PJ_SUCCESS) + goto on_return; + +#else + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, &ssock->param.qos_params, 1, ssock->pool->obj_name, + NULL); + if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) + goto on_return; + + /* Apply socket options, if specified */ + if (ssock->param.sockopt_params.cnt) { + status = pj_sock_setsockopt_params(ssock->sock, &ssock->param.sockopt_params); + if (status != PJ_SUCCESS && !ssock->param.sockopt_ignore_error) + goto on_return; + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = ssock->param.grp_lock; + asock_cfg.async_cnt = ssock->param.async_cnt; + asock_cfg.concurrency = ssock->param.concurrency; + asock_cfg.whole_data = PJ_TRUE; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = asock_on_data_read; + asock_cb.on_data_sent = asock_on_data_sent; + + status = pj_activesock_create(ssock->pool, ssock->sock, ssock->param.sock_type, &asock_cfg, ssock->param.ioqueue, + &asock_cb, ssock, &ssock->asock); + + if (status != PJ_SUCCESS) + goto on_return; + + /* Start read */ + status = pj_activesock_start_read2(ssock->asock, ssock->pool, (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, PJ_IOQUEUE_ALWAYS_ASYNC); + if (status != PJ_SUCCESS) + goto on_return; +#endif + + /* Update local address */ + status = get_localaddr(ssock, &ssock->local_addr, &ssock->addr_len); + if (status != PJ_SUCCESS) { + /* This fails on few envs, e.g: win IOCP, just tolerate this and + * use parent local address instead. + */ + pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr); + } + + /* Prepare write/send state */ + pj_assert(ssock->send_buf.max_len == 0); + ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool, ssock->param.send_buffer_size); + if (!ssock->send_buf.buf) + return PJ_ENOMEM; + + ssock->send_buf.max_len = ssock->param.send_buffer_size; + ssock->send_buf.start = ssock->send_buf.buf; + ssock->send_buf.len = 0; + + /* Start handshake timer */ + if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) { + pj_assert(ssock->timer.id == TIMER_NONE); + status = pj_timer_heap_schedule_w_grp_lock(ssock->param.timer_heap, &ssock->timer, &ssock->param.timeout, + TIMER_HANDSHAKE_TIMEOUT, ssock->param.grp_lock); + if (status != PJ_SUCCESS) { + ssock->timer.id = TIMER_NONE; + status = PJ_SUCCESS; + } + } + + /* Start SSL handshake */ + ssock->ssl_state = SSL_STATE_HANDSHAKING; + ssl_set_state(ssock, PJ_TRUE); + status = ssl_do_handshake(ssock); + +on_return: + if (ssock && status != PJ_EPENDING) { + on_handshake_complete(ssock, status); + } + + /* Must return PJ_TRUE whatever happened, as we must continue listening */ + return PJ_TRUE; +} + +static pj_bool_t ssock_on_connect_complete(pj_ssl_sock_t *ssock, pj_status_t status) +{ + unsigned i; + + if (status != PJ_SUCCESS) + goto on_return; + + /* Update local address */ + ssock->addr_len = sizeof(pj_sockaddr); + status = get_localaddr(ssock, &ssock->local_addr, &ssock->addr_len); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create SSL context */ + status = ssl_create(ssock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Prepare read buffer */ + ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool, ssock->param.async_cnt, sizeof(void *)); + if (!ssock->asock_rbuf) + return PJ_ENOMEM; + + for (i = 0; i < ssock->param.async_cnt; ++i) { + ssock->asock_rbuf[i] = + (void *)pj_pool_alloc(ssock->pool, ssock->param.read_buffer_size + sizeof(read_data_t *)); + if (!ssock->asock_rbuf[i]) + return PJ_ENOMEM; + } + + /* Start read */ +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_start_read(ssock, ssock->param.async_cnt, (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, 0); +#else + status = pj_activesock_start_read2(ssock->asock, ssock->pool, (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, PJ_IOQUEUE_ALWAYS_ASYNC); +#endif + if (status != PJ_SUCCESS) + goto on_return; + + /* Prepare write/send state */ + pj_assert(ssock->send_buf.max_len == 0); + ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool, ssock->param.send_buffer_size); + if (!ssock->send_buf.buf) + return PJ_ENOMEM; + + ssock->send_buf.max_len = ssock->param.send_buffer_size; + ssock->send_buf.start = ssock->send_buf.buf; + ssock->send_buf.len = 0; + + /* Set peer name */ + ssl_set_peer_name(ssock); + + /* Start SSL handshake */ + ssock->ssl_state = SSL_STATE_HANDSHAKING; + ssl_set_state(ssock, PJ_FALSE); + + status = ssl_do_handshake(ssock); + if (status != PJ_EPENDING) + goto on_return; + + return PJ_TRUE; + +on_return: + return on_handshake_complete(ssock, status); +} + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK +static pj_bool_t asock_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_data_read(ssock, data, size, status, remainder); +} + +static pj_bool_t asock_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_data_sent(ssock, send_key, sent); +} + +static pj_bool_t asock_on_accept_complete2(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_accept_complete(ssock, newsock, NULL, src_addr, src_addr_len, status); +} + +static pj_bool_t asock_on_connect_complete(pj_activesock_t *asock, pj_status_t status) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_connect_complete(ssock, status); +} +#endif + +/* + ******************************************************************* + * API + ******************************************************************* + */ + +/* Get available ciphers. */ +PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[], unsigned *cipher_num) +{ + unsigned i; + + PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL); + + ssl_ciphers_populate(); + + if (ssl_cipher_num == 0) { + *cipher_num = 0; + return PJ_ENOTFOUND; + } + + *cipher_num = PJ_MIN(*cipher_num, ssl_cipher_num); + + for (i = 0; i < *cipher_num; ++i) + ciphers[i] = ssl_ciphers[i].id; + + return PJ_SUCCESS; +} + +/* Get cipher name string */ +PJ_DEF(const char *) pj_ssl_cipher_name(pj_ssl_cipher cipher) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_cipher_num; ++i) { + if (cipher == ssl_ciphers[i].id) + return ssl_ciphers[i].name; + } + + return NULL; +} + +/* Get cipher identifier */ +PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_cipher_num; ++i) { + if (!pj_ansi_stricmp(ssl_ciphers[i].name, cipher_name)) + return ssl_ciphers[i].id; + } + + return PJ_TLS_UNKNOWN_CIPHER; +} + +/* Check if the specified cipher is supported by SSL/TLS backend. */ +PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_cipher_num; ++i) { + if (cipher == ssl_ciphers[i].id) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* Get available curves. */ +PJ_DEF(pj_status_t) pj_ssl_curve_get_availables(pj_ssl_curve curves[], unsigned *curve_num) +{ + unsigned i; + + PJ_ASSERT_RETURN(curves && curve_num, PJ_EINVAL); + + ssl_ciphers_populate(); + + if (ssl_curves_num == 0) { + *curve_num = 0; + return PJ_ENOTFOUND; + } + + *curve_num = PJ_MIN(*curve_num, ssl_curves_num); + + for (i = 0; i < *curve_num; ++i) + curves[i] = ssl_curves[i].id; + + return PJ_SUCCESS; +} + +/* Get curve name string. */ +PJ_DEF(const char *) pj_ssl_curve_name(pj_ssl_curve curve) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_curves_num; ++i) { + if (curve == ssl_curves[i].id) + return ssl_curves[i].name; + } + + return NULL; +} + +/* Get curve ID from curve name string. */ +PJ_DEF(pj_ssl_curve) pj_ssl_curve_id(const char *curve_name) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_curves_num; ++i) { + if (ssl_curves[i].name && !pj_ansi_stricmp(ssl_curves[i].name, curve_name)) { + return ssl_curves[i].id; + } + } + + return PJ_TLS_UNKNOWN_CURVE; +} + +/* Check if the specified curve is supported by SSL/TLS backend. */ +PJ_DEF(pj_bool_t) pj_ssl_curve_is_supported(pj_ssl_curve curve) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_curves_num; ++i) { + if (curve == ssl_curves[i].id) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* + * Create SSL socket instance. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool, const pj_ssl_sock_param *param, pj_ssl_sock_t **p_ssock) +{ + pj_ssl_sock_t *ssock; + pj_status_t status; + pj_pool_t *info_pool; + + PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL); + PJ_ASSERT_RETURN(param->sock_type == pj_SOCK_STREAM(), PJ_ENOTSUP); + + info_pool = pj_pool_create(pool->factory, "ssl_chain%p", 512, 512, NULL); + pool = pj_pool_create(pool->factory, "ssl%p", 512, 512, NULL); + + /* Create secure socket */ + ssock = ssl_alloc(pool); + if (!ssock) + return PJ_ENOMEM; + ssock->pool = pool; + ssock->info_pool = info_pool; + ssock->sock = PJ_INVALID_SOCKET; + ssock->ssl_state = SSL_STATE_NULL; + ssock->circ_buf_input.owner = ssock; + ssock->circ_buf_output.owner = ssock; + pj_list_init(&ssock->write_pending); + pj_list_init(&ssock->write_pending_empty); + pj_list_init(&ssock->send_pending); + pj_timer_entry_init(&ssock->timer, 0, ssock, &on_timer); + pj_ioqueue_op_key_init(&ssock->handshake_op_key, sizeof(pj_ioqueue_op_key_t)); + pj_ioqueue_op_key_init(&ssock->shutdown_op_key, sizeof(pj_ioqueue_op_key_t)); + + /* Create secure socket mutex */ + status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &ssock->circ_buf_output_mutex); + ssock->write_mutex = ssock->circ_buf_output_mutex; + if (status != PJ_SUCCESS) + return status; + + /* Create input circular buffer mutex */ + status = pj_lock_create_simple_mutex(pool, pool->obj_name, &ssock->circ_buf_input_mutex); + if (status != PJ_SUCCESS) + return status; + + /* Init secure socket param */ + pj_ssl_sock_param_copy(pool, &ssock->param, param); + + if (ssock->param.grp_lock) { + pj_grp_lock_add_ref(ssock->param.grp_lock); + pj_grp_lock_add_handler(ssock->param.grp_lock, pool, ssock, ssl_on_destroy); + } + + ssock->param.read_buffer_size = ((ssock->param.read_buffer_size + 7) >> 3) << 3; + if (!ssock->param.timer_heap) { + PJ_LOG(3, (ssock->pool->obj_name, "Warning: timer heap is not " + "available. It is recommended to supply one to avoid " + "a race condition if more than one worker threads " + "are used.")); + } + + /* Finally */ + *p_ssock = ssock; + + return PJ_SUCCESS; +} + +/* + * Close the secure socket. This will unregister the socket from the + * ioqueue and ultimately close the socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock) +{ + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + if (!ssock->pool || ssock->is_closing) + return PJ_SUCCESS; + + ssock->is_closing = PJ_TRUE; + + if (ssock->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); + ssock->timer.id = TIMER_NONE; + } + + ssl_reset_sock_state(ssock); + + /* Wipe out cert & key buffer. */ + if (ssock->cert) { + pj_ssl_cert_wipe_keys(ssock->cert); + ssock->cert = NULL; + } + + if (ssock->param.grp_lock) { + pj_grp_lock_dec_ref(ssock->param.grp_lock); + } else { + ssl_on_destroy(ssock); + } + + return PJ_SUCCESS; +} + +/* + * Associate arbitrary data with the secure socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock, void *user_data) +{ + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + ssock->param.user_data = user_data; + return PJ_SUCCESS; +} + +/* + * Retrieve the user data previously associated with this secure + * socket. + */ +PJ_DEF(void *) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock) +{ + PJ_ASSERT_RETURN(ssock, NULL); + + return ssock->param.user_data; +} + +/* + * Retrieve the local address and port used by specified SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_get_info(pj_ssl_sock_t *ssock, pj_ssl_sock_info *info) +{ + pj_bzero(info, sizeof(*info)); + + /* Established flag */ + info->established = (ssock->ssl_state == SSL_STATE_ESTABLISHED); + + /* Protocol */ + info->proto = ssock->param.proto; + + /* Local address */ + pj_sockaddr_cp(&info->local_addr, &ssock->local_addr); + + /* Certificates info */ + info->local_cert_info = &ssock->local_cert_info; + info->remote_cert_info = &ssock->remote_cert_info; + + /* Remote address */ + if (pj_sockaddr_has_addr(&ssock->rem_addr)) + pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr); + + if (info->established) { + info->cipher = ssl_get_cipher(ssock); + + /* Verification status */ + info->verify_status = ssock->verify_status; + } + + /* Last known SSL error code */ + info->last_native_err = ssock->last_err; + + /* Group lock */ + info->grp_lock = ssock->param.grp_lock; + + return PJ_SUCCESS; +} + +/* + * Starts read operation on this secure socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_read(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + void **readbuf; + unsigned i; + + PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + readbuf = (void **)pj_pool_calloc(pool, ssock->param.async_cnt, sizeof(void *)); + if (!readbuf) + return PJ_ENOMEM; + + for (i = 0; i < ssock->param.async_cnt; ++i) { + readbuf[i] = pj_pool_alloc(pool, buff_size); + if (!readbuf[i]) + return PJ_ENOMEM; + } + + return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags); +} + +/* + * Same as #pj_ssl_sock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_read2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], pj_uint32_t flags) +{ + unsigned i; + + PJ_ASSERT_RETURN(ssock && pool && buff_size && readbuf, PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + /* Create SSL socket read buffer */ + ssock->ssock_rbuf = (read_data_t *)pj_pool_calloc(pool, ssock->param.async_cnt, sizeof(read_data_t)); + if (!ssock->ssock_rbuf) + return PJ_ENOMEM; + + /* Store SSL socket read buffer pointer in the activesock read buffer */ + for (i = 0; i < ssock->param.async_cnt; ++i) { + read_data_t **p_ssock_rbuf = OFFSET_OF_READ_DATA_PTR(ssock, ssock->asock_rbuf[i]); + + ssock->ssock_rbuf[i].data = readbuf[i]; + ssock->ssock_rbuf[i].len = 0; + + *p_ssock_rbuf = &ssock->ssock_rbuf[i]; + } + + ssock->read_size = buff_size; + ssock->read_started = PJ_TRUE; + ssock->read_flags = flags; + + for (i = 0; i < ssock->param.async_cnt; ++i) { + if (ssock->asock_rbuf[i]) { + pj_size_t remainder = 0; + ssock_on_data_read(ssock, ssock->asock_rbuf[i], 0, PJ_SUCCESS, &remainder); + } + } + + return PJ_SUCCESS; +} + +/* + * Same as pj_ssl_sock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_recvfrom(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(buff_size); + PJ_UNUSED_ARG(flags); + + return PJ_ENOTSUP; +} + +/* + * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_recvfrom2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(buff_size); + PJ_UNUSED_ARG(readbuf); + PJ_UNUSED_ARG(flags); + + return PJ_ENOTSUP; +} + +/* Write plain data to SSL and flush the buffer. */ +static pj_status_t ssl_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t size, + unsigned flags) +{ + pj_status_t status; + int nwritten; + + /* Write the plain data to SSL, after SSL encrypts it, the buffer will + * contain the secured data to be sent via socket. Note that re- + * negotitation may be on progress, so sending data should be delayed + * until re-negotiation is completed. + */ + pj_lock_acquire(ssock->write_mutex); + /* Don't write to SSL if send buffer is full and some data is in + * write buffer already, just return PJ_ENOMEM. + */ + if (ssock->send_buf_pending.data_len) { + pj_lock_release(ssock->write_mutex); + return PJ_ENOMEM; + } + status = ssl_write(ssock, data, size, &nwritten); + pj_lock_release(ssock->write_mutex); + + if (status == PJ_SUCCESS && nwritten == size) { + /* All data written, flush write buffer to network socket */ + status = flush_circ_buf_output(ssock, send_key, size, flags); + } else if (status == PJ_EEOF) { + /* Re-negotiation is on progress, flush re-negotiation data */ + status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + /* Just return PJ_EBUSY when re-negotiation is on progress */ + status = PJ_EBUSY; + } + } + + return status; +} + +/* Flush delayed data sending in the write pending list. */ +static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock) +{ + /* Check for another ongoing flush */ + if (ssock->flushing_write_pend) + return PJ_EBUSY; + + pj_lock_acquire(ssock->write_mutex); + + /* Again, check for another ongoing flush */ + if (ssock->flushing_write_pend) { + pj_lock_release(ssock->write_mutex); + return PJ_EBUSY; + } + + /* Set ongoing flush flag */ + ssock->flushing_write_pend = PJ_TRUE; + + while (!pj_list_empty(&ssock->write_pending)) { + write_data_t *wp; + pj_status_t status; + + wp = ssock->write_pending.next; + + /* Ticket #1573: Don't hold mutex while calling socket send. */ + pj_lock_release(ssock->write_mutex); + + status = ssl_send(ssock, &wp->key, wp->data.ptr, wp->plain_data_len, wp->flags); + if (status != PJ_SUCCESS) { + /* Reset ongoing flush flag first. */ + ssock->flushing_write_pend = PJ_FALSE; + return status; + } + + pj_lock_acquire(ssock->write_mutex); + pj_list_erase(wp); + pj_list_push_back(&ssock->write_pending_empty, wp); + } + + /* Reset ongoing flush flag */ + ssock->flushing_write_pend = PJ_FALSE; + + pj_lock_release(ssock->write_mutex); + + return PJ_SUCCESS; +} + +/* Sending is delayed, push back the sending data into pending list. */ +static pj_status_t delay_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t size, + unsigned flags) +{ + write_data_t *wp; + + pj_lock_acquire(ssock->write_mutex); + + /* Init write pending instance */ + if (!pj_list_empty(&ssock->write_pending_empty)) { + wp = ssock->write_pending_empty.next; + pj_list_erase(wp); + } else { + wp = PJ_POOL_ZALLOC_T(ssock->pool, write_data_t); + } + + wp->app_key = send_key; + wp->plain_data_len = size; + wp->data.ptr = data; + wp->flags = flags; + + pj_list_push_back(&ssock->write_pending, wp); + + pj_lock_release(ssock->write_mutex); + + /* Must return PJ_EPENDING */ + return PJ_EPENDING; +} + +/** + * Send data using the socket. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(ssock && data && size && (*size > 0), PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + // Ticket #1573: Don't hold mutex while calling PJLIB socket send(). + // pj_lock_acquire(ssock->write_mutex); + + /* Flush delayed send first. Sending data might be delayed when + * re-negotiation is on-progress. + */ + status = flush_delayed_send(ssock); + if (status == PJ_EBUSY) { + /* Re-negotiation or flushing is on progress, delay sending */ + status = delay_send(ssock, send_key, data, *size, flags); + goto on_return; + } else if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Write data to SSL */ + status = ssl_send(ssock, send_key, data, *size, flags); + if (status == PJ_EBUSY) { + /* Re-negotiation is on progress, delay sending */ + status = delay_send(ssock, send_key, data, *size, flags); + } + +on_return: + // pj_lock_release(ssock->write_mutex); + return status; +} + +/** + * Send datagram using the socket. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_sendto(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(send_key); + PJ_UNUSED_ARG(data); + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(flags); + PJ_UNUSED_ARG(addr); + PJ_UNUSED_ARG(addr_len); + + return PJ_ENOTSUP; +} + +/** + * Starts asynchronous socket accept() operations on this secure socket. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_accept(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, int addr_len) +{ + return pj_ssl_sock_start_accept2(ssock, pool, localaddr, addr_len, &ssock->param); +} + +/** + * Same as #pj_ssl_sock_start_accept(), but application provides parameter + * for new accepted secure sockets. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_accept2(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, int addr_len, + const pj_ssl_sock_param *newsock_param) +{ + pj_status_t status; +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + pj_activesock_cb asock_cb; + pj_activesock_cfg asock_cfg; +#endif + + PJ_ASSERT_RETURN(ssock && pool && localaddr && addr_len, PJ_EINVAL); + + /* Verify new socket parameters */ + if (newsock_param->grp_lock != ssock->param.grp_lock || newsock_param->sock_af != ssock->param.sock_af || + newsock_param->sock_type != ssock->param.sock_type) { + return PJ_EINVAL; + } + + ssock->is_server = PJ_TRUE; + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_start_accept(ssock, pool, localaddr, addr_len, newsock_param); + if (status != PJ_SUCCESS) + goto on_error; +#else + /* Create socket */ + status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, &ssock->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply SO_REUSEADDR */ + if (ssock->param.reuse_addr) { + int enabled = 1; + status = pj_sock_setsockopt(ssock->sock, pj_SOL_SOCKET(), pj_SO_REUSEADDR(), &enabled, sizeof(enabled)); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ssock->pool->obj_name, status, "Warning: error applying SO_REUSEADDR")); + } + } + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, &ssock->param.qos_params, 2, ssock->pool->obj_name, + NULL); + if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) + goto on_error; + + /* Apply socket options, if specified */ + if (ssock->param.sockopt_params.cnt) { + status = pj_sock_setsockopt_params(ssock->sock, &ssock->param.sockopt_params); + + if (status != PJ_SUCCESS && !ssock->param.sockopt_ignore_error) + goto on_error; + } + + /* Bind socket */ + status = pj_sock_bind(ssock->sock, localaddr, addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start listening to the address */ + status = pj_sock_listen(ssock->sock, PJ_SOMAXCONN); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.async_cnt = ssock->param.async_cnt; + asock_cfg.concurrency = ssock->param.concurrency; + asock_cfg.whole_data = PJ_FALSE; + asock_cfg.grp_lock = ssock->param.grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + // asock_cb.on_accept_complete = asock_on_accept_complete; + asock_cb.on_accept_complete2 = asock_on_accept_complete2; + + status = pj_activesock_create(pool, ssock->sock, ssock->param.sock_type, &asock_cfg, ssock->param.ioqueue, + &asock_cb, ssock, &ssock->asock); + + if (status != PJ_SUCCESS) + goto on_error; + + /* Start accepting */ + pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param); + ssock->newsock_param.grp_lock = NULL; + status = pj_activesock_start_accept(ssock->asock, pool); + if (status != PJ_SUCCESS) + goto on_error; + + /* Update local address */ + ssock->addr_len = addr_len; + status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, &ssock->addr_len); + if (status != PJ_SUCCESS) + pj_sockaddr_cp(&ssock->local_addr, localaddr); +#endif + + return PJ_SUCCESS; + +on_error: + ssl_reset_sock_state(ssock); + return status; +} + +/** + * Starts asynchronous socket connect() operation. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, + const pj_sockaddr_t *remaddr, int addr_len) +{ + pj_ssl_start_connect_param param; + param.pool = pool; + param.localaddr = localaddr; + param.local_port_range = 0; + param.remaddr = remaddr; + param.addr_len = addr_len; + + return pj_ssl_sock_start_connect2(ssock, ¶m); +} + +PJ_DEF(pj_status_t) pj_ssl_sock_start_connect2(pj_ssl_sock_t *ssock, pj_ssl_start_connect_param *connect_param) +{ + pj_status_t status; +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_start_connect(ssock, connect_param); + if (status != PJ_EPENDING) + goto on_error; +#else + pj_activesock_cb asock_cb; + pj_activesock_cfg asock_cfg; + + pj_pool_t *pool = connect_param->pool; + const pj_sockaddr_t *localaddr = connect_param->localaddr; + pj_uint16_t port_range = connect_param->local_port_range; + const pj_sockaddr_t *remaddr = connect_param->remaddr; + int addr_len = connect_param->addr_len; + + PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, PJ_EINVAL); + + /* Create socket */ + status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, &ssock->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, &ssock->param.qos_params, 2, ssock->pool->obj_name, + NULL); + if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) + goto on_error; + + /* Apply socket options, if specified */ + if (ssock->param.sockopt_params.cnt) { + status = pj_sock_setsockopt_params(ssock->sock, &ssock->param.sockopt_params); + + if (status != PJ_SUCCESS && !ssock->param.sockopt_ignore_error) + goto on_error; + } + + /* Bind socket */ + if (port_range) { + pj_uint16_t max_bind_retry = MAX_BIND_RETRY; + if (port_range && port_range < max_bind_retry) { + max_bind_retry = port_range; + } + status = pj_sock_bind_random(ssock->sock, localaddr, port_range, max_bind_retry); + } else { + status = pj_sock_bind(ssock->sock, localaddr, addr_len); + } + + if (status != PJ_SUCCESS) + goto on_error; + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.async_cnt = ssock->param.async_cnt; + asock_cfg.concurrency = ssock->param.concurrency; + asock_cfg.whole_data = PJ_TRUE; + asock_cfg.grp_lock = ssock->param.grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_connect_complete = asock_on_connect_complete; + asock_cb.on_data_read = asock_on_data_read; + asock_cb.on_data_sent = asock_on_data_sent; + + status = pj_activesock_create(pool, ssock->sock, ssock->param.sock_type, &asock_cfg, ssock->param.ioqueue, + &asock_cb, ssock, &ssock->asock); + + if (status != PJ_SUCCESS) + goto on_error; + + /* Save remote address */ + pj_sockaddr_cp(&ssock->rem_addr, remaddr); + + status = pj_activesock_start_connect(ssock->asock, pool, remaddr, addr_len); + + if (status == PJ_SUCCESS) + asock_on_connect_complete(ssock->asock, PJ_SUCCESS); + else if (status != PJ_EPENDING) + goto on_error; + + /* Update local address */ + ssock->addr_len = addr_len; + status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, &ssock->addr_len); + /* Note that we may not get an IP address here. This can + * happen for example on Windows, where getsockname() + * would return 0.0.0.0 if socket has just started the + * async connect. In this case, just leave the local + * address with 0.0.0.0 for now; it will be updated + * once the socket is established. + */ + +#endif + + /* Start timer */ + if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) { + pj_assert(ssock->timer.id == TIMER_NONE); + status = pj_timer_heap_schedule_w_grp_lock(ssock->param.timer_heap, &ssock->timer, &ssock->param.timeout, + TIMER_HANDSHAKE_TIMEOUT, ssock->param.grp_lock); + if (status != PJ_SUCCESS) { + ssock->timer.id = TIMER_NONE; + status = PJ_SUCCESS; + } + } + + /* Update SSL state */ + ssock->is_server = PJ_FALSE; + + return PJ_EPENDING; + +on_error: + ssl_reset_sock_state(ssock); + return status; +} + +PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + status = ssl_renegotiate(ssock); + if (status == PJ_SUCCESS) { + status = ssl_do_handshake(ssock); + } + + return status; +} + +static void wipe_buf(pj_str_t *buf) +{ + volatile char *p = buf->ptr; + pj_ssize_t len = buf->slen; + while (len--) + *p++ = 0; + buf->slen = 0; +} + +PJ_DEF(void) pj_ssl_cert_wipe_keys(pj_ssl_cert_t *cert) +{ + if (cert) { + wipe_buf(&cert->CA_file); + wipe_buf(&cert->CA_path); + wipe_buf(&cert->cert_file); + wipe_buf(&cert->privkey_file); + wipe_buf(&cert->privkey_pass); + wipe_buf(&cert->CA_buf); + wipe_buf(&cert->cert_buf); + wipe_buf(&cert->privkey_buf); + } +} + +/* Load credentials from files. */ +PJ_DEF(pj_status_t) +pj_ssl_cert_load_from_files(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *cert_file, + const pj_str_t *privkey_file, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert) +{ + return pj_ssl_cert_load_from_files2(pool, CA_file, NULL, cert_file, privkey_file, privkey_pass, p_cert); +} + +PJ_DEF(pj_status_t) +pj_ssl_cert_load_from_files2(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *CA_path, + const pj_str_t *cert_file, const pj_str_t *privkey_file, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert) +{ + pj_ssl_cert_t *cert; + + PJ_ASSERT_RETURN(pool && (CA_file || CA_path) && cert_file && privkey_file, PJ_EINVAL); + + cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + if (CA_file) { + pj_strdup_with_null(pool, &cert->CA_file, CA_file); + } + if (CA_path) { + pj_strdup_with_null(pool, &cert->CA_path, CA_path); + } + pj_strdup_with_null(pool, &cert->cert_file, cert_file); + pj_strdup_with_null(pool, &cert->privkey_file, privkey_file); + pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass); + + *p_cert = cert; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_ssl_cert_load_from_buffer(pj_pool_t *pool, const pj_ssl_cert_buffer *CA_buf, const pj_ssl_cert_buffer *cert_buf, + const pj_ssl_cert_buffer *privkey_buf, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert) +{ + pj_ssl_cert_t *cert; + + PJ_ASSERT_RETURN(pool && CA_buf && cert_buf && privkey_buf, PJ_EINVAL); + + cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + pj_strdup(pool, &cert->CA_buf, CA_buf); + pj_strdup(pool, &cert->cert_buf, cert_buf); + pj_strdup(pool, &cert->privkey_buf, privkey_buf); + pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass); + + *p_cert = cert; + + return PJ_SUCCESS; +} + +/* Set SSL socket credentials. */ +PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_ssl_cert_t *cert) +{ + pj_ssl_cert_t *cert_; + + PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL); + + cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + pj_memcpy(cert_, cert, sizeof(pj_ssl_cert_t)); + pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file); + pj_strdup_with_null(pool, &cert_->CA_path, &cert->CA_path); + pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file); + pj_strdup_with_null(pool, &cert_->privkey_file, &cert->privkey_file); + pj_strdup_with_null(pool, &cert_->privkey_pass, &cert->privkey_pass); + + pj_strdup(pool, &cert_->CA_buf, &cert->CA_buf); + pj_strdup(pool, &cert_->cert_buf, &cert->cert_buf); + pj_strdup(pool, &cert_->privkey_buf, &cert->privkey_buf); + + ssock->cert = cert_; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h new file mode 100755 index 000000000..781a97d02 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2019-2019 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SSL_SOCK_IMP_COMMON_H__ +#define __SSL_SOCK_IMP_COMMON_H__ + +#include +#include + +/* + * SSL/TLS state enumeration. + */ +enum ssl_state { SSL_STATE_NULL, SSL_STATE_HANDSHAKING, SSL_STATE_ESTABLISHED, SSL_STATE_ERROR }; + +/* + * Internal timer types. + */ +enum timer_id { TIMER_NONE, TIMER_HANDSHAKE_TIMEOUT, TIMER_CLOSE }; + +/* + * Structure of SSL socket read buffer. + */ +typedef struct read_data_t { + void *data; + pj_size_t len; +} read_data_t; + +/* + * Structure of SSL socket write data. + */ +typedef struct write_data_t { + PJ_DECL_LIST_MEMBER(struct write_data_t); + pj_ioqueue_op_key_t key; + pj_size_t record_len; + pj_ioqueue_op_key_t *app_key; + pj_size_t plain_data_len; + pj_size_t data_len; + unsigned flags; + union { + char content[1]; + const char *ptr; + } data; +} write_data_t; + +/* + * Structure of SSL socket write buffer (circular buffer). + */ +typedef struct send_buf_t { + char *buf; + pj_size_t max_len; + char *start; + pj_size_t len; +} send_buf_t; + +/* Circular buffer object */ +typedef struct circ_buf_t { + pj_ssl_sock_t *owner; /* owner of the circular buffer */ + pj_size_t cap; /* maximum number of elements (must be power of 2) */ + pj_size_t readp; /* index of oldest element */ + pj_size_t writep; /* index at which to write new element */ + pj_size_t size; /* number of elements */ + pj_uint8_t *buf; /* data buffer */ + pj_pool_t *pool; /* where new allocations will take place */ +} circ_buf_t; + +/* + * Secure socket structure definition. + */ +struct pj_ssl_sock_t { + pj_pool_t *pool; + pj_pool_t *info_pool; /* this is for certificate chain + * information allocation. Don't use for + * other purposes. */ + pj_ssl_sock_t *parent; + pj_ssl_sock_param param; + pj_ssl_sock_param newsock_param; + pj_ssl_cert_t *cert; + + pj_ssl_cert_info local_cert_info; + pj_ssl_cert_info remote_cert_info; + + pj_bool_t is_server; + enum ssl_state ssl_state; + pj_ioqueue_op_key_t handshake_op_key; + pj_ioqueue_op_key_t shutdown_op_key; + pj_timer_entry timer; + pj_status_t verify_status; + pj_status_t handshake_status; + + pj_bool_t is_closing; + unsigned long last_err; + + pj_sock_t sock; + pj_activesock_t *asock; + + pj_sockaddr local_addr; + pj_sockaddr rem_addr; + int addr_len; + + pj_bool_t read_started; + pj_size_t read_size; + pj_uint32_t read_flags; + void **asock_rbuf; + read_data_t *ssock_rbuf; + + write_data_t write_pending; /* list of pending write to ssl */ + write_data_t write_pending_empty; /* cache for write_pending */ + pj_bool_t flushing_write_pend; /* flag of flushing is ongoing*/ + send_buf_t send_buf; + write_data_t send_buf_pending; /* send buffer is full but some + * data is queuing in wbio. */ + write_data_t send_pending; /* list of pending write to network */ + pj_lock_t *write_mutex; /* protect write BIO and send_buf */ + + circ_buf_t circ_buf_input; + pj_lock_t *circ_buf_input_mutex; + + circ_buf_t circ_buf_output; + pj_lock_t *circ_buf_output_mutex; +}; + +/* + * Certificate/credential structure definition. + */ +struct pj_ssl_cert_t { + pj_str_t CA_file; + pj_str_t CA_path; + pj_str_t cert_file; + pj_str_t privkey_file; + pj_str_t privkey_pass; + + /* Certificate buffer. */ + pj_ssl_cert_buffer CA_buf; + pj_ssl_cert_buffer cert_buf; + pj_ssl_cert_buffer privkey_buf; +}; + +/* ssl available ciphers */ +static unsigned ssl_cipher_num; +static struct ssl_ciphers_t { + pj_ssl_cipher id; + const char *name; +} ssl_ciphers[PJ_SSL_SOCK_MAX_CIPHERS]; + +/* ssl available curves */ +static unsigned ssl_curves_num; +static struct ssl_curves_t { + pj_ssl_curve id; + const char *name; +} ssl_curves[PJ_SSL_SOCK_MAX_CURVES]; + +/* + ******************************************************************* + * I/O functions. + ******************************************************************* + */ + +static pj_bool_t io_empty(pj_ssl_sock_t *ssock, circ_buf_t *cb); +static pj_size_t io_size(pj_ssl_sock_t *ssock, circ_buf_t *cb); +static void io_read(pj_ssl_sock_t *ssock, circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len); +static pj_status_t io_write(pj_ssl_sock_t *ssock, circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len); + +static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len); +static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata); +static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock); + +#ifdef SSL_SOCK_IMP_USE_CIRC_BUF +/* + ******************************************************************* + * Circular buffer functions. + ******************************************************************* + */ + +static pj_status_t circ_init(pj_pool_factory *factory, circ_buf_t *cb, pj_size_t cap); +static void circ_deinit(circ_buf_t *cb); +static pj_bool_t circ_empty(const circ_buf_t *cb); +static pj_size_t circ_size(const circ_buf_t *cb); +static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len); +static pj_status_t circ_write(circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len); + +inline static pj_bool_t io_empty(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + return circ_empty(cb); +} +inline static pj_size_t io_size(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + return circ_size(cb); +} +inline static void io_reset(pj_ssl_sock_t *ssock, circ_buf_t *cb) {} +inline static void io_read(pj_ssl_sock_t *ssock, circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) +{ + return circ_read(cb, dst, len); +} +inline static pj_status_t io_write(pj_ssl_sock_t *ssock, circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) +{ + return circ_write(cb, src, len); +} + +#endif + +/* + ******************************************************************* + * The below functions must be implemented by SSL backend. + ******************************************************************* + */ + +/* Allocate SSL backend struct */ +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool); +/* Create and initialize new SSL context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock); +/* Destroy SSL context and instance */ +static void ssl_destroy(pj_ssl_sock_t *ssock); +/* Reset SSL socket state */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock); + +/* Ciphers and certs */ +static void ssl_ciphers_populate(); +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock); +static void ssl_update_certs_info(pj_ssl_sock_t *ssock); + +/* SSL session functions */ +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server); +static void ssl_set_peer_name(pj_ssl_sock_t *ssock); + +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock); +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock); +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size); +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, pj_ssize_t size, int *nwritten); + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + +static void ssl_close_sockets(pj_ssl_sock_t *ssock); + +static pj_status_t network_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags); +static pj_status_t network_start_read(pj_ssl_sock_t *ssock, unsigned async_count, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); +static pj_status_t network_start_accept(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, + int addr_len, const pj_ssl_sock_param *newsock_param); +static pj_status_t network_start_connect(pj_ssl_sock_t *ssock, pj_ssl_start_connect_param *connect_param); +static pj_status_t network_setup_connection(pj_ssl_sock_t *ssock, void *connection); +static pj_status_t network_get_localaddr(pj_ssl_sock_t *ssock, pj_sockaddr_t *addr, int *namelen); + +#endif + +#endif /* __SSL_SOCK_IMP_COMMON_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c new file mode 100755 index 000000000..c99398c75 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c @@ -0,0 +1,2371 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK is enabled and when the backend is + * OpenSSL. + */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL) + +#include "ssl_sock_imp_common.c" + +#define THIS_FILE "ssl_sock_ossl.c" + +/* + * Include OpenSSL headers + */ +#include +#include +#include +#include +#include +#if !defined(OPENSSL_NO_DH) +#include +#endif + +#include +#include +#include + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif + +/* Specify whether server supports session reuse using session ID. */ +#define SERVER_SUPPORT_SESSION_REUSE 1 + +/* Specify whether server should disable session tickets. */ +#define SERVER_DISABLE_SESSION_TICKETS 1 + +/* Each server application must set its own session id context, + * which is used to distinguish the contexts and is stored in + * exported sessions. + */ +#ifndef SERVER_SESSION_ID_CONTEXT +#define SERVER_SESSION_ID_CONTEXT 999 +#endif + +/* Server session timeout duration. Default is 300 sec. */ +#define SERVER_SESSION_TIMEOUT 300 + +#if defined(LIBRESSL_VERSION_NUMBER) +#define USING_LIBRESSL 1 +#else +#define USING_LIBRESSL 0 +#endif + +#if defined(OPENSSL_IS_BORINGSSL) +#define USING_BORINGSSL 1 + +#define TLSEXT_nid_unknown 0x1000000 + +#undef SSL_CTRL_SET_ECDH_AUTO +#define SSL_CTRL_SET_ECDH_AUTO 94 + +#else +#define USING_BORINGSSL 0 +#endif + +#if !USING_LIBRESSL && !defined(OPENSSL_NO_EC) && OPENSSL_VERSION_NUMBER >= 0x1000200fL + +#include + +static const unsigned nid_cid_map[] = { + NID_sect163k1, /* sect163k1 (1) */ + NID_sect163r1, /* sect163r1 (2) */ + NID_sect163r2, /* sect163r2 (3) */ + NID_sect193r1, /* sect193r1 (4) */ + NID_sect193r2, /* sect193r2 (5) */ + NID_sect233k1, /* sect233k1 (6) */ + NID_sect233r1, /* sect233r1 (7) */ + NID_sect239k1, /* sect239k1 (8) */ + NID_sect283k1, /* sect283k1 (9) */ + NID_sect283r1, /* sect283r1 (10) */ + NID_sect409k1, /* sect409k1 (11) */ + NID_sect409r1, /* sect409r1 (12) */ + NID_sect571k1, /* sect571k1 (13) */ + NID_sect571r1, /* sect571r1 (14) */ + NID_secp160k1, /* secp160k1 (15) */ + NID_secp160r1, /* secp160r1 (16) */ + NID_secp160r2, /* secp160r2 (17) */ + NID_secp192k1, /* secp192k1 (18) */ + NID_X9_62_prime192v1, /* secp192r1 (19) */ + NID_secp224k1, /* secp224k1 (20) */ + NID_secp224r1, /* secp224r1 (21) */ + NID_secp256k1, /* secp256k1 (22) */ + NID_X9_62_prime256v1, /* secp256r1 (23) */ + NID_secp384r1, /* secp384r1 (24) */ + NID_secp521r1, /* secp521r1 (25) */ + NID_brainpoolP256r1, /* brainpoolP256r1 (26) */ + NID_brainpoolP384r1, /* brainpoolP384r1 (27) */ + NID_brainpoolP512r1 /* brainpoolP512r1 (28) */ +}; + +static unsigned get_cid_from_nid(unsigned nid) +{ + unsigned i, cid = 0; + for (i = 0; i < PJ_ARRAY_SIZE(nid_cid_map); ++i) { + if (nid == nid_cid_map[i]) { + cid = i + 1; + break; + } + } + return cid; +} + +static unsigned get_nid_from_cid(unsigned cid) +{ + if ((cid == 0) || (cid > PJ_ARRAY_SIZE(nid_cid_map))) + return 0; + + return nid_cid_map[cid - 1]; +} + +#endif + +static void update_certs_info(pj_ssl_sock_t *ssock, X509_STORE_CTX *ctx, pj_ssl_cert_info *local_cert_info, + pj_ssl_cert_info *remote_cert_info, pj_bool_t is_verify); + +#if !USING_LIBRESSL && OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OPENSSL_NO_SSL2 /* seems to be removed in 1.1.0 */ +#define M_ASN1_STRING_data(x) ASN1_STRING_get0_data(x) +#define M_ASN1_STRING_length(x) ASN1_STRING_length(x) +#if defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT >= 0x10100000L +#define X509_get_notBefore(x) X509_get0_notBefore(x) +#define X509_get_notAfter(x) X509_get0_notAfter(x) +#endif +#elif !USING_LIBRESSL +#define SSL_CIPHER_get_id(c) (c)->id +#define SSL_set_session(ssl, s) (ssl)->session = (s) +#define X509_STORE_CTX_get0_cert(ctx) ((ctx)->cert) +#endif + +#ifdef _MSC_VER +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#pragma comment(lib, "libcrypto") +#pragma comment(lib, "libssl") +#pragma comment(lib, "crypt32") +#else +#pragma comment(lib, "libeay32") +#pragma comment(lib, "ssleay32") +#endif +#endif + +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 +#ifdef _MSC_VER +#define strerror_r(err, buf, len) strerror_s(buf, len, err) +#else +#define strerror_r(err, buf, len) pj_ansi_strncpy(buf, strerror(err), len) +#endif +#endif + +/* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated + * since MacOSX 10.7). + */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS == 1 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +/* + * Secure socket structure definition. + */ +typedef struct ossl_sock_t { + pj_ssl_sock_t base; + + SSL_CTX *ossl_ctx; + pj_bool_t own_ctx; + SSL *ossl_ssl; + BIO *ossl_rbio; + BIO *ossl_wbio; +} ossl_sock_t; + +/** + * Mapping from OpenSSL error codes to pjlib error space. + */ + +#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 6) + +#define PJ_SSL_ERRNO_SPACE_SIZE PJ_ERRNO_SPACE_SIZE + +/* Expected maximum value of reason component in OpenSSL error code */ +#define MAX_OSSL_ERR_REASON 1200 + +static char *SSLErrorString(int err) +{ + switch (err) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + default: + return "SSL_ERROR_UNKNOWN"; + } +} + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ERROR_LOG(msg, err, ssock) \ + { \ + char err_str[PJ_ERR_MSG_SIZE]; \ + char buf[PJ_INET6_ADDRSTRLEN + 10]; \ + ERR_error_string_n(err, err_str, sizeof(err_str)); \ + PJ_LOG(2, \ + ("SSL", "%s (%s): Level: %d err: <%lu> <%s> len: %d peer: %s", msg, action, level, err, err_str, len, \ + (ssock && pj_sockaddr_has_addr(&ssock->rem_addr) \ + ? pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3) \ + : "???"))); \ + } +#else +#define ERROR_LOG(msg, err, ssock) \ + { \ + char buf[PJ_INET6_ADDRSTRLEN + 10]; \ + PJ_LOG(2, ("SSL", \ + "%s (%s): Level: %d err: <%lu> <%s-%s-%s> len: %d " \ + "peer: %s", \ + msg, action, level, err, (ERR_lib_error_string(err) ? ERR_lib_error_string(err) : "???"), \ + (ERR_func_error_string(err) ? ERR_func_error_string(err) : "???"), \ + (ERR_reason_error_string(err) ? ERR_reason_error_string(err) : "???"), len, \ + (ssock && pj_sockaddr_has_addr(&ssock->rem_addr) \ + ? pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3) \ + : "???"))); \ + } +#endif + +static void SSLLogErrors(char *action, int ret, int ssl_err, int len, pj_ssl_sock_t *ssock) +{ + char *ssl_err_str = SSLErrorString(ssl_err); + + if (!action) { + action = "UNKNOWN"; + } + + switch (ssl_err) { + case SSL_ERROR_SYSCALL: { + unsigned long err2 = ERR_get_error(); + if (err2) { + int level = 0; + while (err2) { + ERROR_LOG("SSL_ERROR_SYSCALL", err2, ssock); + level++; + err2 = ERR_get_error(); + } + } else if (ret == 0) { + /* An EOF was observed that violates the protocol */ + + /* The TLS/SSL handshake was not successful but was shut down + * controlled and by the specifications of the TLS/SSL protocol. + */ + } else if (ret == -1) { + /* BIO error - look for more info in errno... */ + char errStr[250] = ""; + strerror_r(errno, errStr, sizeof(errStr)); + /* for now - continue logging these if they occur.... */ + PJ_LOG(4, ("SSL", + "BIO error, SSL_ERROR_SYSCALL (%s): " + "errno: <%d> <%s> len: %d", + action, errno, errStr, len)); + } else { + /* ret!=0 & ret!=-1 & nothing on error stack - is this valid??? */ + PJ_LOG(2, ("SSL", "SSL_ERROR_SYSCALL (%s) ret: %d len: %d", action, ret, len)); + } + break; + } + case SSL_ERROR_SSL: { + unsigned long err2 = ERR_get_error(); + int level = 0; + + while (err2) { + unsigned long next_err; + + ERROR_LOG("SSL_ERROR_SSL", err2, ssock); + level++; + + do { + next_err = ERR_get_error(); + } while (next_err == err2); + err2 = next_err; + } + break; + } + default: + PJ_LOG(2, ("SSL", "%lu [%s] (%s) ret: %d len: %d", ssl_err, ssl_err_str, action, ret, len)); + break; + } +} + +static pj_status_t GET_STATUS_FROM_SSL_ERR(unsigned long err) +{ + pj_status_t status; + + /* OpenSSL error range is much wider than PJLIB errno space, so + * if it exceeds the space, only the error reason will be kept. + * Note that the last native error will be kept as is and can be + * retrieved via SSL socket info. + */ + status = ERR_GET_LIB(err) * MAX_OSSL_ERR_REASON + ERR_GET_REASON(err); + if (status > PJ_SSL_ERRNO_SPACE_SIZE) + status = ERR_GET_REASON(err); + + status += PJ_SSL_ERRNO_START; + return status; +} + +/* err contains ERR_get_error() status */ +static pj_status_t STATUS_FROM_SSL_ERR(char *action, pj_ssl_sock_t *ssock, unsigned long err) +{ + int level = 0; + int len = 0; // dummy + + ERROR_LOG("STATUS_FROM_SSL_ERR", err, ssock); + level++; + + /* General SSL error, dig more from OpenSSL error queue */ + if (err == SSL_ERROR_SSL) { + err = ERR_get_error(); + ERROR_LOG("STATUS_FROM_SSL_ERR", err, ssock); + } + + if (ssock) + ssock->last_err = err; + return GET_STATUS_FROM_SSL_ERR(err); +} + +/* err contains SSL_get_error() status */ +static pj_status_t STATUS_FROM_SSL_ERR2(char *action, pj_ssl_sock_t *ssock, int ret, int err, int len) +{ + unsigned long ssl_err = err; + + if (err == SSL_ERROR_SSL) { + ssl_err = ERR_peek_error(); + } + + /* Dig for more from OpenSSL error queue */ + SSLLogErrors(action, ret, err, len, ssock); + + if (ssock) + ssock->last_err = ssl_err; + return GET_STATUS_FROM_SSL_ERR(ssl_err); +} + +static pj_status_t GET_SSL_STATUS(pj_ssl_sock_t *ssock) +{ + return STATUS_FROM_SSL_ERR("status", ssock, ERR_get_error()); +} + +/* + * Get error string of OpenSSL. + */ +static pj_str_t ssl_strerror(pj_status_t status, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + unsigned long ssl_err = status; + + if (ssl_err) { + unsigned long l, r; + ssl_err -= PJ_SSL_ERRNO_START; + l = ssl_err / MAX_OSSL_ERR_REASON; + r = ssl_err % MAX_OSSL_ERR_REASON; +#if USING_BORINGSSL + ssl_err = ERR_PACK(l, r); +#else + ssl_err = ERR_PACK(l, 0, r); +#endif + } + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + { + const char *tmp = NULL; + tmp = ERR_reason_error_string(ssl_err); + if (tmp) { + pj_ansi_strncpy(buf, tmp, bufsize); + errstr = pj_str(buf); + return errstr; + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown OpenSSL error %lu", ssl_err); + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + return errstr; +} + +/* Additional ciphers recognized by SSL_set_cipher_list() + but not returned from SSL_get_ciphers(). + NOTE: ids are designed to not conflict with those from + SSL_get_cipher() which get masked to the lower 24 + bits before use. +*/ +static const struct ssl_ciphers_t ADDITIONAL_CIPHERS[] = {{0xFF000000, "DEFAULT"}, {0xFF000001, "@SECLEVEL=1"}, + {0xFF000002, "@SECLEVEL=2"}, {0xFF000003, "@SECLEVEL=3"}, + {0xFF000004, "@SECLEVEL=4"}, {0xFF000005, "@SECLEVEL=5"}}; +static const unsigned int ADDITIONAL_CIPHER_COUNT = sizeof(ADDITIONAL_CIPHERS) / sizeof(ADDITIONAL_CIPHERS[0]); + +/* + ******************************************************************* + * I/O functions. + ******************************************************************* + */ + +static pj_bool_t io_empty(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + PJ_UNUSED_ARG(cb); + + return !BIO_pending(ossock->ossl_wbio); +} + +static pj_size_t io_size(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + char *data; + + PJ_UNUSED_ARG(cb); + + return BIO_get_mem_data(ossock->ossl_wbio, &data); +} + +static void io_read(pj_ssl_sock_t *ssock, circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + char *data; + + PJ_UNUSED_ARG(cb); + + BIO_get_mem_data(ossock->ossl_wbio, &data); + pj_memcpy(dst, data, len); + + /* Reset write BIO */ + (void)BIO_reset(ossock->ossl_wbio); +} + +static pj_status_t io_write(pj_ssl_sock_t *ssock, circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int nwritten; + + nwritten = BIO_write(ossock->ossl_rbio, src, (int)len); + return (nwritten < (int)len) ? GET_SSL_STATUS(cb->owner) : PJ_SUCCESS; +} + +/* + ******************************************************************* + */ + +/* OpenSSL library initialization counter */ +static int openssl_init_count; + +/* OpenSSL application data index */ +static int sslsock_idx; + +#if defined(PJ_SSL_SOCK_OSSL_USE_THREAD_CB) && PJ_SSL_SOCK_OSSL_USE_THREAD_CB != 0 && \ + OPENSSL_VERSION_NUMBER < 0x10100000L + +/* Thread lock pool.*/ +static pj_caching_pool cp; +static pj_pool_t *lock_pool; + +/* OpenSSL locking list. */ +static pj_lock_t **ossl_locks; + +/* OpenSSL number locks. */ +static unsigned ossl_num_locks; + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 +static void ossl_set_thread_id(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long)pj_thread_get_os_handle(pj_thread_this())); +} + +#else + +static unsigned long ossl_thread_id(void) +{ + return ((unsigned long)pj_thread_get_os_handle(pj_thread_this())); +} +#endif + +static void ossl_lock(int mode, int id, const char *file, int line) +{ + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + + if (openssl_init_count == 0) + return; + + if (mode & CRYPTO_LOCK) { + if (ossl_locks[id]) { + // PJ_LOG(6, (THIS_FILE, "Lock File (%s) Line(%d)", file, line)); + pj_lock_acquire(ossl_locks[id]); + } + } else { + if (ossl_locks[id]) { + // PJ_LOG(6, (THIS_FILE, "Unlock File (%s) Line(%d)", file, line)); + pj_lock_release(ossl_locks[id]); + } + } +} + +static void release_thread_cb(void) +{ + unsigned i = 0; + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + CRYPTO_THREADID_set_callback(NULL); +#else + CRYPTO_set_id_callback(NULL); +#endif + CRYPTO_set_locking_callback(NULL); + + for (; i < ossl_num_locks; ++i) { + if (ossl_locks[i]) { + pj_lock_destroy(ossl_locks[i]); + ossl_locks[i] = NULL; + } + } + if (lock_pool) { + pj_pool_release(lock_pool); + lock_pool = NULL; + pj_caching_pool_destroy(&cp); + } + ossl_locks = NULL; + ossl_num_locks = 0; +} + +static pj_status_t init_ossl_lock() +{ + pj_status_t status = PJ_SUCCESS; + + pj_caching_pool_init(&cp, NULL, 0); + + lock_pool = pj_pool_create(&cp.factory, "ossl-lock", 64, 64, NULL); + + if (!lock_pool) { + status = PJ_ENOMEM; + PJ_PERROR(1, (THIS_FILE, status, "Fail creating OpenSSL lock pool")); + pj_caching_pool_destroy(&cp); + return status; + } + + ossl_num_locks = CRYPTO_num_locks(); + ossl_locks = (pj_lock_t **)pj_pool_calloc(lock_pool, ossl_num_locks, sizeof(pj_lock_t *)); + + if (ossl_locks) { + unsigned i = 0; + for (; (i < ossl_num_locks) && (status == PJ_SUCCESS); ++i) { + status = pj_lock_create_simple_mutex(lock_pool, "ossl_lock%p", &ossl_locks[i]); + } + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (THIS_FILE, status, "Fail creating mutex for OpenSSL lock")); + release_thread_cb(); + return status; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + CRYPTO_THREADID_set_callback(ossl_set_thread_id); +#else + CRYPTO_set_id_callback(ossl_thread_id); +#endif + CRYPTO_set_locking_callback(ossl_lock); + status = pj_atexit(&release_thread_cb); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (THIS_FILE, status, + "Warning! Unable to set OpenSSL " + "lock thread callback unrelease method.")); + } + } else { + status = PJ_ENOMEM; + PJ_PERROR(1, (THIS_FILE, status, "Fail creating OpenSSL locks")); + release_thread_cb(); + } + return status; +} + +#endif + +/* Initialize OpenSSL */ +static pj_status_t init_openssl(void) +{ + pj_status_t status; + + if (openssl_init_count) + return PJ_SUCCESS; + + openssl_init_count = 1; + + PJ_LOG(4, (THIS_FILE, "OpenSSL version : %x", OPENSSL_VERSION_NUMBER)); + /* Register error subsystem */ + status = pj_register_strerror(PJ_SSL_ERRNO_START, PJ_SSL_ERRNO_SPACE_SIZE, &ssl_strerror); + pj_assert(status == PJ_SUCCESS); + + /* Init OpenSSL lib */ +#if USING_LIBRESSL || OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_library_init(); + SSL_load_error_strings(); +#else + OPENSSL_init_ssl(0, NULL); +#endif +#if OPENSSL_VERSION_NUMBER < 0x009080ffL + /* This is now synonym of SSL_library_init() */ + OpenSSL_add_all_algorithms(); +#endif + + /* Init available ciphers */ + if (ssl_cipher_num == 0 || ssl_curves_num == 0) { + SSL_METHOD *meth = NULL; + SSL_CTX *ctx; + SSL *ssl; + STACK_OF(SSL_CIPHER) * sk_cipher; + SSL_SESSION *ssl_sess; + unsigned i, n; + int nid; + const char *cname; + +#if (USING_LIBRESSL && LIBRESSL_VERSION_NUMBER < 0x2020100fL) || OPENSSL_VERSION_NUMBER < 0x10100000L + + meth = (SSL_METHOD *)SSLv23_server_method(); + if (!meth) + meth = (SSL_METHOD *)TLSv1_server_method(); +#ifndef OPENSSL_NO_SSL3_METHOD + if (!meth) + meth = (SSL_METHOD *)SSLv3_server_method(); +#endif +#ifndef OPENSSL_NO_SSL2 + if (!meth) + meth = (SSL_METHOD *)SSLv2_server_method(); +#endif + +#else + /* Specific version methods are deprecated in 1.1.0 */ + meth = (SSL_METHOD *)TLS_method(); +#endif + + pj_assert(meth); + + ctx = SSL_CTX_new(meth); + SSL_CTX_set_cipher_list(ctx, "ALL:COMPLEMENTOFALL"); + + ssl = SSL_new(ctx); + + sk_cipher = SSL_get_ciphers(ssl); + + n = sk_SSL_CIPHER_num(sk_cipher); + if (n > PJ_ARRAY_SIZE(ssl_ciphers) - ADDITIONAL_CIPHER_COUNT) + n = PJ_ARRAY_SIZE(ssl_ciphers) - ADDITIONAL_CIPHER_COUNT; + + for (i = 0; i < n; ++i) { + const SSL_CIPHER *c; + c = sk_SSL_CIPHER_value(sk_cipher, i); + ssl_ciphers[i].id = (pj_ssl_cipher)(pj_uint32_t)SSL_CIPHER_get_id(c) & 0x00FFFFFF; + ssl_ciphers[i].name = SSL_CIPHER_get_name(c); + } + + /* Add cipher aliases not returned from SSL_get_ciphers() */ + for (i = 0; i < ADDITIONAL_CIPHER_COUNT; ++i) { + ssl_ciphers[n++] = ADDITIONAL_CIPHERS[i]; + } + ssl_cipher_num = n; + +#if USING_BORINGSSL + ssl_sess = SSL_SESSION_new(ctx); +#else + ssl_sess = SSL_SESSION_new(); +#endif + SSL_set_session(ssl, ssl_sess); + +#if !USING_LIBRESSL && !defined(OPENSSL_NO_EC) && OPENSSL_VERSION_NUMBER >= 0x1000200fL +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + ssl_curves_num = EC_get_builtin_curves(NULL, 0); +#else + +#if USING_BORINGSSL + ssl_curves_num = SSL_get_curve_id(ssl); +#else + ssl_curves_num = SSL_get_shared_curve(ssl, -1); +#endif + + if (ssl_curves_num > PJ_ARRAY_SIZE(ssl_curves)) + ssl_curves_num = PJ_ARRAY_SIZE(ssl_curves); +#endif + + if (ssl_curves_num > 0) { +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + EC_builtin_curve *curves = NULL; + + curves = OPENSSL_malloc((int)sizeof(*curves) * ssl_curves_num); + if (!EC_get_builtin_curves(curves, ssl_curves_num)) { + OPENSSL_free(curves); + curves = NULL; + ssl_curves_num = 0; + } + + n = ssl_curves_num; + ssl_curves_num = 0; + + for (i = 0; i < n; i++) { + nid = curves[i].nid; + + if (0 != get_cid_from_nid(nid)) { + cname = OBJ_nid2sn(nid); + + if (!cname) + cname = OBJ_nid2sn(nid); + + if (cname) { + ssl_curves[ssl_curves_num].id = get_cid_from_nid(nid); + ssl_curves[ssl_curves_num].name = cname; + + ssl_curves_num++; + + if (ssl_curves_num >= PJ_SSL_SOCK_MAX_CURVES) + break; + } + } + } + + if (curves) + OPENSSL_free(curves); +#else + for (i = 0; i < ssl_curves_num; i++) { +#if USING_BORINGSSL + nid = SSL_get_curve_id(ssl); +#else + nid = SSL_get_shared_curve(ssl, i); +#endif + + if (nid & TLSEXT_nid_unknown) { + cname = "curve unknown"; + nid &= 0xFFFF; + } else { + cname = EC_curve_nid2nist(nid); + if (!cname) + cname = OBJ_nid2sn(nid); + } + + ssl_curves[i].id = get_cid_from_nid(nid); + ssl_curves[i].name = cname; + } +#endif + } +#else + PJ_UNUSED_ARG(nid); + PJ_UNUSED_ARG(cname); + ssl_curves_num = 0; +#endif + + SSL_free(ssl); + + /* On OpenSSL 1.1.1, omitting SSL_SESSION_free() will cause + * memory leak (e.g: as reported by Address Sanitizer). But using + * SSL_SESSION_free() may cause crash (due to double free?) on 1.0.x. + * As OpenSSL docs specifies to not calling SSL_SESSION_free() after + * SSL_free(), perhaps it is safer to obey this, the leak amount seems + * to be relatively small (<500 bytes) and should occur once only in + * the library lifetime. +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + SSL_SESSION_free(ssl_sess); +#endif + */ + + SSL_CTX_free(ctx); + } + + /* Create OpenSSL application data index for SSL socket */ + sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL); + if (sslsock_idx == -1) { + status = STATUS_FROM_SSL_ERR2("Init", NULL, -1, ERR_get_error(), 0); + PJ_LOG(1, (THIS_FILE, "Fatal error: failed to get application data index for " + "SSL socket")); + return status; + } + +#if defined(PJ_SSL_SOCK_OSSL_USE_THREAD_CB) && PJ_SSL_SOCK_OSSL_USE_THREAD_CB != 0 && \ + OPENSSL_VERSION_NUMBER < 0x10100000L + + status = init_ossl_lock(); + if (status != PJ_SUCCESS) + return status; +#endif + + return status; +} + +/* Shutdown OpenSSL */ +static void shutdown_openssl(void) +{ + PJ_UNUSED_ARG(openssl_init_count); +} + +/* SSL password callback. */ +static int password_cb(char *buf, int num, int rwflag, void *user_data) +{ + pj_ssl_cert_t *cert = (pj_ssl_cert_t *)user_data; + + PJ_UNUSED_ARG(rwflag); + + if (num < cert->privkey_pass.slen) + return 0; + + pj_memcpy(buf, cert->privkey_pass.ptr, cert->privkey_pass.slen); + return (int)cert->privkey_pass.slen; +} + +/* SSL certificate verification result callback. + * Note that this callback seems to be always called from library worker + * thread, e.g: active socket on_read_complete callback, which should have + * already been equipped with race condition avoidance mechanism (should not + * be destroyed while callback is being invoked). + */ +static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + pj_ssl_sock_t *ssock = NULL; + SSL *ossl_ssl = NULL; + int err; + + /* Get SSL instance */ + ossl_ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + if (!ossl_ssl) { + PJ_LOG(1, (THIS_FILE, "SSL verification callback failed to get SSL instance")); + goto on_return; + } + + /* Get SSL socket instance */ + ssock = SSL_get_ex_data(ossl_ssl, sslsock_idx); + if (!ssock) { + /* SSL socket may have been destroyed */ + PJ_LOG(1, (THIS_FILE, + "SSL verification callback failed to get SSL socket " + "instance (sslsock_idx=%d).", + sslsock_idx)); + goto on_return; + } + + if (ssock->param.cb.on_verify_cb) { + update_certs_info(ssock, x509_ctx, &ssock->local_cert_info, &ssock->remote_cert_info, PJ_TRUE); + preverify_ok = (*ssock->param.cb.on_verify_cb)(ssock, ssock->is_server); + + goto on_return; + } + + /* Store verification status */ + err = X509_STORE_CTX_get_error(x509_ctx); + switch (err) { + case X509_V_OK: + break; + + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND; + break; + + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + break; + + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + break; + + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + ssock->verify_status |= PJ_SSL_CERT_ECRL_FAILURE; + break; + + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + break; + + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH; + break; + + case X509_V_ERR_CERT_REVOKED: + ssock->verify_status |= PJ_SSL_CERT_EREVOKED; + break; + + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_INVALID_CA: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_PURPOSE; + break; + + case X509_V_ERR_CERT_CHAIN_TOO_LONG: /* not really used */ + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + ssock->verify_status |= PJ_SSL_CERT_ECHAIN_TOO_LONG; + break; + + /* Unknown errors */ + case X509_V_ERR_OUT_OF_MEM: + default: + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + break; + } + + /* When verification is not requested just return ok here, however + * application can still get the verification status. + */ + if (PJ_FALSE == ssock->param.verify_peer) + preverify_ok = 1; + +on_return: + return preverify_ok; +} + +/* Setting SSL sock cipher list */ +static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock); +/* Setting SSL sock curves list */ +static pj_status_t set_curves_list(pj_ssl_sock_t *ssock); +/* Setting sigalgs list */ +static pj_status_t set_sigalgs(pj_ssl_sock_t *ssock); +/* Setting entropy for rng */ +static void set_entropy(pj_ssl_sock_t *ssock); + +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + return (pj_ssl_sock_t *)PJ_POOL_ZALLOC_T(pool, ossl_sock_t); +} + +#if !USING_BORINGSSL + +static int xname_cmp(const X509_NAME *const *a, const X509_NAME *const *b) +{ + return X509_NAME_cmp(*a, *b); +} + +#else + +static int xname_cmp(const X509_NAME **a, const X509_NAME **b) +{ + return X509_NAME_cmp(*a, *b); +} + +#endif + +#if !defined(OPENSSL_NO_DH) + +static void set_option(const pj_ssl_sock_t *ssock, SSL_CTX *ctx) +{ + unsigned long options = SSL_OP_CIPHER_SERVER_PREFERENCE | +#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L + SSL_OP_SINGLE_ECDH_USE | +#endif + SSL_OP_SINGLE_DH_USE; + options = SSL_CTX_set_options(ctx, options); + PJ_LOG(4, (ssock->pool->obj_name, "SSL DH " + "initialized, PFS cipher-suites enabled")); +} + +static void set_dh_use_option(BIO *bio, const pj_ssl_sock_t *ssock, const pj_str_t *pass, SSL_CTX *ctx) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + if (dh != NULL) { + if (SSL_CTX_set_tmp_dh(ctx, dh)) { + set_option(ssock, ctx); + } + DH_free(dh); + } + PJ_UNUSED_ARG(pass); +#else + OSSL_DECODER_CTX *dctx; + EVP_PKEY *dh_pkey = NULL; + const char *format = "PEM"; + const char *structure = NULL; + const char *keytype = NULL; + + dctx = OSSL_DECODER_CTX_new_for_pkey(&dh_pkey, format, structure, keytype, 0, NULL, NULL); + if (dctx != NULL) { + if (pass->slen) { + OSSL_DECODER_CTX_set_passphrase(dctx, (const unsigned char *)pass->ptr, pass->slen); + } + + if (OSSL_DECODER_from_bio(dctx, bio)) { + if (SSL_CTX_set0_tmp_dh_pkey(ctx, dh_pkey)) { + set_option(ssock, ctx); + } + } + OSSL_DECODER_CTX_free(dctx); + } +#endif +} + +#endif + +/* Initialize OpenSSL context for the ssock */ +static pj_status_t init_ossl_ctx(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + SSL_CTX *ctx = NULL; + SSL_METHOD *ssl_method = NULL; + pj_uint32_t ssl_opt = 0; + pj_ssl_cert_t *cert = ssock->cert; + int rc; + pj_status_t status; + + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) + ssock->param.proto = PJ_SSL_SOCK_PROTO_SSL23; + + /* Determine SSL method to use */ + /* Specific version methods are deprecated since 1.1.0 */ +#if (USING_LIBRESSL && LIBRESSL_VERSION_NUMBER < 0x2020100fL) || OPENSSL_VERSION_NUMBER < 0x10100000L + switch (ssock->param.proto) { + case PJ_SSL_SOCK_PROTO_TLS1: + ssl_method = (SSL_METHOD *)TLSv1_method(); + break; +#ifndef OPENSSL_NO_SSL2 + case PJ_SSL_SOCK_PROTO_SSL2: + ssl_method = (SSL_METHOD *)SSLv2_method(); + break; +#endif +#ifndef OPENSSL_NO_SSL3_METHOD + case PJ_SSL_SOCK_PROTO_SSL3: + ssl_method = (SSL_METHOD *)SSLv3_method(); +#endif + break; + } +#endif + + if (!ssl_method) { +#if (USING_LIBRESSL && LIBRESSL_VERSION_NUMBER < 0x2020100fL) || OPENSSL_VERSION_NUMBER < 0x10100000L + ssl_method = (SSL_METHOD *)SSLv23_method(); +#else + ssl_method = (SSL_METHOD *)TLS_method(); +#endif + +#ifdef SSL_OP_NO_SSLv2 + /** Check if SSLv2 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL2) == 0) ? SSL_OP_NO_SSLv2 : 0; +#endif + +#ifdef SSL_OP_NO_SSLv3 + /** Check if SSLv3 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) == 0) ? SSL_OP_NO_SSLv3 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1 + /** Check if TLSv1 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) == 0) ? SSL_OP_NO_TLSv1 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_1 + /** Check if TLSv1_1 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) == 0) ? SSL_OP_NO_TLSv1_1 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_2 + /** Check if TLSv1_2 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) == 0) ? SSL_OP_NO_TLSv1_2 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_3 + /** Check if TLSv1_3 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) == 0) ? SSL_OP_NO_TLSv1_3 : 0; +#endif + } + + ossock->ossl_ctx = ctx = SSL_CTX_new(ssl_method); + if (ctx == NULL) { + return GET_SSL_STATUS(ssock); + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (ssock->param.proto <= PJ_SSL_SOCK_PROTO_TLS1_1) { + /* TLS 1.0, TLS 1.1 no longer working at the default security + * level of 1 and instead requires security level 0. */ + SSL_CTX_set_security_level(ossock->ossl_ctx, 0); + } +#endif + + if (ssock->is_server) { + unsigned int sid_ctx = SERVER_SESSION_ID_CONTEXT; + +#if SERVER_DISABLE_SESSION_TICKETS + /* Disable session tickets for TLSv1.2 and below. */ + ssl_opt |= SSL_OP_NO_TICKET; +#ifdef SSL_CTX_set_num_tickets + /* Set the number of TLSv1.3 session tickets issued to 0. */ + SSL_CTX_set_num_tickets(ctx, 0); +#endif + +#endif + + SSL_CTX_set_timeout(ctx, SERVER_SESSION_TIMEOUT); + if (!SSL_CTX_set_session_id_context(ctx, (const unsigned char *)&sid_ctx, sizeof(sid_ctx))) { + PJ_LOG(1, (THIS_FILE, "Warning! Unable to set server session id " + "context. Session reuse will not work.")); + } + } + + if (ssl_opt) + SSL_CTX_set_options(ctx, ssl_opt); + + /* Set cipher list */ + status = set_cipher_list(ssock); + if (status != PJ_SUCCESS) { + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } + + /* Apply credentials */ + if (cert) { + /* Load CA list if one is specified. */ + if (cert->CA_file.slen || cert->CA_path.slen) { + + rc = SSL_CTX_load_verify_locations(ctx, cert->CA_file.slen == 0 ? NULL : cert->CA_file.ptr, + cert->CA_path.slen == 0 ? NULL : cert->CA_path.ptr); + + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + if (cert->CA_file.slen) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading CA list file '%s'", cert->CA_file.ptr)); + } + if (cert->CA_path.slen) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading CA path '%s'", cert->CA_path.ptr)); + } + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "CA certificates loaded from '%s%s%s'", cert->CA_file.ptr, + ((cert->CA_file.slen && cert->CA_path.slen) ? " + " : ""), cert->CA_path.ptr)); + } + } + + /* Set password callback */ + if (cert->privkey_pass.slen) { + SSL_CTX_set_default_passwd_cb(ctx, password_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx, cert); + } + + /* Load certificate if one is specified */ + if (cert->cert_file.slen) { + + /* Load certificate chain from file into ctx */ + rc = SSL_CTX_use_certificate_chain_file(ctx, cert->cert_file.ptr); + + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading certificate chain file '%s'", + cert->cert_file.ptr)); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Certificate chain loaded from '%s'", cert->cert_file.ptr)); + } + } + + /* Load private key if one is specified */ + if (cert->privkey_file.slen) { + /* Adds the first private key found in file to ctx */ + rc = SSL_CTX_use_PrivateKey_file(ctx, cert->privkey_file.ptr, SSL_FILETYPE_PEM); + + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR( + 1, (ssock->pool->obj_name, status, "Error adding private key from '%s'", cert->privkey_file.ptr)); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Private key loaded from '%s'", cert->privkey_file.ptr)); + } + +#if !defined(OPENSSL_NO_DH) + if (ssock->is_server) { + BIO *bio = BIO_new_file(cert->privkey_file.ptr, "r"); + if (bio != NULL) { + set_dh_use_option(bio, ssock, &cert->privkey_pass, ctx); + BIO_free(bio); + } + } +#endif + } + + /* Load from buffer. */ + if (cert->cert_buf.slen) { + BIO *cbio; + X509 *xcert = NULL; + + cbio = BIO_new_mem_buf((void *)cert->cert_buf.ptr, cert->cert_buf.slen); + if (cbio != NULL) { + xcert = PEM_read_bio_X509(cbio, NULL, 0, NULL); + if (xcert != NULL) { + rc = SSL_CTX_use_certificate(ctx, xcert); + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading chain certificate from buffer")); + X509_free(xcert); + BIO_free(cbio); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Certificate chain loaded from buffer")); + } + X509_free(xcert); + } + BIO_free(cbio); + } + } + + if (cert->CA_buf.slen) { + BIO *cbio = BIO_new_mem_buf((void *)cert->CA_buf.ptr, cert->CA_buf.slen); + X509_STORE *cts = SSL_CTX_get_cert_store(ctx); + + if (cbio && cts) { + STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL); + + if (inf != NULL) { + int i = 0, cnt = 0; + for (; i < sk_X509_INFO_num(inf); i++) { + X509_INFO *itmp = sk_X509_INFO_value(inf, i); + if (!itmp->x509) + continue; + + rc = X509_STORE_add_cert(cts, itmp->x509); + if (rc == 1) { + ++cnt; + } else { +#if PJ_LOG_MAX_LEVEL >= 4 + char buf[256]; + PJ_LOG(4, (ssock->pool->obj_name, "Error adding CA cert: %s", + X509_NAME_oneline(X509_get_subject_name(itmp->x509), buf, sizeof(buf)))); +#endif + } + } + PJ_LOG(4, (ssock->pool->obj_name, "CA certificates loaded from buffer (cnt=%d)", cnt)); + } + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free(cbio); + } + } + + if (cert->privkey_buf.slen) { + BIO *kbio; + EVP_PKEY *pkey = NULL; + + kbio = BIO_new_mem_buf((void *)cert->privkey_buf.ptr, cert->privkey_buf.slen); + if (kbio != NULL) { + pkey = PEM_read_bio_PrivateKey(kbio, NULL, &password_cb, cert); + if (pkey) { + rc = SSL_CTX_use_PrivateKey(ctx, pkey); + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error adding private key from buffer")); + EVP_PKEY_free(pkey); + BIO_free(kbio); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Private key loaded from buffer")); + } + EVP_PKEY_free(pkey); + } else { + PJ_LOG(1, (ssock->pool->obj_name, "Error reading private key from buffer")); + } + + if (ssock->is_server) { + set_dh_use_option(kbio, ssock, &cert->privkey_pass, ctx); + } + BIO_free(kbio); + } + } + } + + if (ssock->is_server) { + char *p = NULL; + + /* If certificate file name contains "_rsa.", let's check if there are + * ecc and dsa certificates too. + */ + if (cert && cert->cert_file.slen) { + const pj_str_t RSA = {"_rsa.", 5}; + p = pj_strstr(&cert->cert_file, &RSA); + if (p) + p++; /* Skip underscore */ + } + if (p) { + /* Certificate type string length must be exactly 3 */ + enum { CERT_TYPE_LEN = 3 }; + const char *cert_types[] = {"ecc", "dsa"}; + char *cf = cert->cert_file.ptr; + int i; + + /* Check and load ECC & DSA certificates & private keys */ + for (i = 0; i < PJ_ARRAY_SIZE(cert_types); ++i) { + int err; + + pj_memcpy(p, cert_types[i], CERT_TYPE_LEN); + if (!pj_file_exists(cf)) + continue; + + err = SSL_CTX_use_certificate_chain_file(ctx, cf); + if (err == 1) + err = SSL_CTX_use_PrivateKey_file(ctx, cf, SSL_FILETYPE_PEM); + if (err == 1) { + PJ_LOG(4, (ssock->pool->obj_name, "Additional certificate '%s' loaded.", cf)); + } else { + PJ_PERROR( + 1, (ssock->pool->obj_name, GET_SSL_STATUS(ssock), "Error loading certificate file '%s'", cf)); + ERR_clear_error(); + } + } + + /* Put back original name */ + pj_memcpy(p, "rsa", CERT_TYPE_LEN); + } + +#if USING_BORINGSSL + if (SSL_CTX_set_mode(ctx, SSL_CTRL_SET_ECDH_AUTO)) { +#else +#ifndef SSL_CTRL_SET_ECDH_AUTO +#define SSL_CTRL_SET_ECDH_AUTO 94 +#endif + + /* SSL_CTX_set_ecdh_auto(ctx,on) requires OpenSSL 1.0.2 which wraps: */ + if (SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, 1, NULL)) { +#endif + PJ_LOG(4, (ssock->pool->obj_name, "SSL ECDH initialized " + "(automatic), faster PFS ciphers enabled")); +#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L + } else { + /* enables AES-128 ciphers, to get AES-256 use NID_secp384r1 */ + EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh != NULL) { + if (SSL_CTX_set_tmp_ecdh(ctx, ecdh)) { + PJ_LOG(4, (ssock->pool->obj_name, "SSL ECDH initialized " + "(secp256r1), faster PFS cipher-suites enabled")); + } + EC_KEY_free(ecdh); + } +#endif + } + } else { + X509_STORE *pkix_validation_store = SSL_CTX_get_cert_store(ctx); + if (NULL != pkix_validation_store) { +#if defined(X509_V_FLAG_TRUSTED_FIRST) + X509_STORE_set_flags(pkix_validation_store, X509_V_FLAG_TRUSTED_FIRST); +#endif +#if defined(X509_V_FLAG_PARTIAL_CHAIN) + X509_STORE_set_flags(pkix_validation_store, X509_V_FLAG_PARTIAL_CHAIN); +#endif + } + } + + /* Add certificate authorities for clients from CA. + * Needed for certificate request during handshake. + */ + if (cert && ssock->is_server) { + STACK_OF(X509_NAME) *ca_dn = NULL; + + if (cert->CA_file.slen > 0) { + ca_dn = SSL_load_client_CA_file(cert->CA_file.ptr); + } else if (cert->CA_buf.slen > 0) { + X509 *x = NULL; + X509_NAME *xn = NULL; + STACK_OF(X509_NAME) *sk = NULL; + BIO *new_bio = BIO_new_mem_buf((void *)cert->CA_buf.ptr, cert->CA_buf.slen); + + sk = sk_X509_NAME_new(xname_cmp); + + if (sk != NULL && new_bio != NULL) { + for (;;) { + if (PEM_read_bio_X509(new_bio, &x, NULL, NULL) == NULL) + break; + + if ((xn = X509_get_subject_name(x)) == NULL) + break; + + if ((xn = X509_NAME_dup(xn)) == NULL) + break; + +#if !USING_BORINGSSL + if (sk_X509_NAME_find(sk, xn) >= 0) { +#else + if (sk_X509_NAME_find(sk, NULL, xn) >= 0) { +#endif + X509_NAME_free(xn); + } else { + sk_X509_NAME_push(sk, xn); + } + X509_free(x); + x = NULL; + } + } + if (sk != NULL) + ca_dn = sk; + if (new_bio != NULL) + BIO_free(new_bio); + } + + if (ca_dn != NULL) { + SSL_CTX_set_client_CA_list(ctx, ca_dn); + PJ_LOG(4, (ssock->pool->obj_name, "CA certificates loaded from %s", + (cert->CA_file.slen ? cert->CA_file.ptr : "buffer"))); + } else { + PJ_LOG(1, (ssock->pool->obj_name, "Error reading CA certificates from %s", + (cert->CA_file.slen ? cert->CA_file.ptr : "buffer"))); + } + } + + /* Early sensitive data cleanup after OpenSSL context setup. However, + * this cannot be done for listener sockets, as the data will still + * be needed by accepted sockets. + */ + if (cert && (!ssock->is_server || ssock->parent)) { + pj_ssl_cert_wipe_keys(cert); + } + + return PJ_SUCCESS; +} + +/* Create and initialize new SSL context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int mode; + pj_status_t status; + + pj_assert(ssock); + + /* Make sure OpenSSL library has been initialized */ + init_openssl(); + + set_entropy(ssock); + + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) + ssock->param.proto = PJ_SSL_SOCK_PROTO_SSL23; + + /* Create SSL context */ + if (SERVER_SUPPORT_SESSION_REUSE && ssock->is_server) { + SSL_CTX *server_ctx = ((ossl_sock_t *)ssock->parent)->ossl_ctx; + + if (!server_ctx) { + status = init_ossl_ctx(ssock->parent); + if (status != PJ_SUCCESS) + return status; + + server_ctx = ((ossl_sock_t *)ssock->parent)->ossl_ctx; + ((ossl_sock_t *)ssock->parent)->own_ctx = PJ_TRUE; + } + ossock->ossl_ctx = server_ctx; + } else { + status = init_ossl_ctx(ssock); + if (status != PJ_SUCCESS) + return status; + } + + /* Create SSL instance */ + ossock->ossl_ssl = SSL_new(ossock->ossl_ctx); + if (ossock->ossl_ssl == NULL) { + return GET_SSL_STATUS(ssock); + } + + /* Set SSL sock as application data of SSL instance */ + SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, ssock); + + /* SSL verification options */ + mode = SSL_VERIFY_PEER; + if (ssock->is_server && ssock->param.require_client_cert) + mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + SSL_set_verify(ossock->ossl_ssl, mode, &verify_cb); + + /* Set curve list */ + status = set_curves_list(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Set sigalg list */ + status = set_sigalgs(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Setup SSL BIOs */ + ossock->ossl_rbio = BIO_new(BIO_s_mem()); + ossock->ossl_wbio = BIO_new(BIO_s_mem()); + (void)BIO_set_close(ossock->ossl_rbio, BIO_CLOSE); + (void)BIO_set_close(ossock->ossl_wbio, BIO_CLOSE); + SSL_set_bio(ossock->ossl_ssl, ossock->ossl_rbio, ossock->ossl_wbio); + + return PJ_SUCCESS; +} + +/* Destroy SSL context and instance */ +static void ssl_destroy(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + /* Destroy SSL instance */ + if (ossock->ossl_ssl) { + SSL_free(ossock->ossl_ssl); /* this will also close BIOs */ + ossock->ossl_ssl = NULL; + } + + /* Destroy SSL context */ + if (ossock->ossl_ctx) { + if (ssock->is_server) { + if (!SERVER_SUPPORT_SESSION_REUSE || ossock->own_ctx) + SSL_CTX_free(ossock->ossl_ctx); + } else { + SSL_CTX_free(ossock->ossl_ctx); + } + ossock->ossl_ctx = NULL; + } + + /* Potentially shutdown OpenSSL library if this is the last + * context exists. + */ + shutdown_openssl(); +} + +/* Reset SSL socket state */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock) +{ + int post_unlock_flush_circ_buf = 0; + + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + /* Must lock around SSL calls, particularly SSL_shutdown + * as it can modify the write BIOs and destructively + * interfere with any ssl_write() calls in progress + * above in a multithreaded environment */ + pj_lock_acquire(ssock->write_mutex); + + /* Detach from SSL instance */ + if (ossock->ossl_ssl) { + SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL); + } + + /** + * Avoid calling SSL_shutdown() if handshake wasn't completed. + * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an + * SSL handshake, while previous versions always return 0. + * Call SSL_shutdown() when there is a timeout handshake failure or + * the last error is not SSL_ERROR_SYSCALL and not SSL_ERROR_SSL. + */ + if (ossock->ossl_ssl && SSL_in_init(ossock->ossl_ssl) == 0) { + if (ssock->handshake_status == PJ_ETIMEDOUT || + (ssock->last_err != SSL_ERROR_SYSCALL && ssock->last_err != SSL_ERROR_SSL)) { + int ret = SSL_shutdown(ossock->ossl_ssl); + if (ret == 0) { + /* SSL_shutdown will potentially trigger a bunch of + * data to dump to the socket */ + post_unlock_flush_circ_buf = 1; + } + } + } + + ssock->ssl_state = SSL_STATE_NULL; + + pj_lock_release(ssock->write_mutex); + + if (post_unlock_flush_circ_buf) { + /* Flush data to send close notify. */ + flush_circ_buf_output(ssock, &ssock->shutdown_op_key, 0, 0); + } + + ssl_close_sockets(ssock); + + /* Upon error, OpenSSL may leave any error description in the thread + * error queue, which sometime may cause next call to SSL API returning + * false error alarm, e.g: in Linux, SSL_CTX_use_certificate_chain_file() + * returning false error after a handshake error (in different SSL_CTX!). + * For now, just clear thread error queue here. + */ + ERR_clear_error(); +} + +static void ssl_ciphers_populate() +{ + if (ssl_cipher_num == 0 || ssl_curves_num == 0) { + init_openssl(); + shutdown_openssl(); + } +} + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + const SSL_CIPHER *cipher; + + /* Current cipher */ + cipher = SSL_get_current_cipher(ossock->ossl_ssl); + if (cipher) { + return (SSL_CIPHER_get_id(cipher) & 0x00FFFFFF); + } else { + return PJ_TLS_UNKNOWN_CIPHER; + } +} + +/* Generate cipher list with user preference order in OpenSSL format */ +static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_pool_t *tmp_pool = NULL; + char *buf = NULL; + enum { BUF_SIZE = 8192 }; + pj_str_t cipher_list; + unsigned i, j; + int ret; + + if (ssock->param.ciphers_num == 0) { + ret = SSL_CTX_set_cipher_list(ossock->ossl_ctx, PJ_SSL_SOCK_OSSL_CIPHERS); + if (ret < 1) { + return GET_SSL_STATUS(ssock); + } + + return PJ_SUCCESS; + } + + /* Create temporary pool. */ + tmp_pool = pj_pool_create(ssock->pool->factory, "ciphpool", BUF_SIZE, BUF_SIZE / 2, NULL); + if (!tmp_pool) + return PJ_ENOMEM; + + buf = (char *)pj_pool_zalloc(tmp_pool, BUF_SIZE); + + pj_strset(&cipher_list, buf, 0); + + /* Generate user specified cipher list in OpenSSL format */ + for (i = 0; i < ssock->param.ciphers_num; ++i) { + for (j = 0; j < ssl_cipher_num; ++j) { + if (ssock->param.ciphers[i] == ssl_ciphers[j].id) { + const char *c_name = ssl_ciphers[j].name; + + /* Check buffer size */ + if (cipher_list.slen + pj_ansi_strlen(c_name) + 2 > BUF_SIZE) { + pj_assert(!"Insufficient temporary buffer for cipher"); + return PJ_ETOOMANY; + } + + /* Add colon separator */ + if (cipher_list.slen) + pj_strcat2(&cipher_list, ":"); + + /* Add the cipher */ + pj_strcat2(&cipher_list, c_name); + break; + } + } + } + + /* Put NULL termination in the generated cipher list */ + cipher_list.ptr[cipher_list.slen] = '\0'; + + /* Finally, set chosen cipher list */ + ret = SSL_CTX_set_cipher_list(ossock->ossl_ctx, buf); + if (ret < 1) { + pj_pool_release(tmp_pool); + return GET_SSL_STATUS(ssock); + } + + pj_pool_release(tmp_pool); + return PJ_SUCCESS; +} + +static pj_status_t set_curves_list(pj_ssl_sock_t *ssock) +{ +#if !USING_LIBRESSL && !defined(OPENSSL_NO_EC) && OPENSSL_VERSION_NUMBER >= 0x1000200fL + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int ret; + int curves[PJ_SSL_SOCK_MAX_CURVES]; + unsigned cnt; + + if (ssock->param.curves_num == 0) + return PJ_SUCCESS; + + for (cnt = 0; cnt < ssock->param.curves_num; cnt++) { + curves[cnt] = get_nid_from_cid(ssock->param.curves[cnt]); + } + + if (SSL_is_server(ossock->ossl_ssl)) { + ret = SSL_set1_curves(ossock->ossl_ssl, curves, ssock->param.curves_num); + if (ret < 1) + return GET_SSL_STATUS(ssock); + } else { + ret = SSL_CTX_set1_curves(ossock->ossl_ctx, curves, ssock->param.curves_num); + if (ret < 1) + return GET_SSL_STATUS(ssock); + } +#else + PJ_UNUSED_ARG(ssock); +#endif + return PJ_SUCCESS; +} + +static pj_status_t set_sigalgs(pj_ssl_sock_t *ssock) +{ +#if !USING_LIBRESSL && OPENSSL_VERSION_NUMBER >= 0x1000200fL + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int ret; + + if (ssock->param.sigalgs.ptr && ssock->param.sigalgs.slen) { +#if !USING_BORINGSSL + if (ssock->is_server) { + ret = SSL_set1_client_sigalgs_list(ossock->ossl_ssl, ssock->param.sigalgs.ptr); + } else { + ret = SSL_set1_sigalgs_list(ossock->ossl_ssl, ssock->param.sigalgs.ptr); + } +#else + ret = SSL_set1_sigalgs_list(ossock->ossl_ssl, ssock->param.sigalgs.ptr); +#endif + + if (ret < 1) + return GET_SSL_STATUS(ssock); + } +#else + PJ_UNUSED_ARG(ssock); +#endif + return PJ_SUCCESS; +} + +static void set_entropy(pj_ssl_sock_t *ssock) +{ + int ret = 0; + + switch (ssock->param.entropy_type) { +#ifndef OPENSSL_NO_EGD + case PJ_SSL_ENTROPY_EGD: + ret = RAND_egd(ssock->param.entropy_path.ptr); + break; +#endif + case PJ_SSL_ENTROPY_RANDOM: + ret = RAND_load_file("/dev/random", 255); + break; + case PJ_SSL_ENTROPY_URANDOM: + ret = RAND_load_file("/dev/urandom", 255); + break; + case PJ_SSL_ENTROPY_FILE: + ret = RAND_load_file(ssock->param.entropy_path.ptr, 255); + break; + case PJ_SSL_ENTROPY_NONE: + default: + break; + } + + if (ret < 0) { + PJ_LOG(3, (ssock->pool->obj_name, + "SSL failed to reseed with entropy type %d " + "[native err=%d]", + ssock->param.entropy_type, ret)); + } +} + +/* Parse OpenSSL ASN1_TIME to pj_time_val and GMT info */ +static pj_bool_t parse_ossl_asn1_time(pj_time_val *tv, pj_bool_t *gmt, const ASN1_TIME *tm) +{ + unsigned long parts[7] = {0}; + char *p, *end; + unsigned len; + pj_bool_t utc; + pj_parsed_time pt; + int i; + + utc = tm->type == V_ASN1_UTCTIME; + p = (char *)tm->data; + len = tm->length; + end = p + len - 1; + + /* GMT */ + *gmt = (*end == 'Z'); + + /* parse parts */ + for (i = 0; i < 7 && p < end; ++i) { + pj_str_t st; + + if (i == 0 && !utc) { + /* 4 digits year part for non-UTC time format */ + st.slen = 4; + } else if (i == 6) { + /* fraction of seconds */ + if (*p == '.') + ++p; + st.slen = end - p + 1; + } else { + /* other parts always 2 digits length */ + st.slen = 2; + } + st.ptr = p; + + parts[i] = pj_strtoul(&st); + p += st.slen; + } + + /* encode parts to pj_time_val */ + pt.year = parts[0]; + if (utc) + pt.year += (pt.year < 50) ? 2000 : 1900; + pt.mon = parts[1] - 1; + pt.day = parts[2]; + pt.hour = parts[3]; + pt.min = parts[4]; + pt.sec = parts[5]; + pt.msec = parts[6]; + + pj_time_encode(&pt, tv); + + return PJ_TRUE; +} + +/* Get Common Name field string from a general name string */ +static void get_cn_from_gen_name(const pj_str_t *gen_name, pj_str_t *cn) +{ + pj_str_t CN_sign = {"/CN=", 4}; + char *p, *q; + + pj_bzero(cn, sizeof(pj_str_t)); + + if (!gen_name->slen) + return; + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 4; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, '/'); + if (q) + cn->slen = q - p; +} + +/* Get certificate info from OpenSSL X509, in case the certificate info + * hal already populated, this function will check if the contents need + * to be updated by inspecting the issuer and the serial number. + */ +static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, X509 *x, pj_bool_t get_pem) +{ + pj_bool_t update_needed; + char buf[512]; + pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */ + const pj_uint8_t *q; + unsigned len; + GENERAL_NAMES *names = NULL; + + pj_assert(pool && ci && x); + + /* Get issuer */ + X509_NAME_oneline(X509_get_issuer_name(x), buf, sizeof(buf)); + + /* Get serial no */ + q = (const pj_uint8_t *)M_ASN1_STRING_data(X509_get_serialNumber(x)); + len = M_ASN1_STRING_length(X509_get_serialNumber(x)); + if (len > sizeof(ci->serial_no)) + len = sizeof(ci->serial_no); + pj_memcpy(serial_no + sizeof(ci->serial_no) - len, q, len); + + /* Check if the contents need to be updated. */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || pj_memcmp(ci->serial_no, serial_no, sizeof(ci->serial_no)); + if (!update_needed) + return; + + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = X509_get_version(x) + 1; + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + get_cn_from_gen_name(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); + + /* Subject */ + pj_strdup2(pool, &ci->subject.info, X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf))); + get_cn_from_gen_name(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + parse_ossl_asn1_time(&ci->validity.start, &ci->validity.gmt, X509_get_notBefore(x)); + parse_ossl_asn1_time(&ci->validity.end, &ci->validity.gmt, X509_get_notAfter(x)); + + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + names = (GENERAL_NAMES *)X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + } + if (names) { + unsigned i, cnt; + + cnt = sk_GENERAL_NAME_num(names); + ci->subj_alt_name.entry = pj_pool_calloc(pool, cnt, sizeof(*ci->subj_alt_name.entry)); + + for (i = 0; i < cnt; ++i) { + unsigned char *p = 0; + pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN; + const GENERAL_NAME *name; + + name = sk_GENERAL_NAME_value(names, i); + + switch (name->type) { + case GEN_EMAIL: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GEN_DNS: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_DNS; + break; + case GEN_URI: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_URI; + break; + case GEN_IPADD: + p = (unsigned char *)M_ASN1_STRING_data(name->d.ip); + len = M_ASN1_STRING_length(name->d.ip); + type = PJ_SSL_CERT_NAME_IP; + break; + default: + break; + } + + if (p && len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + if (type == PJ_SSL_CERT_NAME_IP) { + int af = pj_AF_INET(); + if (len == sizeof(pj_in6_addr)) + af = pj_AF_INET6(); + pj_inet_ntop2(af, p, buf, sizeof(buf)); + pj_strdup2(pool, &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, buf); + } else { + pj_strdup2(pool, &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, (char *)p); + OPENSSL_free(p); + } + ci->subj_alt_name.cnt++; + } + } + GENERAL_NAMES_free(names); + names = NULL; + } + + if (get_pem) { + /* Update raw Certificate info in PEM format. */ + BIO *bio; + BUF_MEM *ptr; + + bio = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_X509(bio, x)) { + PJ_LOG(3, (THIS_FILE, "Error retrieving raw certificate info")); + ci->raw.ptr = NULL; + ci->raw.slen = 0; + } else { + BIO_write(bio, "\0", 1); + BIO_get_mem_ptr(bio, &ptr); + pj_strdup2(pool, &ci->raw, ptr->data); + } + BIO_free(bio); + } +} + +/* Update remote certificates chain info. This function should be + * called after handshake or renegotiation successfully completed. + */ +static void ssl_update_remote_cert_chain_info(pj_pool_t *pool, pj_ssl_cert_info *ci, STACK_OF(X509) * chain, + pj_bool_t get_pem) +{ + int i; + + /* For now, get_pem has to be PJ_TRUE */ + pj_assert(get_pem); + PJ_UNUSED_ARG(get_pem); + + ci->raw_chain.cert_raw = (pj_str_t *)pj_pool_calloc(pool, sk_X509_num(chain), sizeof(pj_str_t)); + ci->raw_chain.cnt = sk_X509_num(chain); + + for (i = 0; i < sk_X509_num(chain); i++) { + BIO *bio; + BUF_MEM *ptr; + X509 *x = sk_X509_value(chain, i); + + bio = BIO_new(BIO_s_mem()); + + if (!PEM_write_bio_X509(bio, x)) { + PJ_LOG(3, (THIS_FILE, "Error retrieving raw certificate info")); + ci->raw_chain.cert_raw[i].ptr = NULL; + ci->raw_chain.cert_raw[i].slen = 0; + } else { + BIO_write(bio, "\0", 1); + BIO_get_mem_ptr(bio, &ptr); + pj_strdup2(pool, &ci->raw_chain.cert_raw[i], ptr->data); + } + + BIO_free(bio); + } +} + +/* Update local & remote certificates info. This function should be + * called after handshake or renegotiation successfully completed. + */ +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + + update_certs_info(ssock, NULL, &ssock->local_cert_info, &ssock->remote_cert_info, PJ_FALSE); +} + +static void update_certs_info(pj_ssl_sock_t *ssock, X509_STORE_CTX *ctx, pj_ssl_cert_info *local_cert_info, + pj_ssl_cert_info *remote_cert_info, pj_bool_t is_verify) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + X509 *x; + STACK_OF(X509) * chain; + + /* Active local certificate */ + x = SSL_get_certificate(ossock->ossl_ssl); + if (x) { + get_cert_info(ssock->pool, local_cert_info, x, PJ_FALSE); + /* Don't free local's X509! */ + } else { + pj_bzero(local_cert_info, sizeof(pj_ssl_cert_info)); + } + + /* Active remote certificate */ + if (is_verify) { + x = X509_STORE_CTX_get0_cert(ctx); + } else { + x = SSL_get_peer_certificate(ossock->ossl_ssl); + } + if (x) { + get_cert_info(ssock->pool, remote_cert_info, x, PJ_TRUE); + if (!is_verify) { + /* Free peer's X509 */ + X509_free(x); + } + } else { + pj_bzero(remote_cert_info, sizeof(pj_ssl_cert_info)); + } + + if (is_verify) { + chain = X509_STORE_CTX_get1_chain(ctx); + } else { + chain = SSL_get_peer_cert_chain(ossock->ossl_ssl); + } + if (chain) { + pj_pool_reset(ssock->info_pool); + ssl_update_remote_cert_chain_info(ssock->info_pool, remote_cert_info, chain, PJ_TRUE); + /* Only free the chain returned by X509_STORE_CTX_get1_chain(). + * The reference count of each cert returned by + * SSL_get_peer_cert_chain() is not incremented. + */ + if (is_verify) { + sk_X509_pop_free(chain, X509_free); + } + } else { + remote_cert_info->raw_chain.cnt = 0; + } +} + +/* Flush write BIO to network socket. Note that any access to write BIO + * MUST be serialized, so mutex protection must cover any call to OpenSSL + * API (that possibly generate data for write BIO) along with the call to + * this function (flushing all data in write BIO generated by above + * OpenSSL API call). + */ +static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_size_t orig_len, + unsigned flags); + +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + if (is_server) { + SSL_set_accept_state(ossock->ossl_ssl); + } else { + SSL_set_connect_state(ossock->ossl_ssl); + } +} + +/* Server Name Indication server callback */ +static int sni_cb(SSL *ssl, int *al, void *arg) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)arg; + const char *sname; + + PJ_UNUSED_ARG(al); + + sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!sname || pj_stricmp2(&ssock->param.server_name, sname)) { + PJ_LOG(4, (THIS_FILE, "Client SNI rejected: %s", sname)); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + return SSL_TLSEXT_ERR_OK; +} + +static void ssl_set_peer_name(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + /* Set server name to connect */ + if (ssock->param.server_name.slen && get_ip_addr_ver(&ssock->param.server_name) == 0) { + if (ssock->is_server) { +#if defined(SSL_CTX_set_tlsext_servername_callback) && defined(SSL_CTX_set_tlsext_servername_arg) + + SSL_CTX_set_tlsext_servername_callback(ossock->ossl_ctx, &sni_cb); + SSL_CTX_set_tlsext_servername_arg(ossock->ossl_ctx, ssock); + +#endif + } else { +#ifdef SSL_set_tlsext_host_name + /* Server name is null terminated already */ + if (!SSL_set_tlsext_host_name(ossock->ossl_ssl, ssock->param.server_name.ptr)) { + char err_str[PJ_ERR_MSG_SIZE]; + + ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str)); + PJ_LOG(3, (ssock->pool->obj_name, + "SSL_set_tlsext_host_name() " + "failed: %s", + err_str)); + } +#endif + } + } +} + +/* Asynchronouse handshake */ +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_status_t status; + int err; + + /* Perform SSL handshake */ + pj_lock_acquire(ssock->write_mutex); + err = SSL_do_handshake(ossock->ossl_ssl); + pj_lock_release(ssock->write_mutex); + + /* SSL_do_handshake() may put some pending data into SSL write BIO, + * flush it if any. + */ + status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + return status; + } + + if (err < 0) { + int err2 = SSL_get_error(ossock->ossl_ssl, err); + if (err2 != SSL_ERROR_NONE && err2 != SSL_ERROR_WANT_READ) { + /* Handshake fails */ + status = STATUS_FROM_SSL_ERR2("Handshake", ssock, err, err2, 0); + return status; + } + } + + /* Check if handshake has been completed */ + if (SSL_is_init_finished(ossock->ossl_ssl)) { + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (ssock->is_server && ssock->ssl_state != SSL_STATE_ESTABLISHED) { + enum { BUF_SIZE = 64 }; + unsigned int len = 0, i; + const unsigned char *sctx, *sid; + char buf[BUF_SIZE + 1]; + SSL_SESSION *sess; + + sess = SSL_get_session(ossock->ossl_ssl); + +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + PJ_LOG(5, (THIS_FILE, + "Session info: reused=%d, resumable=%d, " + "timeout=%d", + SSL_session_reused(ossock->ossl_ssl), SSL_SESSION_is_resumable(sess), + SSL_SESSION_get_timeout(sess))); +#else + PJ_LOG(5, (THIS_FILE, + "Session info: reused=%d, resumable=%d, " + "timeout=%d", + SSL_session_reused(ossock->ossl_ssl), -1, SSL_SESSION_get_timeout(sess))); +#endif + + sid = SSL_SESSION_get_id(sess, &len); + len *= 2; + if (len >= BUF_SIZE) + len = BUF_SIZE; + for (i = 0; i < len; i += 2) + pj_ansi_sprintf(buf + i, "%02X", sid[i / 2]); + buf[len] = '\0'; + PJ_LOG(5, (THIS_FILE, "Session id: %s", buf)); + + sctx = SSL_SESSION_get0_id_context(sess, &len); + if (len >= BUF_SIZE) + len = BUF_SIZE; + for (i = 0; i < len; i++) + pj_ansi_sprintf(buf + i, "%d", sctx[i]); + buf[len] = '\0'; + PJ_LOG(5, (THIS_FILE, "Session id context: %s", buf)); + } +#endif + + ssock->ssl_state = SSL_STATE_ESTABLISHED; + return PJ_SUCCESS; + } + + return PJ_EPENDING; +} + +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int size_ = *size; + int len = size_; + + /* SSL_read() may write some data to write buffer when re-negotiation + * is on progress, so let's protect it with write mutex. + */ + pj_lock_acquire(ssock->write_mutex); + *size = size_ = SSL_read(ossock->ossl_ssl, data, size_); + + if (size_ <= 0) { + pj_status_t status; + int err = SSL_get_error(ossock->ossl_ssl, size_); + + /* SSL might just return SSL_ERROR_WANT_READ in + * re-negotiation. + */ + if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ) { + if (err == SSL_ERROR_SYSCALL && size_ == -1 && ERR_peek_error() == 0 && errno == 0) { + status = STATUS_FROM_SSL_ERR2("Read", ssock, size_, err, len); + PJ_LOG(4, ("SSL", "SSL_read() = -1, with " + "SSL_ERROR_SYSCALL, no SSL error, " + "and errno = 0 - skip BIO error")); + /* Ignore these errors */ + } else { + /* Reset SSL socket state, then return PJ_FALSE */ + status = STATUS_FROM_SSL_ERR2("Read", ssock, size_, err, len); + pj_lock_release(ssock->write_mutex); + /* Unfortunately we can't hold the lock here to reset all the state. + * We probably should though. + */ + ssl_reset_sock_state(ssock); + return status; + } + } + + pj_lock_release(ssock->write_mutex); + /* Need renegotiation */ + return PJ_EEOF; + } + + pj_lock_release(ssock->write_mutex); + + return PJ_SUCCESS; +} + +/* Write plain data to SSL and flush write BIO. */ +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, pj_ssize_t size, int *nwritten) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_status_t status = PJ_SUCCESS; + + *nwritten = SSL_write(ossock->ossl_ssl, data, (int)size); + if (*nwritten <= 0) { + /* SSL failed to process the data, it may just that re-negotiation + * is on progress. + */ + int err; + err = SSL_get_error(ossock->ossl_ssl, *nwritten); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_NONE) { + status = PJ_EEOF; + } else { + /* Some problem occured */ + status = STATUS_FROM_SSL_ERR2("Write", ssock, *nwritten, err, size); + } + } else if (*nwritten < size) { + /* nwritten < size, shouldn't happen, unless write BIO cannot hold + * the whole secured data, perhaps because of insufficient memory. + */ + status = PJ_ENOMEM; + } + + return status; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_status_t status = PJ_SUCCESS; + int ret; + + if (SSL_renegotiate_pending(ossock->ossl_ssl)) + return PJ_EPENDING; + + ret = SSL_renegotiate(ossock->ossl_ssl); + if (ret <= 0) { + status = GET_SSL_STATUS(ssock); + } + + return status; +} + +/* Put back deprecation warning setting */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS == 1 +#pragma GCC diagnostic pop +#endif + +#endif /* PJ_HAS_SSL_SOCK */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/string.c b/src/tuya_p2p/pjproject/pjlib/src/pj/string.c new file mode 100755 index 000000000..4713be813 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/string.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if PJ_FUNCTIONS_ARE_INLINED == 0 +#include +#endif + +PJ_DEF(pj_ssize_t) pj_strspn(const pj_str_t *str, const pj_str_t *set_char) +{ + pj_ssize_t i, j, count = 0; + for (i = 0; i < str->slen; i++) { + if (count != i) + break; + + for (j = 0; j < set_char->slen; j++) { + if (str->ptr[i] == set_char->ptr[j]) + count++; + } + } + return count; +} + +PJ_DEF(pj_ssize_t) pj_strspn2(const pj_str_t *str, const char *set_char) +{ + pj_ssize_t i, j, count = 0; + for (i = 0; i < str->slen; i++) { + if (count != i) + break; + + for (j = 0; set_char[j] != 0; j++) { + if (str->ptr[i] == set_char[j]) + count++; + } + } + return count; +} + +PJ_DEF(pj_ssize_t) pj_strcspn(const pj_str_t *str, const pj_str_t *set_char) +{ + pj_ssize_t i, j; + for (i = 0; i < str->slen; i++) { + for (j = 0; j < set_char->slen; j++) { + if (str->ptr[i] == set_char->ptr[j]) + return i; + } + } + return i; +} + +PJ_DEF(pj_ssize_t) pj_strcspn2(const pj_str_t *str, const char *set_char) +{ + pj_ssize_t i, j; + for (i = 0; i < str->slen; i++) { + for (j = 0; set_char[j] != 0; j++) { + if (str->ptr[i] == set_char[j]) + return i; + } + } + return i; +} + +PJ_DEF(pj_ssize_t) pj_strtok(const pj_str_t *str, const pj_str_t *delim, pj_str_t *tok, pj_size_t start_idx) +{ + pj_ssize_t str_idx; + + pj_assert(str->slen >= 0); + pj_assert(delim->slen >= 0); + + tok->slen = 0; + if ((str->slen <= 0) || ((pj_size_t)str->slen < start_idx)) { + return str->slen; + } + + tok->ptr = str->ptr + start_idx; + tok->slen = str->slen - start_idx; + + str_idx = pj_strspn(tok, delim); + if (start_idx + str_idx == (pj_size_t)str->slen) { + return str->slen; + } + tok->ptr += str_idx; + tok->slen -= str_idx; + + tok->slen = pj_strcspn(tok, delim); + return start_idx + str_idx; +} + +PJ_DEF(pj_ssize_t) pj_strtok2(const pj_str_t *str, const char *delim, pj_str_t *tok, pj_size_t start_idx) +{ + pj_ssize_t str_idx; + + pj_assert(str->slen >= 0); + + tok->slen = 0; + if ((str->slen <= 0) || ((pj_size_t)str->slen < start_idx)) { + return str->slen; + } + + tok->ptr = str->ptr + start_idx; + tok->slen = str->slen - start_idx; + + str_idx = pj_strspn2(tok, delim); + if (start_idx + str_idx == (pj_size_t)str->slen) { + return str->slen; + } + tok->ptr += str_idx; + tok->slen -= str_idx; + + tok->slen = pj_strcspn2(tok, delim); + return start_idx + str_idx; +} + +PJ_DEF(char *) pj_strstr(const pj_str_t *str, const pj_str_t *substr) +{ + const char *s, *ends; + + PJ_ASSERT_RETURN(str->slen >= 0 && substr->slen >= 0, NULL); + + /* Check if the string is empty */ + if (str->slen <= 0) + return NULL; + + /* Special case when substr is empty */ + if (substr->slen <= 0) { + return (char *)str->ptr; + } + + s = str->ptr; + ends = str->ptr + str->slen - substr->slen; + for (; s <= ends; ++s) { + if (pj_ansi_strncmp(s, substr->ptr, substr->slen) == 0) + return (char *)s; + } + return NULL; +} + +PJ_DEF(char *) pj_stristr(const pj_str_t *str, const pj_str_t *substr) +{ + const char *s, *ends; + + PJ_ASSERT_RETURN(str->slen >= 0 && substr->slen >= 0, NULL); + + /* Check if the string is empty */ + if (str->slen <= 0) + return NULL; + + /* Special case when substr is empty */ + if (substr->slen == 0) { + return (char *)str->ptr; + } + + s = str->ptr; + ends = str->ptr + str->slen - substr->slen; + for (; s <= ends; ++s) { + if (pj_ansi_strnicmp(s, substr->ptr, substr->slen) == 0) + return (char *)s; + } + return NULL; +} + +PJ_DEF(pj_str_t *) pj_strltrim(pj_str_t *str) +{ + char *end = str->ptr + str->slen; + register char *p = str->ptr; + + pj_assert(str->slen >= 0); + + while (p < end && pj_isspace(*p)) + ++p; + str->slen -= (p - str->ptr); + str->ptr = p; + return str; +} + +PJ_DEF(pj_str_t *) pj_strrtrim(pj_str_t *str) +{ + char *end = str->ptr + str->slen; + register char *p = end - 1; + + pj_assert(str->slen >= 0); + + while (p >= str->ptr && pj_isspace(*p)) + --p; + str->slen -= ((end - p) - 1); + return str; +} + +PJ_DEF(char *) pj_create_random_string(char *str, pj_size_t len) +{ + unsigned i; + char *p = str; + + PJ_CHECK_STACK(); + + for (i = 0; i < len / 8; ++i) { + pj_uint32_t val = pj_rand(); + pj_val_to_hex_digit((val & 0xFF000000) >> 24, p + 0); + pj_val_to_hex_digit((val & 0x00FF0000) >> 16, p + 2); + pj_val_to_hex_digit((val & 0x0000FF00) >> 8, p + 4); + pj_val_to_hex_digit((val & 0x000000FF) >> 0, p + 6); + p += 8; + } + for (i = i * 8; i < len; ++i) { + *p++ = pj_hex_digits[pj_rand() & 0x0F]; + } + return str; +} + +PJ_DEF(long) pj_strtol(const pj_str_t *str) +{ + PJ_CHECK_STACK(); + + if (str->slen > 0 && (str->ptr[0] == '+' || str->ptr[0] == '-')) { + pj_str_t s; + + s.ptr = str->ptr + 1; + s.slen = str->slen - 1; + return (str->ptr[0] == '-' ? -(long)pj_strtoul(&s) : pj_strtoul(&s)); + } else + return pj_strtoul(str); +} + +PJ_DEF(pj_status_t) pj_strtol2(const pj_str_t *str, long *value) +{ + pj_str_t s; + unsigned long retval = 0; + pj_bool_t is_negative = PJ_FALSE; + int rc = 0; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(str->slen >= 0, PJ_EINVAL); + + if (!str || !value) { + return PJ_EINVAL; + } + + s = *str; + pj_strltrim(&s); + + if (s.slen == 0) + return PJ_EINVAL; + + if (s.ptr[0] == '+' || s.ptr[0] == '-') { + is_negative = (s.ptr[0] == '-'); + s.ptr += 1; + s.slen -= 1; + } + + rc = pj_strtoul3(&s, &retval, 10); + if (rc == PJ_EINVAL) { + return rc; + } else if (rc != PJ_SUCCESS) { + *value = is_negative ? PJ_MINLONG : PJ_MAXLONG; + return is_negative ? PJ_ETOOSMALL : PJ_ETOOBIG; + } + + if (retval > PJ_MAXLONG && !is_negative) { + *value = PJ_MAXLONG; + return PJ_ETOOBIG; + } + + if (retval > (PJ_MAXLONG + 1UL) && is_negative) { + *value = PJ_MINLONG; + return PJ_ETOOSMALL; + } + + *value = is_negative ? -(long)retval : retval; + + return PJ_SUCCESS; +} + +PJ_DEF(unsigned long) pj_strtoul(const pj_str_t *str) +{ + unsigned long value; + unsigned i; + + PJ_CHECK_STACK(); + + pj_assert(str->slen >= 0); + + value = 0; + for (i = 0; i < (unsigned)str->slen; ++i) { + if (!pj_isdigit(str->ptr[i])) + break; + value = value * 10 + (str->ptr[i] - '0'); + } + return value; +} + +PJ_DEF(unsigned long) pj_strtoul2(const pj_str_t *str, pj_str_t *endptr, unsigned base) +{ + unsigned long value; + unsigned i; + + PJ_CHECK_STACK(); + + pj_assert(str->slen >= 0); + + value = 0; + if (base <= 10) { + for (i = 0; i < (unsigned)str->slen; ++i) { + unsigned c = (str->ptr[i] - '0'); + if (c >= base) + break; + value = value * base + c; + } + } else if (base == 16) { + for (i = 0; i < (unsigned)str->slen; ++i) { + if (!pj_isxdigit(str->ptr[i])) + break; + value = value * 16 + pj_hex_digit_to_val(str->ptr[i]); + } + } else { + pj_assert(!"Unsupported base"); + i = 0; + value = 0xFFFFFFFFUL; + } + + if (endptr) { + endptr->ptr = str->ptr + i; + endptr->slen = (str->slen < 0) ? 0 : (str->slen - i); + } + + return value; +} + +PJ_DEF(pj_status_t) pj_strtoul3(const pj_str_t *str, unsigned long *value, unsigned base) +{ + pj_str_t s; + unsigned i; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(str->slen >= 0, PJ_EINVAL); + + if (!str || !value) { + return PJ_EINVAL; + } + + s = *str; + pj_strltrim(&s); + + if (s.slen == 0 || s.ptr[0] < '0' || (base <= 10 && (unsigned)s.ptr[0] > ('0' - 1) + base) || + (base == 16 && !pj_isxdigit(s.ptr[0]))) { + return PJ_EINVAL; + } + + *value = 0; + if (base <= 10) { + for (i = 0; i < (unsigned)s.slen; ++i) { + unsigned c = s.ptr[i] - '0'; + if (s.ptr[i] < '0' || (unsigned)s.ptr[i] > ('0' - 1) + base) { + break; + } + if (*value > PJ_MAXULONG / base) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + + *value *= base; + if ((PJ_MAXULONG - *value) < c) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + *value += c; + } + } else if (base == 16) { + for (i = 0; i < (unsigned)s.slen; ++i) { + unsigned c = pj_hex_digit_to_val(s.ptr[i]); + if (!pj_isxdigit(s.ptr[i])) + break; + + if (*value > PJ_MAXULONG / base) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + *value *= base; + if ((PJ_MAXULONG - *value) < c) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + *value += c; + } + } else { + pj_assert(!"Unsupported base"); + return PJ_EINVAL; + } + return PJ_SUCCESS; +} + +PJ_DEF(float) pj_strtof(const pj_str_t *str) +{ + pj_str_t part; + char *pdot; + float val; + + pj_assert(str->slen >= 0); + + if (str->slen <= 0) + return 0; + + pdot = (char *)pj_memchr(str->ptr, '.', str->slen); + part.ptr = str->ptr; + part.slen = pdot ? pdot - str->ptr : str->slen; + + if (part.slen) + val = (float)pj_strtol(&part); + else + val = 0; + + if (pdot) { + part.ptr = pdot + 1; + part.slen = (str->ptr + str->slen - pdot - 1); + if (part.slen) { + pj_str_t endptr; + float fpart, fdiv; + int i; + fpart = (float)pj_strtoul2(&part, &endptr, 10); + fdiv = 1.0; + for (i = 0; i < (part.slen - endptr.slen); ++i) + fdiv = fdiv * 10; + if (val >= 0) + val += (fpart / fdiv); + else + val -= (fpart / fdiv); + } + } + return val; +} + +PJ_DEF(int) pj_utoa(unsigned long val, char *buf) +{ + return pj_utoa_pad(val, buf, 0, 0); +} + +PJ_DEF(int) pj_utoa_pad(unsigned long val, char *buf, int min_dig, int pad) +{ + char *p; + int len; + + PJ_CHECK_STACK(); + + p = buf; + do { + unsigned long digval = (unsigned long)(val % 10); + val /= 10; + *p++ = (char)(digval + '0'); + } while (val > 0); + + len = (int)(p - buf); + while (len < min_dig) { + *p++ = (char)pad; + ++len; + } + *p-- = '\0'; + + do { + char temp = *p; + *p = *buf; + *buf = temp; + --p; + ++buf; + } while (buf < p); + + return len; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c b/src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c new file mode 100755 index 000000000..ccbceb910 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +/* + * addr_resolv.h + */ +PJ_EXPORT_SYMBOL(pj_gethostbyname) + +/* + * array.h + */ +PJ_EXPORT_SYMBOL(pj_array_insert) +PJ_EXPORT_SYMBOL(pj_array_erase) +PJ_EXPORT_SYMBOL(pj_array_find) + +/* + * config.h + */ +PJ_EXPORT_SYMBOL(pj_dump_config) + +/* + * errno.h + */ +PJ_EXPORT_SYMBOL(pj_get_os_error) +PJ_EXPORT_SYMBOL(pj_set_os_error) +PJ_EXPORT_SYMBOL(pj_get_netos_error) +PJ_EXPORT_SYMBOL(pj_set_netos_error) +PJ_EXPORT_SYMBOL(pj_strerror) + +/* + * except.h + */ +PJ_EXPORT_SYMBOL(pj_throw_exception_) +PJ_EXPORT_SYMBOL(pj_push_exception_handler_) +PJ_EXPORT_SYMBOL(pj_pop_exception_handler_) +PJ_EXPORT_SYMBOL(pj_setjmp) +PJ_EXPORT_SYMBOL(pj_longjmp) +PJ_EXPORT_SYMBOL(pj_exception_id_alloc) +PJ_EXPORT_SYMBOL(pj_exception_id_free) +PJ_EXPORT_SYMBOL(pj_exception_id_name) + +/* + * fifobuf.h + */ +PJ_EXPORT_SYMBOL(pj_fifobuf_init) +PJ_EXPORT_SYMBOL(pj_fifobuf_max_size) +PJ_EXPORT_SYMBOL(pj_fifobuf_alloc) +PJ_EXPORT_SYMBOL(pj_fifobuf_unalloc) +PJ_EXPORT_SYMBOL(pj_fifobuf_free) + +/* + * guid.h + */ +PJ_EXPORT_SYMBOL(pj_generate_unique_string) +PJ_EXPORT_SYMBOL(pj_create_unique_string) + +/* + * hash.h + */ +PJ_EXPORT_SYMBOL(pj_hash_calc) +PJ_EXPORT_SYMBOL(pj_hash_create) +PJ_EXPORT_SYMBOL(pj_hash_get) +PJ_EXPORT_SYMBOL(pj_hash_set) +PJ_EXPORT_SYMBOL(pj_hash_count) +PJ_EXPORT_SYMBOL(pj_hash_first) +PJ_EXPORT_SYMBOL(pj_hash_next) +PJ_EXPORT_SYMBOL(pj_hash_this) + +/* + * ioqueue.h + */ +PJ_EXPORT_SYMBOL(pj_ioqueue_create) +PJ_EXPORT_SYMBOL(pj_ioqueue_destroy) +PJ_EXPORT_SYMBOL(pj_ioqueue_set_lock) +PJ_EXPORT_SYMBOL(pj_ioqueue_register_sock) +PJ_EXPORT_SYMBOL(pj_ioqueue_unregister) +PJ_EXPORT_SYMBOL(pj_ioqueue_get_user_data) +PJ_EXPORT_SYMBOL(pj_ioqueue_poll) +PJ_EXPORT_SYMBOL(pj_ioqueue_read) +PJ_EXPORT_SYMBOL(pj_ioqueue_recv) +PJ_EXPORT_SYMBOL(pj_ioqueue_recvfrom) +PJ_EXPORT_SYMBOL(pj_ioqueue_write) +PJ_EXPORT_SYMBOL(pj_ioqueue_send) +PJ_EXPORT_SYMBOL(pj_ioqueue_sendto) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 +PJ_EXPORT_SYMBOL(pj_ioqueue_accept) +PJ_EXPORT_SYMBOL(pj_ioqueue_connect) +#endif + +/* + * list.h + */ +PJ_EXPORT_SYMBOL(pj_list_insert_before) +PJ_EXPORT_SYMBOL(pj_list_insert_nodes_before) +PJ_EXPORT_SYMBOL(pj_list_insert_after) +PJ_EXPORT_SYMBOL(pj_list_insert_nodes_after) +PJ_EXPORT_SYMBOL(pj_list_merge_first) +PJ_EXPORT_SYMBOL(pj_list_merge_last) +PJ_EXPORT_SYMBOL(pj_list_erase) +PJ_EXPORT_SYMBOL(pj_list_find_node) +PJ_EXPORT_SYMBOL(pj_list_search) + +/* + * log.h + */ +PJ_EXPORT_SYMBOL(pj_log_write) +#if PJ_LOG_MAX_LEVEL >= 1 +PJ_EXPORT_SYMBOL(pj_log_set_log_func) +PJ_EXPORT_SYMBOL(pj_log_get_log_func) +PJ_EXPORT_SYMBOL(pj_log_set_level) +PJ_EXPORT_SYMBOL(pj_log_get_level) +PJ_EXPORT_SYMBOL(pj_log_set_decor) +PJ_EXPORT_SYMBOL(pj_log_get_decor) +PJ_EXPORT_SYMBOL(pj_log_1) +#endif +#if PJ_LOG_MAX_LEVEL >= 2 +PJ_EXPORT_SYMBOL(pj_log_2) +#endif +#if PJ_LOG_MAX_LEVEL >= 3 +PJ_EXPORT_SYMBOL(pj_log_3) +#endif +#if PJ_LOG_MAX_LEVEL >= 4 +PJ_EXPORT_SYMBOL(pj_log_4) +#endif +#if PJ_LOG_MAX_LEVEL >= 5 +PJ_EXPORT_SYMBOL(pj_log_5) +#endif +#if PJ_LOG_MAX_LEVEL >= 6 +PJ_EXPORT_SYMBOL(pj_log_6) +#endif + +/* + * os.h + */ +PJ_EXPORT_SYMBOL(pj_init) +PJ_EXPORT_SYMBOL(pj_getpid) +PJ_EXPORT_SYMBOL(pj_thread_register) +PJ_EXPORT_SYMBOL(pj_thread_create) +PJ_EXPORT_SYMBOL(pj_thread_get_name) +PJ_EXPORT_SYMBOL(pj_thread_resume) +PJ_EXPORT_SYMBOL(pj_thread_this) +PJ_EXPORT_SYMBOL(pj_thread_join) +PJ_EXPORT_SYMBOL(pj_thread_destroy) +PJ_EXPORT_SYMBOL(pj_thread_sleep) +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 +PJ_EXPORT_SYMBOL(pj_thread_check_stack) +PJ_EXPORT_SYMBOL(pj_thread_get_stack_max_usage) +PJ_EXPORT_SYMBOL(pj_thread_get_stack_info) +#endif +PJ_EXPORT_SYMBOL(pj_atomic_create) +PJ_EXPORT_SYMBOL(pj_atomic_destroy) +PJ_EXPORT_SYMBOL(pj_atomic_set) +PJ_EXPORT_SYMBOL(pj_atomic_get) +PJ_EXPORT_SYMBOL(pj_atomic_inc) +PJ_EXPORT_SYMBOL(pj_atomic_dec) +PJ_EXPORT_SYMBOL(pj_thread_local_alloc) +PJ_EXPORT_SYMBOL(pj_thread_local_free) +PJ_EXPORT_SYMBOL(pj_thread_local_set) +PJ_EXPORT_SYMBOL(pj_thread_local_get) +PJ_EXPORT_SYMBOL(pj_enter_critical_section) +PJ_EXPORT_SYMBOL(pj_leave_critical_section) +PJ_EXPORT_SYMBOL(pj_mutex_create) +PJ_EXPORT_SYMBOL(pj_mutex_lock) +PJ_EXPORT_SYMBOL(pj_mutex_unlock) +PJ_EXPORT_SYMBOL(pj_mutex_trylock) +PJ_EXPORT_SYMBOL(pj_mutex_destroy) +#if defined(PJ_DEBUG) && PJ_DEBUG != 0 +PJ_EXPORT_SYMBOL(pj_mutex_is_locked) +#endif +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +PJ_EXPORT_SYMBOL(pj_sem_create) +PJ_EXPORT_SYMBOL(pj_sem_wait) +PJ_EXPORT_SYMBOL(pj_sem_trywait) +PJ_EXPORT_SYMBOL(pj_sem_post) +PJ_EXPORT_SYMBOL(pj_sem_destroy) +#endif +PJ_EXPORT_SYMBOL(pj_gettimeofday) +PJ_EXPORT_SYMBOL(pj_time_decode) +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 +PJ_EXPORT_SYMBOL(pj_gettickcount) +PJ_EXPORT_SYMBOL(pj_get_timestamp) +PJ_EXPORT_SYMBOL(pj_get_timestamp_freq) +PJ_EXPORT_SYMBOL(pj_elapsed_time) +PJ_EXPORT_SYMBOL(pj_elapsed_usec) +PJ_EXPORT_SYMBOL(pj_elapsed_nanosec) +PJ_EXPORT_SYMBOL(pj_elapsed_cycle) +#endif + +/* + * pool.h + */ +PJ_EXPORT_SYMBOL(pj_pool_create) +PJ_EXPORT_SYMBOL(pj_pool_release) +PJ_EXPORT_SYMBOL(pj_pool_getobjname) +PJ_EXPORT_SYMBOL(pj_pool_reset) +PJ_EXPORT_SYMBOL(pj_pool_get_capacity) +PJ_EXPORT_SYMBOL(pj_pool_get_used_size) +PJ_EXPORT_SYMBOL(pj_pool_alloc) +PJ_EXPORT_SYMBOL(pj_pool_calloc) +PJ_EXPORT_SYMBOL(pj_pool_factory_default_policy) +PJ_EXPORT_SYMBOL(pj_pool_create_int) +PJ_EXPORT_SYMBOL(pj_pool_init_int) +PJ_EXPORT_SYMBOL(pj_pool_destroy_int) +PJ_EXPORT_SYMBOL(pj_caching_pool_init) +PJ_EXPORT_SYMBOL(pj_caching_pool_destroy) + +/* + * rand.h + */ +PJ_EXPORT_SYMBOL(pj_rand) +PJ_EXPORT_SYMBOL(pj_srand) + +/* + * rbtree.h + */ +PJ_EXPORT_SYMBOL(pj_rbtree_init) +PJ_EXPORT_SYMBOL(pj_rbtree_first) +PJ_EXPORT_SYMBOL(pj_rbtree_last) +PJ_EXPORT_SYMBOL(pj_rbtree_next) +PJ_EXPORT_SYMBOL(pj_rbtree_prev) +PJ_EXPORT_SYMBOL(pj_rbtree_insert) +PJ_EXPORT_SYMBOL(pj_rbtree_find) +PJ_EXPORT_SYMBOL(pj_rbtree_erase) +PJ_EXPORT_SYMBOL(pj_rbtree_max_height) +PJ_EXPORT_SYMBOL(pj_rbtree_min_height) + +/* + * sock.h + */ +PJ_EXPORT_SYMBOL(PJ_AF_UNIX) +PJ_EXPORT_SYMBOL(PJ_AF_INET) +PJ_EXPORT_SYMBOL(PJ_AF_INET6) +PJ_EXPORT_SYMBOL(PJ_AF_PACKET) +PJ_EXPORT_SYMBOL(PJ_AF_IRDA) +PJ_EXPORT_SYMBOL(PJ_SOCK_STREAM) +PJ_EXPORT_SYMBOL(PJ_SOCK_DGRAM) +PJ_EXPORT_SYMBOL(PJ_SOCK_RAW) +PJ_EXPORT_SYMBOL(PJ_SOCK_RDM) +PJ_EXPORT_SYMBOL(PJ_SOL_SOCKET) +PJ_EXPORT_SYMBOL(PJ_SOL_IP) +PJ_EXPORT_SYMBOL(PJ_SOL_TCP) +PJ_EXPORT_SYMBOL(PJ_SOL_UDP) +PJ_EXPORT_SYMBOL(PJ_SOL_IPV6) +PJ_EXPORT_SYMBOL(pj_ntohs) +PJ_EXPORT_SYMBOL(pj_htons) +PJ_EXPORT_SYMBOL(pj_ntohl) +PJ_EXPORT_SYMBOL(pj_htonl) +PJ_EXPORT_SYMBOL(pj_inet_ntoa) +PJ_EXPORT_SYMBOL(pj_inet_aton) +PJ_EXPORT_SYMBOL(pj_inet_addr) +PJ_EXPORT_SYMBOL(pj_sockaddr_in_set_str_addr) +PJ_EXPORT_SYMBOL(pj_sockaddr_in_init) +PJ_EXPORT_SYMBOL(pj_gethostname) +PJ_EXPORT_SYMBOL(pj_gethostaddr) +PJ_EXPORT_SYMBOL(pj_sock_socket) +PJ_EXPORT_SYMBOL(pj_sock_close) +PJ_EXPORT_SYMBOL(pj_sock_bind) +PJ_EXPORT_SYMBOL(pj_sock_bind_in) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 +PJ_EXPORT_SYMBOL(pj_sock_listen) +PJ_EXPORT_SYMBOL(pj_sock_accept) +PJ_EXPORT_SYMBOL(pj_sock_shutdown) +#endif +PJ_EXPORT_SYMBOL(pj_sock_connect) +PJ_EXPORT_SYMBOL(pj_sock_getpeername) +PJ_EXPORT_SYMBOL(pj_sock_getsockname) +PJ_EXPORT_SYMBOL(pj_sock_getsockopt) +PJ_EXPORT_SYMBOL(pj_sock_setsockopt) +PJ_EXPORT_SYMBOL(pj_sock_recv) +PJ_EXPORT_SYMBOL(pj_sock_recvfrom) +PJ_EXPORT_SYMBOL(pj_sock_send) +PJ_EXPORT_SYMBOL(pj_sock_sendto) + +/* + * sock_select.h + */ +PJ_EXPORT_SYMBOL(PJ_FD_ZERO) +PJ_EXPORT_SYMBOL(PJ_FD_SET) +PJ_EXPORT_SYMBOL(PJ_FD_CLR) +PJ_EXPORT_SYMBOL(PJ_FD_ISSET) +PJ_EXPORT_SYMBOL(pj_sock_select) + +/* + * string.h + */ +PJ_EXPORT_SYMBOL(pj_str) +PJ_EXPORT_SYMBOL(pj_strassign) +PJ_EXPORT_SYMBOL(pj_strcpy) +PJ_EXPORT_SYMBOL(pj_strcpy2) +PJ_EXPORT_SYMBOL(pj_strdup) +PJ_EXPORT_SYMBOL(pj_strdup_with_null) +PJ_EXPORT_SYMBOL(pj_strdup2) +PJ_EXPORT_SYMBOL(pj_strdup3) +PJ_EXPORT_SYMBOL(pj_strcmp) +PJ_EXPORT_SYMBOL(pj_strcmp2) +PJ_EXPORT_SYMBOL(pj_strncmp) +PJ_EXPORT_SYMBOL(pj_strncmp2) +PJ_EXPORT_SYMBOL(pj_stricmp) +PJ_EXPORT_SYMBOL(pj_stricmp2) +PJ_EXPORT_SYMBOL(pj_strnicmp) +PJ_EXPORT_SYMBOL(pj_strnicmp2) +PJ_EXPORT_SYMBOL(pj_strcat) +PJ_EXPORT_SYMBOL(pj_strltrim) +PJ_EXPORT_SYMBOL(pj_strrtrim) +PJ_EXPORT_SYMBOL(pj_strtrim) +PJ_EXPORT_SYMBOL(pj_create_random_string) +PJ_EXPORT_SYMBOL(pj_strtoul) +PJ_EXPORT_SYMBOL(pj_utoa) +PJ_EXPORT_SYMBOL(pj_utoa_pad) + +/* + * timer.h + */ +PJ_EXPORT_SYMBOL(pj_timer_heap_mem_size) +PJ_EXPORT_SYMBOL(pj_timer_heap_create) +PJ_EXPORT_SYMBOL(pj_timer_entry_init) +PJ_EXPORT_SYMBOL(pj_timer_heap_schedule) +PJ_EXPORT_SYMBOL(pj_timer_heap_cancel) +PJ_EXPORT_SYMBOL(pj_timer_heap_count) +PJ_EXPORT_SYMBOL(pj_timer_heap_earliest_time) +PJ_EXPORT_SYMBOL(pj_timer_heap_poll) + +/* + * types.h + */ +PJ_EXPORT_SYMBOL(pj_time_val_normalize) diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/timer.c b/src/tuya_p2p/pjproject/pjlib/src/pj/timer.c new file mode 100755 index 000000000..1f876cae7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/timer.c @@ -0,0 +1,902 @@ +/* + * The PJLIB's timer heap is based (or more correctly, copied and modied) + * from ACE library by Douglas C. Schmidt. ACE is an excellent OO framework + * that implements many core patterns for concurrent communication software. + * If you're looking for C++ alternative of PJLIB, then ACE is your best + * solution. + * + * You may use this file according to ACE open source terms or PJLIB open + * source terms. You can find the fine ACE library at: + * http://www.cs.wustl.edu/~schmidt/ACE.html + * + * ACE is Copyright (C)1993-2006 Douglas C. Schmidt + * + * GNU Public License: + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "timer.c" + +#define HEAP_PARENT(X) (X == 0 ? 0 : (((X)-1) / 2)) +#define HEAP_LEFT(X) (((X) + (X)) + 1) + +#define DEFAULT_MAX_TIMED_OUT_PER_POLL (64) + +/* Enable this to raise assertion in order to catch bug of timer entry + * which has been deallocated without being cancelled. If disabled, + * the timer heap will simply remove the destroyed entry (and print log) + * and resume normally. + * This setting only works if PJ_TIMER_USE_COPY is enabled. + */ +#define ASSERT_IF_ENTRY_DESTROYED (PJ_TIMER_USE_COPY ? 0 : 0) + +enum { F_DONT_CALL = 1, F_DONT_ASSERT = 2, F_SET_ID = 4 }; + +#if PJ_TIMER_USE_COPY + +/* Duplicate/copy of the timer entry. */ +typedef struct pj_timer_entry_dup { +#if PJ_TIMER_USE_LINKED_LIST + /** + * Standard list members. + */ + PJ_DECL_LIST_MEMBER(struct pj_timer_entry_dup); +#endif + + /** + * The duplicate copy. + */ + pj_timer_entry dup; + + /** + * Pointer of the original timer entry. + */ + pj_timer_entry *entry; + + /** + * The future time when the timer expires, which the value is updated + * by timer heap when the timer is scheduled. + */ + pj_time_val _timer_value; + + /** + * Internal: the group lock used by this entry, set when + * pj_timer_heap_schedule_w_lock() is used. + */ + pj_grp_lock_t *_grp_lock; + +#if PJ_TIMER_DEBUG + const char *src_file; + int src_line; +#endif + +} pj_timer_entry_dup; + +#define GET_TIMER(ht, node) &ht->timer_dups[node->_timer_id] +#define GET_ENTRY(node) node->entry +#define GET_FIELD(node, _timer_id) node->dup._timer_id + +#else + +typedef pj_timer_entry pj_timer_entry_dup; + +#define GET_TIMER(ht, node) node +#define GET_ENTRY(node) node +#define GET_FIELD(node, _timer_id) node->_timer_id + +#endif + +/** + * The implementation of timer heap. + */ +struct pj_timer_heap_t { + /** Pool from which the timer heap resize will get the storage from */ + pj_pool_t *pool; + + /** Maximum size of the heap. */ + pj_size_t max_size; + + /** Current size of the heap. */ + pj_size_t cur_size; + + /** Max timed out entries to process per poll. */ + unsigned max_entries_per_poll; + + /** Lock object. */ + pj_lock_t *lock; + + /** Autodelete lock. */ + pj_bool_t auto_delete_lock; + + /** + * Current contents of the Heap, which is organized as a "heap" of + * pj_timer_entry *'s. In this context, a heap is a "partially + * ordered, almost complete" binary tree, which is stored in an + * array. + */ + pj_timer_entry_dup **heap; + +#if PJ_TIMER_USE_LINKED_LIST + /** + * If timer heap uses linked list, then this will represent the head of + * the list. + */ + pj_timer_entry_dup head_list; +#endif + + /** + * An array of "pointers" that allows each pj_timer_entry in the + * to be located in O(1) time. Basically, + * contains the slot in the array where an pj_timer_entry + * with timer id resides. Thus, the timer id passed back from + * is really an slot into the array. The + * array serves two purposes: negative values are + * treated as "pointers" for the , whereas positive + * values are treated as "pointers" into the array. + */ + pj_timer_id_t *timer_ids; + + /** + * An array of timer entry copies. + */ + pj_timer_entry_dup *timer_dups; + + /** + * "Pointer" to the first element in the freelist contained within + * the array, which is organized as a stack. + */ + pj_timer_id_t timer_ids_freelist; + + /** Callback to be called when a timer expires. */ + pj_timer_heap_callback *callback; +}; + +PJ_INLINE(void) lock_timer_heap(pj_timer_heap_t *ht) +{ + if (ht->lock) { + pj_lock_acquire(ht->lock); + } +} + +PJ_INLINE(void) unlock_timer_heap(pj_timer_heap_t *ht) +{ + if (ht->lock) { + pj_lock_release(ht->lock); + } +} + +static void copy_node(pj_timer_heap_t *ht, pj_size_t slot, pj_timer_entry_dup *moved_node) +{ + PJ_CHECK_STACK(); + + // Insert into its new location in the heap. + ht->heap[slot] = moved_node; + + // Update the corresponding slot in the parallel array. + ht->timer_ids[GET_FIELD(moved_node, _timer_id)] = (int)slot; +} + +static pj_timer_id_t pop_freelist(pj_timer_heap_t *ht) +{ + // We need to truncate this to for backwards compatibility. + pj_timer_id_t new_id = ht->timer_ids_freelist; + + PJ_CHECK_STACK(); + + // The freelist values in the are negative, so we need + // to negate them to get the next freelist "pointer." + ht->timer_ids_freelist = -ht->timer_ids[ht->timer_ids_freelist]; + + return new_id; +} + +static void push_freelist(pj_timer_heap_t *ht, pj_timer_id_t old_id) +{ + PJ_CHECK_STACK(); + + // The freelist values in the are negative, so we need + // to negate them to get the next freelist "pointer." + ht->timer_ids[old_id] = -ht->timer_ids_freelist; + ht->timer_ids_freelist = old_id; +} + +static void reheap_down(pj_timer_heap_t *ht, pj_timer_entry_dup *moved_node, size_t slot, size_t child) +{ + PJ_CHECK_STACK(); + + // Restore the heap property after a deletion. + + while (child < ht->cur_size) { + // Choose the smaller of the two children. + if (child + 1 < ht->cur_size && + PJ_TIME_VAL_LT(ht->heap[child + 1]->_timer_value, ht->heap[child]->_timer_value)) { + child++; + } + + // Perform a if the child has a larger timeout value than + // the . + if (PJ_TIME_VAL_LT(ht->heap[child]->_timer_value, moved_node->_timer_value)) { + copy_node(ht, slot, ht->heap[child]); + slot = child; + child = HEAP_LEFT(child); + } else + // We've found our location in the heap. + break; + } + + copy_node(ht, slot, moved_node); +} + +static void reheap_up(pj_timer_heap_t *ht, pj_timer_entry_dup *moved_node, size_t slot, size_t parent) +{ + // Restore the heap property after an insertion. + + while (slot > 0) { + // If the parent node is greater than the we need + // to copy it down. + if (PJ_TIME_VAL_LT(moved_node->_timer_value, ht->heap[parent]->_timer_value)) { + copy_node(ht, slot, ht->heap[parent]); + slot = parent; + parent = HEAP_PARENT(slot); + } else + break; + } + + // Insert the new node into its proper resting place in the heap and + // update the corresponding slot in the parallel array. + copy_node(ht, slot, moved_node); +} + +static pj_timer_entry_dup *remove_node(pj_timer_heap_t *ht, size_t slot) +{ + pj_timer_entry_dup *removed_node = ht->heap[slot]; + + // Return this timer id to the freelist. + push_freelist(ht, GET_FIELD(removed_node, _timer_id)); + + // Decrement the size of the heap by one since we're removing the + // "slot"th node. + ht->cur_size--; + + // Set the ID + if (GET_FIELD(removed_node, _timer_id) != GET_ENTRY(removed_node)->_timer_id) { +#if PJ_TIMER_DEBUG + PJ_LOG(3, (THIS_FILE, + "Bug! Trying to remove entry %p from %s " + "line %d, which has been deallocated " + "without being cancelled", + GET_ENTRY(removed_node), removed_node->src_file, removed_node->src_line)); +#else + PJ_LOG(3, (THIS_FILE, + "Bug! Trying to remove entry %p " + "which has been deallocated " + "without being cancelled", + GET_ENTRY(removed_node))); +#endif +#if ASSERT_IF_ENTRY_DESTROYED + pj_assert(removed_node->dup._timer_id == removed_node->entry->_timer_id); +#endif + } + GET_ENTRY(removed_node)->_timer_id = -1; + GET_FIELD(removed_node, _timer_id) = -1; + +#if !PJ_TIMER_USE_LINKED_LIST + // Only try to reheapify if we're not deleting the last entry. + + if (slot < ht->cur_size) { + pj_size_t parent; + pj_timer_entry_dup *moved_node = ht->heap[ht->cur_size]; + + // Move the end node to the location being removed and update + // the corresponding slot in the parallel array. + copy_node(ht, slot, moved_node); + + // If the time_value_> is great than or equal its + // parent it needs be moved down the heap. + parent = HEAP_PARENT(slot); + + if (PJ_TIME_VAL_GTE(moved_node->_timer_value, ht->heap[parent]->_timer_value)) { + reheap_down(ht, moved_node, slot, HEAP_LEFT(slot)); + } else { + reheap_up(ht, moved_node, slot, parent); + } + } +#else + pj_list_erase(removed_node); +#endif + + return removed_node; +} + +static pj_status_t grow_heap(pj_timer_heap_t *ht) +{ + // All the containers will double in size from max_size_ + size_t new_size = ht->max_size * 2; +#if PJ_TIMER_USE_COPY + pj_timer_entry_dup *new_timer_dups = 0; +#endif + pj_timer_id_t *new_timer_ids; + pj_size_t i; + pj_timer_entry_dup **new_heap = 0; + +#if PJ_TIMER_USE_LINKED_LIST + pj_timer_entry_dup *tmp_dup = NULL; + pj_timer_entry_dup *new_dup; +#endif + + PJ_LOG(6, (THIS_FILE, "Growing heap size from %d to %d", ht->max_size, new_size)); + + // First grow the heap itself. + new_heap = (pj_timer_entry_dup **)pj_pool_calloc(ht->pool, new_size, sizeof(pj_timer_entry_dup *)); + if (!new_heap) + return PJ_ENOMEM; + +#if PJ_TIMER_USE_COPY + // Grow the array of timer copies. + + new_timer_dups = (pj_timer_entry_dup *)pj_pool_alloc(ht->pool, sizeof(pj_timer_entry_dup) * new_size); + if (!new_timer_dups) + return PJ_ENOMEM; + + memcpy(new_timer_dups, ht->timer_dups, ht->max_size * sizeof(pj_timer_entry_dup)); + for (i = 0; i < ht->cur_size; i++) { + int idx = (int)(ht->heap[i] - ht->timer_dups); + // Point to the address in the new array + pj_assert(idx >= 0 && idx < (int)ht->max_size); + new_heap[i] = &new_timer_dups[idx]; + } + ht->timer_dups = new_timer_dups; +#else + memcpy(new_heap, ht->heap, ht->max_size * sizeof(pj_timer_entry *)); +#endif + +#if PJ_TIMER_USE_LINKED_LIST + tmp_dup = ht->head_list.next; + pj_list_init(&ht->head_list); + for (; tmp_dup != &ht->head_list; tmp_dup = tmp_dup->next) { + int slot = ht->timer_ids[GET_FIELD(tmp_dup, _timer_id)]; + new_dup = new_heap[slot]; + pj_list_push_back(&ht->head_list, new_dup); + } +#endif + + ht->heap = new_heap; + + // Grow the array of timer ids. + + new_timer_ids = 0; + new_timer_ids = (pj_timer_id_t *)pj_pool_alloc(ht->pool, new_size * sizeof(pj_timer_id_t)); + if (!new_timer_ids) + return PJ_ENOMEM; + + memcpy(new_timer_ids, ht->timer_ids, ht->max_size * sizeof(pj_timer_id_t)); + + // delete [] timer_ids_; + ht->timer_ids = new_timer_ids; + + // And add the new elements to the end of the "freelist". + for (i = ht->max_size; i < new_size; i++) + ht->timer_ids[i] = -((pj_timer_id_t)(i + 1)); + + ht->max_size = new_size; + + return PJ_SUCCESS; +} + +static pj_status_t insert_node(pj_timer_heap_t *ht, pj_timer_entry *new_node, const pj_time_val *future_time) +{ + pj_timer_entry_dup *timer_copy; + +#if PJ_TIMER_USE_LINKED_LIST + pj_timer_entry_dup *tmp_node = NULL; +#endif + + if (ht->cur_size + 2 >= ht->max_size) { + pj_status_t status = grow_heap(ht); + if (status != PJ_SUCCESS) + return status; + } + + timer_copy = GET_TIMER(ht, new_node); +#if PJ_TIMER_USE_COPY + // Create a duplicate of the timer entry. + pj_bzero(timer_copy, sizeof(*timer_copy)); + pj_memcpy(&timer_copy->dup, new_node, sizeof(*new_node)); + timer_copy->entry = new_node; +#endif + +#if PJ_TIMER_USE_LINKED_LIST + pj_list_init(timer_copy); +#endif + + timer_copy->_timer_value = *future_time; + +#if !PJ_TIMER_USE_LINKED_LIST + reheap_up(ht, timer_copy, ht->cur_size, HEAP_PARENT(ht->cur_size)); +#else + if (ht->cur_size == 0) { + pj_list_push_back(&ht->head_list, timer_copy); + } else if (PJ_TIME_VAL_GTE(*future_time, ht->head_list.prev->_timer_value)) { + /* Insert the max value to the end of the list. */ + pj_list_insert_before(&ht->head_list, timer_copy); + } else { + tmp_node = ht->head_list.next; + while (tmp_node->next != &ht->head_list && PJ_TIME_VAL_GT(*future_time, tmp_node->_timer_value)) { + tmp_node = tmp_node->next; + } + if (PJ_TIME_VAL_LT(*future_time, tmp_node->_timer_value)) { + pj_list_insert_before(tmp_node, timer_copy); + } else { + pj_list_insert_after(tmp_node, timer_copy); + } + } + copy_node(ht, new_node->_timer_id - 1, timer_copy); +#endif + ht->cur_size++; + + return PJ_SUCCESS; +} + +static pj_status_t schedule_entry(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *future_time) +{ + if (ht->cur_size < ht->max_size) { + // Obtain the next unique sequence number. + // Set the entry + entry->_timer_id = pop_freelist(ht); + + return insert_node(ht, entry, future_time); + } else + return -1; +} + +static int cancel(pj_timer_heap_t *ht, pj_timer_entry *entry, unsigned flags) +{ + long timer_node_slot; + + PJ_CHECK_STACK(); + + // Check to see if the timer_id is out of range + if (entry->_timer_id < 1 || (pj_size_t)entry->_timer_id >= ht->max_size) { + entry->_timer_id = -1; + return 0; + } + + timer_node_slot = ht->timer_ids[entry->_timer_id]; + + if (timer_node_slot < 0) { // Check to see if timer_id is still valid. + entry->_timer_id = -1; + return 0; + } + + if (entry != GET_ENTRY(ht->heap[timer_node_slot])) { + if ((flags & F_DONT_ASSERT) == 0) + pj_assert(entry == GET_ENTRY(ht->heap[timer_node_slot])); + entry->_timer_id = -1; + return 0; + } else { + remove_node(ht, timer_node_slot); + + if ((flags & F_DONT_CALL) == 0) { + // Call the close hook. + (*ht->callback)(ht, entry); + } + return 1; + } +} + +/* + * Calculate memory size required to create a timer heap. + */ +PJ_DEF(pj_size_t) pj_timer_heap_mem_size(pj_size_t count) +{ + return /* size of the timer heap itself: */ + sizeof(pj_timer_heap_t) + + /* size of each entry: */ + (count + 2) * (sizeof(pj_timer_entry_dup *) + sizeof(pj_timer_id_t) + sizeof(pj_timer_entry_dup)) + + /* lock, pool etc: */ + 132; +} + +/* + * Create a new timer heap. + */ +PJ_DEF(pj_status_t) pj_timer_heap_create(pj_pool_t *pool, pj_size_t size, pj_timer_heap_t **p_heap) +{ + pj_timer_heap_t *ht; + pj_size_t i; + + PJ_ASSERT_RETURN(pool && p_heap, PJ_EINVAL); + + *p_heap = NULL; + + /* Magic? */ + size += 2; + + /* Allocate timer heap data structure from the pool */ + ht = PJ_POOL_ZALLOC_T(pool, pj_timer_heap_t); + if (!ht) + return PJ_ENOMEM; + + /* Initialize timer heap sizes */ + ht->max_size = size; + ht->cur_size = 0; + ht->max_entries_per_poll = DEFAULT_MAX_TIMED_OUT_PER_POLL; + ht->timer_ids_freelist = 1; + ht->pool = pool; + + /* Lock. */ + ht->lock = NULL; + ht->auto_delete_lock = 0; + + // Create the heap array. + ht->heap = (pj_timer_entry_dup **)pj_pool_calloc(pool, size, sizeof(pj_timer_entry_dup *)); + if (!ht->heap) + return PJ_ENOMEM; + +#if PJ_TIMER_USE_COPY + // Create the timer entry copies array. + ht->timer_dups = (pj_timer_entry_dup *)pj_pool_alloc(pool, sizeof(pj_timer_entry_dup) * size); + if (!ht->timer_dups) + return PJ_ENOMEM; +#endif + + // Create the parallel + ht->timer_ids = (pj_timer_id_t *)pj_pool_alloc(pool, sizeof(pj_timer_id_t) * size); + if (!ht->timer_ids) + return PJ_ENOMEM; + + // Initialize the "freelist," which uses negative values to + // distinguish freelist elements from "pointers" into the + // array. + for (i = 0; i < size; ++i) + ht->timer_ids[i] = -((pj_timer_id_t)(i + 1)); + +#if PJ_TIMER_USE_LINKED_LIST + pj_list_init(&ht->head_list); +#endif + + *p_heap = ht; + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_timer_heap_destroy(pj_timer_heap_t *ht) +{ + if (ht->lock && ht->auto_delete_lock) { + pj_lock_destroy(ht->lock); + ht->lock = NULL; + } +} + +PJ_DEF(void) pj_timer_heap_set_lock(pj_timer_heap_t *ht, pj_lock_t *lock, pj_bool_t auto_del) +{ + if (ht->lock && ht->auto_delete_lock) + pj_lock_destroy(ht->lock); + + ht->lock = lock; + ht->auto_delete_lock = auto_del; +} + +PJ_DEF(unsigned) pj_timer_heap_set_max_timed_out_per_poll(pj_timer_heap_t *ht, unsigned count) +{ + unsigned old_count = ht->max_entries_per_poll; + ht->max_entries_per_poll = count; + return old_count; +} + +PJ_DEF(pj_timer_entry *) pj_timer_entry_init(pj_timer_entry *entry, int id, void *user_data, pj_timer_heap_callback *cb) +{ + pj_assert(entry && cb); + + entry->_timer_id = -1; + entry->id = id; + entry->user_data = user_data; + entry->cb = cb; +#if !PJ_TIMER_USE_COPY + entry->_grp_lock = NULL; +#endif + + return entry; +} + +PJ_DEF(pj_bool_t) pj_timer_entry_running(pj_timer_entry *entry) +{ + return (entry->_timer_id >= 1); +} + +#if PJ_TIMER_DEBUG +static pj_status_t schedule_w_grp_lock_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, + pj_bool_t set_id, int id_val, pj_grp_lock_t *grp_lock, const char *src_file, + int src_line) +#else +static pj_status_t schedule_w_grp_lock(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, + pj_bool_t set_id, int id_val, pj_grp_lock_t *grp_lock) +#endif +{ + pj_status_t status; + pj_time_val expires; + + PJ_ASSERT_RETURN(ht && entry && delay, PJ_EINVAL); + PJ_ASSERT_RETURN(entry->cb != NULL, PJ_EINVAL); + + /* Prevent same entry from being scheduled more than once */ + // PJ_ASSERT_RETURN(entry->_timer_id < 1, PJ_EINVALIDOP); + + pj_gettickcount(&expires); + PJ_TIME_VAL_ADD(expires, *delay); + + lock_timer_heap(ht); + + /* Prevent same entry from being scheduled more than once */ + if (pj_timer_entry_running(entry)) { + unlock_timer_heap(ht); + PJ_LOG(3, (THIS_FILE, "Warning! Rescheduling outstanding entry (%p)", entry)); + return PJ_EINVALIDOP; + } + + status = schedule_entry(ht, entry, &expires); + if (status == PJ_SUCCESS) { + pj_timer_entry_dup *timer_copy = GET_TIMER(ht, entry); + + if (set_id) + GET_FIELD(timer_copy, id) = entry->id = id_val; + timer_copy->_grp_lock = grp_lock; + if (timer_copy->_grp_lock) { + pj_grp_lock_add_ref(timer_copy->_grp_lock); + } +#if PJ_TIMER_DEBUG + timer_copy->src_file = src_file; + timer_copy->src_line = src_line; +#endif + } + unlock_timer_heap(ht); + + return status; +} + +#if PJ_TIMER_DEBUG +PJ_DEF(pj_status_t) +pj_timer_heap_schedule_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, const char *src_file, + int src_line) +{ + return schedule_w_grp_lock_dbg(ht, entry, delay, PJ_FALSE, 1, NULL, src_file, src_line); +} + +PJ_DEF(pj_status_t) +pj_timer_heap_schedule_w_grp_lock_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock, const char *src_file, int src_line) +{ + return schedule_w_grp_lock_dbg(ht, entry, delay, PJ_TRUE, id_val, grp_lock, src_file, src_line); +} + +#else +PJ_DEF(pj_status_t) pj_timer_heap_schedule(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay) +{ + return schedule_w_grp_lock(ht, entry, delay, PJ_FALSE, 1, NULL); +} + +PJ_DEF(pj_status_t) +pj_timer_heap_schedule_w_grp_lock(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock) +{ + return schedule_w_grp_lock(ht, entry, delay, PJ_TRUE, id_val, grp_lock); +} +#endif + +static int cancel_timer(pj_timer_heap_t *ht, pj_timer_entry *entry, unsigned flags, int id_val) +{ + pj_timer_entry_dup *timer_copy; + pj_grp_lock_t *grp_lock; + int count; + + PJ_ASSERT_RETURN(ht && entry, PJ_EINVAL); + + lock_timer_heap(ht); + timer_copy = GET_TIMER(ht, entry); + grp_lock = timer_copy->_grp_lock; + + count = cancel(ht, entry, flags | F_DONT_CALL); + if (count > 0) { + /* Timer entry found & cancelled */ + if (flags & F_SET_ID) { + entry->id = id_val; + } + if (grp_lock) { + pj_grp_lock_dec_ref(grp_lock); + } + } + unlock_timer_heap(ht); + + return count; +} + +PJ_DEF(int) pj_timer_heap_cancel(pj_timer_heap_t *ht, pj_timer_entry *entry) +{ + return cancel_timer(ht, entry, 0, 0); +} + +PJ_DEF(int) pj_timer_heap_cancel_if_active(pj_timer_heap_t *ht, pj_timer_entry *entry, int id_val) +{ + return cancel_timer(ht, entry, F_SET_ID | F_DONT_ASSERT, id_val); +} + +PJ_DEF(unsigned) pj_timer_heap_poll(pj_timer_heap_t *ht, pj_time_val *next_delay) +{ + pj_time_val now; + pj_time_val min_time_node = {0, 0}; + unsigned count; + pj_timer_id_t slot = 0; + + PJ_ASSERT_RETURN(ht, 0); + + lock_timer_heap(ht); + if (!ht->cur_size && next_delay) { + next_delay->sec = next_delay->msec = PJ_MAXINT32; + unlock_timer_heap(ht); + return 0; + } + + count = 0; + pj_gettickcount(&now); + + if (ht->cur_size) { +#if PJ_TIMER_USE_LINKED_LIST + slot = ht->timer_ids[GET_FIELD(ht->head_list.next, _timer_id)]; +#endif + min_time_node = ht->heap[slot]->_timer_value; + } + + while (ht->cur_size && PJ_TIME_VAL_LTE(min_time_node, now) && count < ht->max_entries_per_poll) { + pj_timer_entry_dup *node = remove_node(ht, slot); + pj_timer_entry *entry = GET_ENTRY(node); + /* Avoid re-use of this timer until the callback is done. */ + /// Not necessary, even causes problem (see also #2176). + /// pj_timer_id_t node_timer_id = pop_freelist(ht); + pj_grp_lock_t *grp_lock; + pj_bool_t valid = PJ_TRUE; + + ++count; + + grp_lock = node->_grp_lock; + node->_grp_lock = NULL; + if (GET_FIELD(node, cb) != entry->cb || GET_FIELD(node, user_data) != entry->user_data) { + valid = PJ_FALSE; +#if PJ_TIMER_DEBUG + PJ_LOG(3, (THIS_FILE, + "Bug! Polling entry %p from %s line %d has " + "been deallocated without being cancelled", + GET_ENTRY(node), node->src_file, node->src_line)); +#else + PJ_LOG(3, (THIS_FILE, + "Bug! Polling entry %p has " + "been deallocated without being cancelled", + GET_ENTRY(node))); +#endif +#if ASSERT_IF_ENTRY_DESTROYED + pj_assert(node->dup.cb == entry->cb); + pj_assert(node->dup.user_data == entry->user_data); +#endif + } + + unlock_timer_heap(ht); + + PJ_RACE_ME(5); + + if (valid && entry->cb) + (*entry->cb)(ht, entry); + + if (valid && grp_lock) + pj_grp_lock_dec_ref(grp_lock); + + lock_timer_heap(ht); + /* Now, the timer is really free for re-use. */ + /// push_freelist(ht, node_timer_id); + + if (ht->cur_size) { +#if PJ_TIMER_USE_LINKED_LIST + slot = ht->timer_ids[GET_FIELD(ht->head_list.next, _timer_id)]; +#endif + min_time_node = ht->heap[slot]->_timer_value; + } + } + if (ht->cur_size && next_delay) { + *next_delay = ht->heap[0]->_timer_value; + PJ_TIME_VAL_SUB(*next_delay, now); + if (next_delay->sec < 0 || next_delay->msec < 0) + next_delay->sec = next_delay->msec = 0; + } else if (next_delay) { + next_delay->sec = next_delay->msec = PJ_MAXINT32; + } + unlock_timer_heap(ht); + + return count; +} + +PJ_DEF(pj_size_t) pj_timer_heap_count(pj_timer_heap_t *ht) +{ + PJ_ASSERT_RETURN(ht, 0); + + return ht->cur_size; +} + +PJ_DEF(pj_status_t) pj_timer_heap_earliest_time(pj_timer_heap_t *ht, pj_time_val *timeval) +{ + pj_assert(ht->cur_size != 0); + if (ht->cur_size == 0) + return PJ_ENOTFOUND; + + lock_timer_heap(ht); + *timeval = ht->heap[0]->_timer_value; + unlock_timer_heap(ht); + + return PJ_SUCCESS; +} + +#if PJ_TIMER_DEBUG +PJ_DEF(void) pj_timer_heap_dump(pj_timer_heap_t *ht) +{ + lock_timer_heap(ht); + + PJ_LOG(3, (THIS_FILE, "Dumping timer heap:")); + PJ_LOG(3, (THIS_FILE, " Cur size: %d entries, max: %d", (int)ht->cur_size, (int)ht->max_size)); + + if (ht->cur_size) { +#if PJ_TIMER_USE_LINKED_LIST + pj_timer_entry_dup *tmp_dup; +#else + unsigned i; +#endif + pj_time_val now; + + PJ_LOG(3, (THIS_FILE, " Entries: ")); + PJ_LOG(3, (THIS_FILE, " _id\tId\tElapsed\tSource")); + PJ_LOG(3, (THIS_FILE, " ----------------------------------")); + + pj_gettickcount(&now); + +#if !PJ_TIMER_USE_LINKED_LIST + for (i = 0; i < (unsigned)ht->cur_size; ++i) { + pj_timer_entry_dup *e = ht->heap[i]; +#else + for (tmp_dup = ht->head_list.next; tmp_dup != &ht->head_list; tmp_dup = tmp_dup->next) { + pj_timer_entry_dup *e = tmp_dup; +#endif + + pj_time_val delta; + + if (PJ_TIME_VAL_LTE(e->_timer_value, now)) + delta.sec = delta.msec = 0; + else { + delta = e->_timer_value; + PJ_TIME_VAL_SUB(delta, now); + } + + PJ_LOG(3, (THIS_FILE, " %d\t%d\t%d.%03d\t%s:%d", GET_FIELD(e, _timer_id), GET_FIELD(e, id), + (int)delta.sec, (int)delta.msec, e->src_file, e->src_line)); + } + } + + unlock_timer_heap(ht); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/types.c b/src/tuya_p2p/pjproject/pjlib/src/pj/types.c new file mode 100755 index 000000000..15534baf5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/types.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pj_time_val_normalize(pj_time_val *t) +{ + PJ_CHECK_STACK(); + + if (t->msec >= 1000) { + t->sec += (t->msec / 1000); + t->msec = (t->msec % 1000); + } else if (t->msec <= -1000) { + do { + t->sec--; + t->msec += 1000; + } while (t->msec <= -1000); + } + + if (t->sec >= 1 && t->msec < 0) { + t->sec--; + t->msec += 1000; + + } else if (t->sec < 0 && t->msec > 0) { + t->sec++; + t->msec -= 1000; + } +} diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h new file mode 100755 index 000000000..ad7029f4d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_H__ +#define __PJMEDIA_H__ + +/** + * @file pjmedia.h + * @brief PJMEDIA main header file. + */ +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +#include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +#include +#include +//#include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +//#include +// #include +// #include +// #include +// #include + +#endif /* __PJMEDIA_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h new file mode 100755 index 000000000..e9e27285c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h @@ -0,0 +1,1587 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_CONFIG_H__ +#define __PJMEDIA_CONFIG_H__ + +/** + * @file pjmedia/config.h Compile time config + * @brief Contains some compile time constants. + */ +#include + +/** + * @defgroup PJMEDIA_BASE Base Types and Configurations + */ + +/** + * @defgroup PJMEDIA_CONFIG Compile time configuration + * @ingroup PJMEDIA_BASE + * @brief Some compile time configuration settings. + * @{ + */ + +/* + * Include config_auto.h if autoconf is used (PJ_AUTOCONF is set) + */ +#if defined(PJ_AUTOCONF) +#include +#endif + +/** + * Initial memory block for media endpoint. + */ +#ifndef PJMEDIA_POOL_LEN_ENDPT +#define PJMEDIA_POOL_LEN_ENDPT 512 +#endif + +/** + * Memory increment for media endpoint. + */ +#ifndef PJMEDIA_POOL_INC_ENDPT +#define PJMEDIA_POOL_INC_ENDPT 512 +#endif + +/** + * Initial memory block for event manager. + */ +#ifndef PJMEDIA_POOL_LEN_EVTMGR +#define PJMEDIA_POOL_LEN_EVTMGR 500 +#endif + +/** + * Memory increment for evnt manager. + */ +#ifndef PJMEDIA_POOL_INC_EVTMGR +#define PJMEDIA_POOL_INC_EVTMGR 500 +#endif + +/** + * Specify whether we prefer to use audio switch board rather than + * conference bridge. + * + * Audio switch board is a kind of simplified version of conference + * bridge, but not really the subset of conference bridge. It has + * stricter rules on audio routing among the pjmedia ports and has + * no audio mixing capability. The power of it is it could work with + * encoded audio frames where conference brigde couldn't. + * + * Default: 0 + */ +#ifndef PJMEDIA_CONF_USE_SWITCH_BOARD +#define PJMEDIA_CONF_USE_SWITCH_BOARD 0 +#endif + +/** + * Specify buffer size for audio switch board, in bytes. This buffer will + * be used for transmitting/receiving audio frame data (and some overheads, + * i.e: pjmedia_frame structure) among conference ports in the audio + * switch board. For example, if a port uses PCM format @44100Hz mono + * and frame time 20ms, the PCM audio data will require 1764 bytes, + * so with overhead, a safe buffer size will be ~1900 bytes. + * + * Default: PJMEDIA_MAX_MTU + */ +#ifndef PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE +#define PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE PJMEDIA_MAX_MTU +#endif + +/** + * Specify whether the conference bridge uses AGC, an automatic adjustment to + * avoid dramatic change in the signal level which can cause noise. + * + * Default: 1 (enabled) + */ +#ifndef PJMEDIA_CONF_USE_AGC +#define PJMEDIA_CONF_USE_AGC 1 +#endif + +/* + * Types of sound stream backends. + */ + +/** + * This macro has been deprecated in releasee 1.1. Please see + * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information. + */ +#if defined(PJMEDIA_SOUND_IMPLEMENTATION) +#error PJMEDIA_SOUND_IMPLEMENTATION has been deprecated +#endif + +/** + * This macro has been deprecated in releasee 1.1. Please see + * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information. + */ +#if defined(PJMEDIA_PREFER_DIRECT_SOUND) +#error PJMEDIA_PREFER_DIRECT_SOUND has been deprecated +#endif + +/** + * This macro controls whether the legacy sound device API is to be + * implemented, for applications that still use the old sound device + * API (sound.h). If this macro is set to non-zero, the sound_legacy.c + * will be included in the compilation. The sound_legacy.c is an + * implementation of old sound device (sound.h) using the new Audio + * Device API. + * + * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more + * info. + */ +#ifndef PJMEDIA_HAS_LEGACY_SOUND_API +#define PJMEDIA_HAS_LEGACY_SOUND_API 1 +#endif + +/** + * Specify default sound device latency, in milisecond. + */ +#ifndef PJMEDIA_SND_DEFAULT_REC_LATENCY +#define PJMEDIA_SND_DEFAULT_REC_LATENCY 100 +#endif + +/** + * Specify default sound device latency, in milisecond. + * + * Default is 160ms for Windows Mobile and 140ms for other platforms. + */ +#ifndef PJMEDIA_SND_DEFAULT_PLAY_LATENCY +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 +#define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 160 +#else +#define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 140 +#endif +#endif + +/* + * Types of WSOLA backend algorithm. + */ + +/** + * This denotes implementation of WSOLA using null algorithm. Expansion + * will generate zero frames, and compression will just discard some + * samples from the input. + * + * This type of implementation may be used as it requires the least + * processing power. + */ +#define PJMEDIA_WSOLA_IMP_NULL 0 + +/** + * This denotes implementation of WSOLA using fixed or floating point WSOLA + * algorithm. This implementation provides the best quality of the result, + * at the expense of one frame delay and intensive processing power + * requirement. + */ +#define PJMEDIA_WSOLA_IMP_WSOLA 1 + +/** + * This denotes implementation of WSOLA algorithm with faster waveform + * similarity calculation. This implementation provides fair quality of + * the result with the main advantage of low processing power requirement. + */ +#define PJMEDIA_WSOLA_IMP_WSOLA_LITE 2 + +/** + * Specify type of Waveform based Similarity Overlap and Add (WSOLA) backend + * implementation to be used. WSOLA is an algorithm to expand and/or compress + * audio frames without changing the pitch, and used by the delaybuf and as PLC + * backend algorithm. + * + * Default is PJMEDIA_WSOLA_IMP_WSOLA + */ +#ifndef PJMEDIA_WSOLA_IMP +#define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA +#endif + +/** + * Specify the default maximum duration of synthetic audio that is generated + * by WSOLA. This value should be long enough to cover burst of packet losses. + * but not too long, because as the duration increases the quality would + * degrade considerably. + * + * Note that this limit is only applied when fading is enabled in the WSOLA + * session. + * + * Default: 80 + */ +#ifndef PJMEDIA_WSOLA_MAX_EXPAND_MSEC +#define PJMEDIA_WSOLA_MAX_EXPAND_MSEC 80 +#endif + +/** + * Specify WSOLA template length, in milliseconds. The longer the template, + * the smoother signal to be generated at the expense of more computation + * needed, since the algorithm will have to compare more samples to find + * the most similar pitch. + * + * Default: 5 + */ +#ifndef PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC +#define PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC 5 +#endif + +/** + * Specify WSOLA algorithm delay, in milliseconds. The algorithm delay is + * used to merge synthetic samples with real samples in the transition + * between real to synthetic and vice versa. The longer the delay, the + * smoother signal to be generated, at the expense of longer latency and + * a slighty more computation. + * + * Default: 5 + */ +#ifndef PJMEDIA_WSOLA_DELAY_MSEC +#define PJMEDIA_WSOLA_DELAY_MSEC 5 +#endif + +/** + * Set this to non-zero to disable fade-out/in effect in the PLC when it + * instructs WSOLA to generate synthetic frames. The use of fading may + * or may not improve the quality of audio, depending on the nature of + * packet loss and the type of audio input (e.g. speech vs music). + * Disabling fading also implicitly remove the maximum limit of synthetic + * audio samples generated by WSOLA (see PJMEDIA_WSOLA_MAX_EXPAND_MSEC). + * + * Default: 0 + */ +#ifndef PJMEDIA_WSOLA_PLC_NO_FADING +#define PJMEDIA_WSOLA_PLC_NO_FADING 0 +#endif + +/** + * Limit the number of calls by stream to the PLC to generate synthetic + * frames to this duration. If packets are still lost after this maximum + * duration, silence will be generated by the stream instead. Since the + * PLC normally should have its own limit on the maximum duration of + * synthetic frames to be generated (for PJMEDIA's PLC, the limit is + * PJMEDIA_WSOLA_MAX_EXPAND_MSEC), we can set this value to a large number + * to give additional flexibility should the PLC wants to do something + * clever with the lost frames. + * + * Default: 240 ms + */ +#ifndef PJMEDIA_MAX_PLC_DURATION_MSEC +#define PJMEDIA_MAX_PLC_DURATION_MSEC 240 +#endif + +/** + * Specify number of sound buffers. Larger number is better for sound + * stability and to accommodate sound devices that are unable to send frames + * in timely manner, however it would probably cause more audio delay (and + * definitely will take more memory). One individual buffer is normally 10ms + * or 20 ms long, depending on ptime settings (samples_per_frame value). + * + * The setting here currently is used by the conference bridge, the splitter + * combiner port, and dsound.c. + * + * Default: (PJMEDIA_SND_DEFAULT_PLAY_LATENCY+20)/20 + */ +#ifndef PJMEDIA_SOUND_BUFFER_COUNT +#define PJMEDIA_SOUND_BUFFER_COUNT ((PJMEDIA_SND_DEFAULT_PLAY_LATENCY + 20) / 20) +#endif + +/** + * Specify which A-law/U-law conversion algorithm to use. + * By default the conversion algorithm uses A-law/U-law table which gives + * the best performance, at the expense of 33 KBytes of static data. + * If this option is disabled, a smaller but slower algorithm will be used. + */ +#ifndef PJMEDIA_HAS_ALAW_ULAW_TABLE +#define PJMEDIA_HAS_ALAW_ULAW_TABLE 1 +#endif + +/** + * Unless specified otherwise, G711 codec is included by default. + */ +#ifndef PJMEDIA_HAS_G711_CODEC +#define PJMEDIA_HAS_G711_CODEC 1 +#endif + +/* + * Warn about obsolete macros. + * + * PJMEDIA_HAS_SMALL_FILTER has been deprecated in 0.7. + */ +#if defined(PJMEDIA_HAS_SMALL_FILTER) +#ifdef _MSC_VER +#pragma message("Warning: PJMEDIA_HAS_SMALL_FILTER macro is deprecated" \ + " and has no effect") +#else +#warning "PJMEDIA_HAS_SMALL_FILTER macro is deprecated and has no effect" +#endif +#endif + +/* + * Warn about obsolete macros. + * + * PJMEDIA_HAS_LARGE_FILTER has been deprecated in 0.7. + */ +#if defined(PJMEDIA_HAS_LARGE_FILTER) +#ifdef _MSC_VER +#pragma message("Warning: PJMEDIA_HAS_LARGE_FILTER macro is deprecated" \ + " and has no effect") +#else +#warning "PJMEDIA_HAS_LARGE_FILTER macro is deprecated" +#endif +#endif + +/* + * These macros are obsolete in 0.7.1 so it will trigger compilation error. + * Please use PJMEDIA_RESAMPLE_IMP to select the resample implementation + * to use. + */ +#ifdef PJMEDIA_HAS_LIBRESAMPLE +#error "PJMEDIA_HAS_LIBRESAMPLE macro is deprecated. Use '#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE'" +#endif + +#ifdef PJMEDIA_HAS_SPEEX_RESAMPLE +#error "PJMEDIA_HAS_SPEEX_RESAMPLE macro is deprecated. Use '#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_SPEEX'" +#endif + +/* + * Sample rate conversion backends. + * Select one of these backends in PJMEDIA_RESAMPLE_IMP. + */ +#define PJMEDIA_RESAMPLE_NONE 1 /**< No resampling. */ +#define PJMEDIA_RESAMPLE_LIBRESAMPLE \ + 2 /**< Sample rate conversion \ + using libresample. */ +#define PJMEDIA_RESAMPLE_SPEEX \ + 3 /**< Sample rate conversion \ + using Speex. */ +#define PJMEDIA_RESAMPLE_LIBSAMPLERATE \ + 4 /**< Sample rate conversion \ + using libsamplerate \ + (a.k.a Secret Rabbit Code) \ + */ + +/** + * Select which resample implementation to use. Currently pjmedia supports: + * - #PJMEDIA_RESAMPLE_LIBRESAMPLE, to use libresample-1.7, this is the default + * implementation to be used. + * - #PJMEDIA_RESAMPLE_LIBSAMPLERATE, to use libsamplerate implementation + * (a.k.a. Secret Rabbit Code). + * - #PJMEDIA_RESAMPLE_SPEEX, to use sample rate conversion in Speex library. + * - #PJMEDIA_RESAMPLE_NONE, to disable sample rate conversion. Any calls to + * resample function will return error. + * + * Default is PJMEDIA_RESAMPLE_LIBRESAMPLE + */ +#ifndef PJMEDIA_RESAMPLE_IMP +#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE +#endif + +/** + * Specify whether libsamplerate, when used, should be linked statically + * into the application. This option is only useful for Visual Studio + * projects, and when this static linking is enabled + */ + +/** + * Default file player/writer buffer size. + */ +#ifndef PJMEDIA_FILE_PORT_BUFSIZE +#define PJMEDIA_FILE_PORT_BUFSIZE 4000 +#endif + +/** + * Maximum frame duration (in msec) to be supported. + * This (among other thing) will affect the size of buffers to be allocated + * for outgoing packets. + */ +#ifndef PJMEDIA_MAX_FRAME_DURATION_MS +#define PJMEDIA_MAX_FRAME_DURATION_MS 200 +#endif + +/** + * Max packet size for transmitting direction. + */ +#ifndef PJMEDIA_MAX_MTU +#define PJMEDIA_MAX_MTU 1500 +#endif + +/** + * Max packet size for receiving direction. + */ +#ifndef PJMEDIA_MAX_MRU +#define PJMEDIA_MAX_MRU 2000 +#endif + +/** + * DTMF/telephone-event duration, in timestamp. To specify the duration in + * milliseconds, use the setting PJMEDIA_DTMF_DURATION_MSEC instead. + */ +#ifndef PJMEDIA_DTMF_DURATION +#define PJMEDIA_DTMF_DURATION 1600 /* in timestamp */ +#endif + +/** + * DTMF/telephone-event duration, in milliseconds. If the value is greater + * than zero, than this setting will be used instead of PJMEDIA_DTMF_DURATION. + * + * Note that for a clockrate of 8 KHz, a dtmf duration of 1600 timestamp + * units (the default value of PJMEDIA_DTMF_DURATION) is equivalent to 200 ms. + */ +#ifndef PJMEDIA_DTMF_DURATION_MSEC +#define PJMEDIA_DTMF_DURATION_MSEC 0 +#endif + +/** + * Number of RTP packets received from different source IP address from the + * remote address required to make the stream switch transmission + * to the source address. + */ +#ifndef PJMEDIA_RTP_NAT_PROBATION_CNT +#define PJMEDIA_RTP_NAT_PROBATION_CNT 10 +#endif + +/** + * Number of RTCP packets received from different source IP address from the + * remote address required to make the stream switch RTCP transmission + * to the source address. + */ +#ifndef PJMEDIA_RTCP_NAT_PROBATION_CNT +#define PJMEDIA_RTCP_NAT_PROBATION_CNT 3 +#endif + +/** + * Specify whether RTCP should be advertised in SDP. This setting would + * affect whether RTCP candidate will be added in SDP when ICE is used. + * Application might want to disable RTCP advertisement in SDP to + * reduce the message size. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_ADVERTISE_RTCP +#define PJMEDIA_ADVERTISE_RTCP 1 +#endif + +/** + * Interval to send regular RTCP packets, in msec. + */ +#ifndef PJMEDIA_RTCP_INTERVAL +#define PJMEDIA_RTCP_INTERVAL 5000 /* msec*/ +#endif + +/** + * Minimum interval between two consecutive outgoing RTCP-FB packets, + * such as Picture Loss Indication, in msec. + */ +#ifndef PJMEDIA_RTCP_FB_INTERVAL +#define PJMEDIA_RTCP_FB_INTERVAL 50 /* msec*/ +#endif + +/** + * Tell RTCP to ignore the first N packets when calculating the + * jitter statistics. From experimentation, the first few packets + * (25 or so) have relatively big jitter, possibly because during + * this time, the program is also busy setting up the signaling, + * so they make the average jitter big. + * + * Default: 25. + */ +#ifndef PJMEDIA_RTCP_IGNORE_FIRST_PACKETS +#define PJMEDIA_RTCP_IGNORE_FIRST_PACKETS 25 +#endif + +/** + * Specify whether RTCP statistics includes raw jitter statistics. + * Raw jitter is defined as absolute value of network transit time + * difference of two consecutive packets; refering to "difference D" + * term in interarrival jitter calculation in RFC 3550 section 6.4.1. + * + * Default: 0 (no). + */ +#ifndef PJMEDIA_RTCP_STAT_HAS_RAW_JITTER +#define PJMEDIA_RTCP_STAT_HAS_RAW_JITTER 0 +#endif + +/** + * Specify the factor with wich RTCP RTT statistics should be normalized + * if exceptionally high. For e.g. mobile networks with potentially large + * fluctuations, this might be unwanted. + * + * Use (0) to disable this feature. + * + * Default: 3. + */ +#ifndef PJMEDIA_RTCP_NORMALIZE_FACTOR +#define PJMEDIA_RTCP_NORMALIZE_FACTOR 3 +#endif + +/** + * Specify whether RTCP statistics includes IP Delay Variation statistics. + * IPDV is defined as network transit time difference of two consecutive + * packets. The IPDV statistic can be useful to inspect clock skew existance + * and level, e.g: when the IPDV mean values were stable in positive numbers, + * then the remote clock (used in sending RTP packets) is faster than local + * system clock. Ideally, the IPDV mean values are always equal to 0. + * + * Default: 0 (no). + */ +#ifndef PJMEDIA_RTCP_STAT_HAS_IPDV +#define PJMEDIA_RTCP_STAT_HAS_IPDV 0 +#endif + +/** + * Specify whether RTCP XR support should be built into PJMEDIA. Disabling + * this feature will reduce footprint slightly. Note that even when this + * setting is enabled, RTCP XR processing will only be performed in stream + * if it is enabled on run-time on per stream basis. See + * PJMEDIA_STREAM_ENABLE_XR setting for more info. + * + * Default: 0 (no). + */ +#ifndef PJMEDIA_HAS_RTCP_XR +#define PJMEDIA_HAS_RTCP_XR 0 +#endif + +/** + * The RTCP XR feature is activated and used by stream if \a enable_rtcp_xr + * field of \a pjmedia_stream_info structure is non-zero. This setting + * controls the default value of this field. + * + * Default: 0 (disabled) + */ +#ifndef PJMEDIA_STREAM_ENABLE_XR +#define PJMEDIA_STREAM_ENABLE_XR 0 +#endif + +/** + * Specify the buffer length for storing any received RTCP SDES text + * in a stream session. Usually RTCP contains only the mandatory SDES + * field, i.e: CNAME. + * + * Default: 64 bytes. + */ +#ifndef PJMEDIA_RTCP_RX_SDES_BUF_LEN +#define PJMEDIA_RTCP_RX_SDES_BUF_LEN 64 +#endif + +/** + * Specify the maximum number of RTCP Feedback capability definition. + * + * Default: 16 + */ +#ifndef PJMEDIA_RTCP_FB_MAX_CAP +#define PJMEDIA_RTCP_FB_MAX_CAP 16 +#endif + +/** + * Specify how long (in miliseconds) the stream should suspend the + * silence detector/voice activity detector (VAD) during the initial + * period of the session. This feature is useful to open bindings in + * all NAT routers between local and remote endpoint since most NATs + * do not allow incoming packet to get in before local endpoint sends + * outgoing packets. + * + * Specify zero to disable this feature. + * + * Default: 600 msec (which gives good probability that some RTP + * packets will reach the destination, but without + * filling up the jitter buffer on the remote end). + */ +#ifndef PJMEDIA_STREAM_VAD_SUSPEND_MSEC +#define PJMEDIA_STREAM_VAD_SUSPEND_MSEC 600 +#endif + +/** + * Perform RTP payload type checking in the audio stream. Normally the peer + * MUST send RTP with payload type as we specified in our SDP. Certain + * agents may not be able to follow this hence the only way to have + * communication is to disable this check. + * + * Default: 1 + */ +#ifndef PJMEDIA_STREAM_CHECK_RTP_PT +#define PJMEDIA_STREAM_CHECK_RTP_PT 1 +#endif + +/** + * Reserve some space for application extra data, e.g: SRTP auth tag, + * in RTP payload, so the total payload length will not exceed the MTU. + */ +#ifndef PJMEDIA_STREAM_RESV_PAYLOAD_LEN +#define PJMEDIA_STREAM_RESV_PAYLOAD_LEN 20 +#endif + +/** + * Specify the maximum duration of silence period in the codec, in msec. + * This is useful for example to keep NAT binding open in the firewall + * and to prevent server from disconnecting the call because no + * RTP packet is received. + * + * This only applies to codecs that use PJMEDIA's VAD such as G711, GSM, + * iLBC, G722, G722.1, L16. Some other codecs, such as Speex, Opus, G729, + * have their own VAD/DTX mechanism will not be affected by this setting. + * + * Use (-1) to disable this feature. + * + * Default: 5000 ms + */ +#ifndef PJMEDIA_CODEC_MAX_SILENCE_PERIOD +#define PJMEDIA_CODEC_MAX_SILENCE_PERIOD 5000 +#endif + +/** + * Suggested or default threshold to be set for fixed silence detection + * or as starting threshold for adaptive silence detection. The threshold + * has the range from zero to 0xFFFF. + */ +#ifndef PJMEDIA_SILENCE_DET_THRESHOLD +#define PJMEDIA_SILENCE_DET_THRESHOLD 4 +#endif + +/** + * Maximum silence threshold in the silence detector. The silence detector + * will not cut the audio transmission if the audio level is above this + * level. + * + * Use 0x10000 (or greater) to disable this feature. + * + * Default: 0x10000 (disabled) + */ +#ifndef PJMEDIA_SILENCE_DET_MAX_THRESHOLD +#define PJMEDIA_SILENCE_DET_MAX_THRESHOLD 0x10000 +#endif + +/** + * Speex Accoustic Echo Cancellation (AEC). + * By default is enabled. + */ +#ifndef PJMEDIA_HAS_SPEEX_AEC +#define PJMEDIA_HAS_SPEEX_AEC 1 +#endif + +/** + * Specify whether Automatic Gain Control (AGC) should also be enabled in + * Speex AEC. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_SPEEX_AEC_USE_AGC +#define PJMEDIA_SPEEX_AEC_USE_AGC 1 +#endif + +/** + * Specify whether denoise should also be enabled in Speex AEC. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_SPEEX_AEC_USE_DENOISE +#define PJMEDIA_SPEEX_AEC_USE_DENOISE 1 +#endif + +/** + * WebRtc Accoustic Echo Cancellation (AEC). + * By default is disabled. + */ +#ifndef PJMEDIA_HAS_WEBRTC_AEC +#define PJMEDIA_HAS_WEBRTC_AEC 0 +#endif + +/** + * Specify whether WebRtc EC should use its mobile version AEC. + * + * Default: 0 (no) + */ +#ifndef PJMEDIA_WEBRTC_AEC_USE_MOBILE +#define PJMEDIA_WEBRTC_AEC_USE_MOBILE 0 +#endif + +/** + * Maximum number of parameters in SDP fmtp attribute. + * + * Default: 16 + */ +#ifndef PJMEDIA_CODEC_MAX_FMTP_CNT +#define PJMEDIA_CODEC_MAX_FMTP_CNT 16 +#endif + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should rather use the codec preference as set by + * remote, or should it rather use the codec preference as specified by + * local endpoint. + * + * For example, suppose incoming call has codec order "8 0 3", while + * local codec order is "3 0 8". If remote codec order is preferable, + * the selected codec will be 8, while if local codec order is preferable, + * the selected codec will be 3. + * + * If set to non-zero, the negotiator will use the codec order as specified + * by remote in the offer. + * + * Note that this behavior can be changed during run-time by calling + * pjmedia_sdp_neg_set_prefer_remote_codec_order(). + * + * Default is 1 (to maintain backward compatibility) + */ +#ifndef PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER +#define PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER 1 +#endif + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should answer with multiple formats or not. + * + * Note that this behavior can be changed during run-time by calling + * pjmedia_sdp_neg_set_allow_multiple_codecs(). + * + * Default is 0 (to maintain backward compatibility) + */ +#ifndef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS +#define PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS 0 +#endif + +/** + * This specifies the maximum number of the customized SDP format + * negotiation callbacks. + */ +#ifndef PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB +#define PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB 8 +#endif + +/** + * This specifies if the SDP negotiator should rewrite answer payload + * type numbers to use the same payload type numbers as the remote offer + * for all matched codecs. + * + * Default is 1 (yes) + */ +#ifndef PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT +#define PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT 1 +#endif + +/** + * This specifies if the SDP negotiator should compare its content before + * incrementing the origin version on the subsequent offer/answer. + * If this is set to 1, origin version will only by incremented if the + * new offer/answer is different than the previous one. For backward + * compatibility and performance this is set to 0. + * + * Default is 0 (No) + */ +#ifndef PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION +#define PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION 0 +#endif + +/** + * Support for sending and decoding RTCP port in SDP (RFC 3605). + * Default is equal to PJMEDIA_ADVERTISE_RTCP setting. + */ +#ifndef PJMEDIA_HAS_RTCP_IN_SDP +#define PJMEDIA_HAS_RTCP_IN_SDP (PJMEDIA_ADVERTISE_RTCP) +#endif + +/** + * This macro controls whether pjmedia should include SDP + * bandwidth modifier "TIAS" (RFC3890). + * + * Note that there is also a run-time variable to turn this setting + * on or off, defined in endpoint.c. To access this variable, use + * the following construct + * + \verbatim + extern pj_bool_t pjmedia_add_bandwidth_tias_in_sdp; + + // Do not enable bandwidth information inclusion in sdp + pjmedia_add_bandwidth_tias_in_sdp = PJ_FALSE; + \endverbatim + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP +#define PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP 1 +#endif + +/** + * This macro controls whether pjmedia should include SDP rtpmap + * attribute for static payload types. SDP rtpmap for static + * payload types are optional, although they are normally included + * for interoperability reason. + * + * Note that there is also a run-time variable to turn this setting + * on or off, defined in endpoint.c. To access this variable, use + * the following construct + * + \verbatim + extern pj_bool_t pjmedia_add_rtpmap_for_static_pt; + + // Do not include rtpmap for static payload types (<96) + pjmedia_add_rtpmap_for_static_pt = PJ_FALSE; + \endverbatim + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT +#define PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT 1 +#endif + +/** + * This macro declares the start payload type for telephone-event + * that is advertised by PJMEDIA for outgoing SDP. If this macro + * is set to zero, telephone events would not be advertised nor + * supported. + */ +#ifndef PJMEDIA_RTP_PT_TELEPHONE_EVENTS +#define PJMEDIA_RTP_PT_TELEPHONE_EVENTS 120 +#endif + +/** + * This macro declares whether PJMEDIA should generate multiple + * telephone-event formats in SDP offer, i.e: one for each audio codec + * clock rate (see also ticket #2088). If this macro is set to zero, only + * one telephone event format will be generated and it uses clock rate 8kHz + * (old behavior before ticket #2088). + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_TELEPHONE_EVENT_ALL_CLOCKRATES +#define PJMEDIA_TELEPHONE_EVENT_ALL_CLOCKRATES 1 +#endif + +/** + * Maximum tones/digits that can be enqueued in the tone generator. + */ +#ifndef PJMEDIA_TONEGEN_MAX_DIGITS +#define PJMEDIA_TONEGEN_MAX_DIGITS 32 +#endif + +/* + * Below specifies the various tone generator backend algorithm. + */ + +/** + * The math's sine(), floating point. This has very good precision + * but it's the slowest and requires floating point support and + * linking with the math library. + */ +#define PJMEDIA_TONEGEN_SINE 1 + +/** + * Floating point approximation of sine(). This has relatively good + * precision and much faster than plain sine(), but it requires floating- + * point support and linking with the math library. + */ +#define PJMEDIA_TONEGEN_FLOATING_POINT 2 + +/** + * Fixed point using sine signal generated by Cordic algorithm. This + * algorithm can be tuned to provide balance between precision and + * performance by tuning the PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP + * setting, and may be suitable for platforms that lack floating-point + * support. + */ +#define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC 3 + +/** + * Fast fixed point using some approximation to generate sine waves. + * The tone generated by this algorithm is not very precise, however + * the algorithm is very fast. + */ +#define PJMEDIA_TONEGEN_FAST_FIXED_POINT 4 + +/** + * Specify the tone generator algorithm to be used. Please see + * http://trac.pjsip.org/repos/wiki/Tone_Generator for the performance + * analysis results of the various tone generator algorithms. + * + * Default value: + * - PJMEDIA_TONEGEN_FLOATING_POINT when PJ_HAS_FLOATING_POINT is set + * - PJMEDIA_TONEGEN_FIXED_POINT_CORDIC when PJ_HAS_FLOATING_POINT is not set + */ +#ifndef PJMEDIA_TONEGEN_ALG +#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT +#define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FLOATING_POINT +#else +#define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FIXED_POINT_CORDIC +#endif +#endif + +/** + * Specify the number of calculation loops to generate the tone, when + * PJMEDIA_TONEGEN_FIXED_POINT_CORDIC algorithm is used. With more calculation + * loops, the tone signal gets more precise, but this will add more + * processing. + * + * Valid values are 1 to 28. + * + * Default value: 10 + */ +#ifndef PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP +#define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP 10 +#endif + +/** + * Enable high quality of tone generation, the better quality will cost + * more CPU load. This is only applied to floating point enabled machines. + * + * By default it is enabled when PJ_HAS_FLOATING_POINT is set. + * + * This macro has been deprecated in version 1.0-rc3. + */ +#ifdef PJMEDIA_USE_HIGH_QUALITY_TONEGEN +#error "The PJMEDIA_USE_HIGH_QUALITY_TONEGEN macro is obsolete" +#endif + +/** + * Fade-in duration for the tone, in milliseconds. Set to zero to disable + * this feature. + * + * Default: 1 (msec) + */ +#ifndef PJMEDIA_TONEGEN_FADE_IN_TIME +#define PJMEDIA_TONEGEN_FADE_IN_TIME 1 +#endif + +/** + * Fade-out duration for the tone, in milliseconds. Set to zero to disable + * this feature. + * + * Default: 2 (msec) + */ +#ifndef PJMEDIA_TONEGEN_FADE_OUT_TIME +#define PJMEDIA_TONEGEN_FADE_OUT_TIME 2 +#endif + +/** + * The default tone generator amplitude (1-32767). + * + * Default value: 12288 + */ +#ifndef PJMEDIA_TONEGEN_VOLUME +#define PJMEDIA_TONEGEN_VOLUME 12288 +#endif + +/** + * Enable support for SRTP media transport. This will require linking + * with libsrtp from the third_party directory. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_HAS_SRTP +#define PJMEDIA_HAS_SRTP 1 +#endif + +/** + * Enable session description for SRTP keying. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_SRTP_HAS_SDES +#define PJMEDIA_SRTP_HAS_SDES 1 +#endif + +/** + * Enable DTLS for SRTP keying. + * + * Default value: 0 (disabled) + */ +#ifndef PJMEDIA_SRTP_HAS_DTLS +#define PJMEDIA_SRTP_HAS_DTLS 0 +#endif + +/** + * Set OpenSSL ciphers for DTLS-SRTP. + * + * Default value: "DEFAULT" + */ +#ifndef PJMEDIA_SRTP_DTLS_OSSL_CIPHERS +#define PJMEDIA_SRTP_DTLS_OSSL_CIPHERS "DEFAULT" +#endif + +/** + * Maximum number of SRTP cryptos. + * + * Default: 16 + */ +#ifndef PJMEDIA_SRTP_MAX_CRYPTOS +#define PJMEDIA_SRTP_MAX_CRYPTOS 16 +#endif + +/** + * Enable AES_CM_256 cryptos in SRTP. + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_CM_256 +#define PJMEDIA_SRTP_HAS_AES_CM_256 1 +#endif + +/** + * Enable AES_CM_192 cryptos in SRTP. + * It was reported that this crypto only works among libsrtp backends, + * so we recommend to disable this. + * + * To enable this, you would require OpenSSL which supports it. + * See https://github.com/pjsip/pjproject/issues/1943 for more info. + * + * Default: disabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_CM_192 +#define PJMEDIA_SRTP_HAS_AES_CM_192 0 +#endif + +/** + * Enable AES_CM_128 cryptos in SRTP. + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_CM_128 +#define PJMEDIA_SRTP_HAS_AES_CM_128 1 +#endif + +/** + * Enable AES_GCM_256 cryptos in SRTP. + * + * To enable this, you would require OpenSSL which supports it. + * See https://github.com/pjsip/pjproject/issues/1943 for more info. + * + * Default: disabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_GCM_256 +#define PJMEDIA_SRTP_HAS_AES_GCM_256 0 +#endif + +/** + * Enable AES_GCM_128 cryptos in SRTP. + * + * To enable this, you would require OpenSSL which supports it. + * See https://github.com/pjsip/pjproject/issues/1943 for more info. + * + * Default: disabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_GCM_128 +#define PJMEDIA_SRTP_HAS_AES_GCM_128 0 +#endif + +/** + * Specify whether SRTP needs to handle condition that old packets with + * incorect RTP seq are still coming when SRTP is restarted. + * + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_CHECK_RTP_SEQ_ON_RESTART +#define PJMEDIA_SRTP_CHECK_RTP_SEQ_ON_RESTART 1 +#endif + +/** + * Specify whether SRTP needs to handle condition that remote may reset + * or maintain ROC when SRTP is restarted. + * + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_CHECK_ROC_ON_RESTART +#define PJMEDIA_SRTP_CHECK_ROC_ON_RESTART 1 +#endif + +/** + * Let the library handle libsrtp initialization and deinitialization. + * Application may want to disable this and manually perform libsrtp + * initialization and deinitialization when it needs to use libsrtp + * before the library is initialized or after the library is shutdown. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT +#define PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT 1 +#endif + +/** + * Enable support to handle codecs with inconsistent clock rate + * between clock rate in SDP/RTP & the clock rate that is actually used. + * This happens for example with G.722 and MPEG audio codecs. + * See: + * - G.722 : RFC 3551 4.5.2 + * - MPEG audio : RFC 3551 4.5.13 & RFC 3119 + * - OPUS : RFC 7587 + * + * Also when this feature is enabled, some handling will be performed + * to deal with clock rate incompatibilities of some phones. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_HANDLE_G722_MPEG_BUG +#define PJMEDIA_HANDLE_G722_MPEG_BUG 1 +#endif + +/* Setting to determine if media transport should switch RTP and RTCP + * remote address to the source address of the packets it receives. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_TRANSPORT_SWITCH_REMOTE_ADDR +#define PJMEDIA_TRANSPORT_SWITCH_REMOTE_ADDR 1 +#endif + +/** + * Transport info (pjmedia_transport_info) contains a socket info and list + * of transport specific info, since transports can be chained together + * (for example, SRTP transport uses UDP transport as the underlying + * transport). This constant specifies maximum number of transport specific + * infos that can be held in a transport info. + */ +#ifndef PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT +#define PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT 4 +#endif + +/** + * Maximum size in bytes of storage buffer of a transport specific info. + */ +#ifndef PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE +#define PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE (50 * sizeof(long)) +#endif + +/** + * Value to be specified in PJMEDIA_STREAM_ENABLE_KA setting. + * This indicates that an empty RTP packet should be used as + * the keep-alive packet. + */ +#define PJMEDIA_STREAM_KA_EMPTY_RTP 1 + +/** + * Value to be specified in PJMEDIA_STREAM_ENABLE_KA setting. + * This indicates that a user defined packet should be used + * as the keep-alive packet. The content of the user-defined + * packet is specified by PJMEDIA_STREAM_KA_USER_PKT. Default + * content is a CR-LF packet. + */ +#define PJMEDIA_STREAM_KA_USER 2 + +/** + * The content of the user defined keep-alive packet. The format + * of the packet is initializer to pj_str_t structure. Note that + * the content may contain NULL character. + */ +#ifndef PJMEDIA_STREAM_KA_USER_PKT +#define PJMEDIA_STREAM_KA_USER_PKT \ + { \ + "\r\n", 2 \ + } +#endif + +/** + * Specify another type of keep-alive and NAT hole punching + * mechanism (the other type is PJMEDIA_STREAM_VAD_SUSPEND_MSEC + * and PJMEDIA_CODEC_MAX_SILENCE_PERIOD) to be used by stream. + * When this feature is enabled, the stream will initially + * transmit one packet to punch a hole in NAT, and periodically + * transmit keep-alive packets. + * + * When this alternative keep-alive mechanism is used, application + * may disable the other keep-alive mechanisms, i.e: by setting + * PJMEDIA_STREAM_VAD_SUSPEND_MSEC to zero and + * PJMEDIA_CODEC_MAX_SILENCE_PERIOD to -1. + * + * The value of this macro specifies the type of packet used + * for the keep-alive mechanism. Valid values are + * PJMEDIA_STREAM_KA_EMPTY_RTP and PJMEDIA_STREAM_KA_USER. + * + * The duration of the keep-alive interval further can be set + * with PJMEDIA_STREAM_KA_INTERVAL setting. + * + * Default: 0 (disabled) + */ +#ifndef PJMEDIA_STREAM_ENABLE_KA +#define PJMEDIA_STREAM_ENABLE_KA 0 +#endif + +/** + * Specify the keep-alive interval of PJMEDIA_STREAM_ENABLE_KA + * mechanism, in seconds. + * + * Default: 5 seconds + */ +#ifndef PJMEDIA_STREAM_KA_INTERVAL +#define PJMEDIA_STREAM_KA_INTERVAL 5 +#endif + +/** + * Specify the number of keep-alive needed to be sent after the stream is + * created. + * + * Setting this to 0 will disable it. + * + * Default : 2 + */ +#ifndef PJMEDIA_STREAM_START_KA_CNT +#define PJMEDIA_STREAM_START_KA_CNT 2 +#endif + +/** + * Specify the interval to send keep-alive after the stream is created, + * in msec. + * + * Default : 1000 + */ +#ifndef PJMEDIA_STREAM_START_KA_INTERVAL_MSEC +#define PJMEDIA_STREAM_START_KA_INTERVAL_MSEC 1000 +#endif + +/** + * Specify the number of identical consecutive error that will be ignored when + * receiving RTP/RTCP data before the library tries to restart the transport. + * + * When receiving RTP/RTCP data, the library will ignore error besides + * PJ_EPENDING or PJ_ECANCELLED and continue the loop to receive the data. + * If the OS always return error, then the loop will continue non stop. + * This setting will limit the number of the identical consecutive error, + * before the library start to restart the transport. If error still happens + * after transport restart, then PJMEDIA_EVENT_MEDIA_TP_ERR event will be + * publish as a notification. + * + * If PJ_ESOCKETSTOP is raised, then transport will be restarted regardless + * of this setting. + * + * To always ignore the error when receving RTP/RTCP, set this to 0. + * + * Default : 20 + */ +#ifndef PJMEDIA_IGNORE_RECV_ERR_CNT +#define PJMEDIA_IGNORE_RECV_ERR_CNT 20 +#endif + +/* + * .... new stuffs ... + */ + +/* + * Video + */ + +/** + * Top level option to enable/disable video features. + * + * Default: 0 (disabled) + */ +#ifndef PJMEDIA_HAS_VIDEO +#define PJMEDIA_HAS_VIDEO 0 +#endif + +/** + * Specify if FFMPEG is available. The value here will be used as the default + * value for other FFMPEG settings below. + * + * Default: 0 + */ +#ifndef PJMEDIA_HAS_FFMPEG +#define PJMEDIA_HAS_FFMPEG 0 +#endif + +/** + * Specify if FFMPEG libavformat is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVFORMAT +#define PJMEDIA_HAS_LIBAVFORMAT PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libavformat is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVCODEC +#define PJMEDIA_HAS_LIBAVCODEC PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libavutil is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVUTIL +#define PJMEDIA_HAS_LIBAVUTIL PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libswscale is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBSWSCALE +#define PJMEDIA_HAS_LIBSWSCALE PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libavdevice is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVDEVICE +#define PJMEDIA_HAS_LIBAVDEVICE PJMEDIA_HAS_FFMPEG +#endif + +/** + * Maximum video planes. + * + * Default: 4 + */ +#ifndef PJMEDIA_MAX_VIDEO_PLANES +#define PJMEDIA_MAX_VIDEO_PLANES 4 +#endif + +/** + * Maximum number of video formats. + * + * Default: 32 + */ +#ifndef PJMEDIA_MAX_VIDEO_FORMATS +#define PJMEDIA_MAX_VIDEO_FORMATS 32 +#endif + +/** + * Specify the maximum time difference (in ms) for synchronization between + * two medias. If the synchronization media source is ahead of time + * greater than this duration, it is considered to make a very large jump + * and the synchronization will be reset. + * + * Default: 20000 + */ +#ifndef PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC +#define PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC 20000 +#endif + +/** + * Maximum video frame size. + * Default: 128kB + */ +#ifndef PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE +#define PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE (1 << 17) +#endif + +/** + * Specify the maximum duration (in ms) for resynchronization. When a media + * is late to another media it is supposed to be synchronized to, it is + * guaranteed to be synchronized again after this duration. While if the + * media is ahead/early by t ms, it is guaranteed to be synchronized after + * t + this duration. This timing only applies if there is no additional + * resynchronization required during the specified duration. + * + * Default: 2000 + */ +#ifndef PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION +#define PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION 2000 +#endif + +/** + * Minimum gap between two consecutive discards in jitter buffer, + * in milliseconds. + * + * Default: 200 ms + */ +#ifndef PJMEDIA_JBUF_DISC_MIN_GAP +#define PJMEDIA_JBUF_DISC_MIN_GAP 200 +#endif + +/** + * Minimum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 1 frame + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MIN_BURST +#define PJMEDIA_JBUF_PRO_DISC_MIN_BURST 1 +#endif + +/** + * Maximum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 200 frames + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MAX_BURST +#define PJMEDIA_JBUF_PRO_DISC_MAX_BURST 100 +#endif + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or lower than + * PJMEDIA_JBUF_PRO_DISC_MIN_BURST, in milliseconds. + * + * Default: 2000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T1 +#define PJMEDIA_JBUF_PRO_DISC_T1 2000 +#endif + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or greater than + * PJMEDIA_JBUF_PRO_DISC_MAX_BURST, in milliseconds. + * + * Default: 10000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T2 +#define PJMEDIA_JBUF_PRO_DISC_T2 10000 +#endif + +/** + * Reset jitter buffer and return silent audio on stream playback start + * (first get_frame()). This is useful to avoid possible noise that may be + * introduced by discard algorithm and neutralize latency when audio device + * is started later than the stream. + * + * Set this to N>0 to allow N silent audio frames returned on stream playback + * start, this will allow about N frames to be buffered in the jitter buffer + * before the playback is started (prefetching effect). + * Set this to zero to disable this feature. + * + * Default: 1 + */ +#ifndef PJMEDIA_STREAM_SOFT_START +#define PJMEDIA_STREAM_SOFT_START 1 +#endif + +/** + * Video stream will discard old picture from the jitter buffer as soon as + * new picture is received, to reduce latency. + * + * Default: 0 + */ +#ifndef PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY +#define PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY 0 +#endif + +/** + * Maximum video payload size. Note that this must not be greater than + * PJMEDIA_MAX_MTU. + * + * Default: (PJMEDIA_MAX_MTU - 20 - (128+16)) if SRTP is enabled, + * otherwise (PJMEDIA_MAX_MTU - 20). + * Note that (128+16) constant value is taken from libSRTP macro + * SRTP_MAX_TRAILER_LEN. + */ +#ifndef PJMEDIA_MAX_VID_PAYLOAD_SIZE +#if PJMEDIA_HAS_SRTP +#define PJMEDIA_MAX_VID_PAYLOAD_SIZE (PJMEDIA_MAX_MTU - 20 - (128 + 16)) +#else +#define PJMEDIA_MAX_VID_PAYLOAD_SIZE (PJMEDIA_MAX_MTU - 20) +#endif +#endif + +/** + * Specify target value for socket receive buffer size. It will be + * applied to RTP socket of media transport using setsockopt(). When + * transport failed to set the specified size, it will try with lower + * value until the highest possible is successfully set. + * + * Setting this to zero will leave the socket receive buffer size to + * OS default (e.g: usually 8 KB on desktop platforms). + * + * Default: 64 KB when video is enabled, otherwise zero (OS default) + */ +#ifndef PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE +#if PJMEDIA_HAS_VIDEO +#define PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE (64 * 1024) +#else +#define PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE 0 +#endif +#endif + +/** + * Specify target value for socket send buffer size. It will be + * applied to RTP socket of media transport using setsockopt(). When + * transport failed to set the specified size, it will try with lower + * value until the highest possible is successfully set. + * + * Setting this to zero will leave the socket send buffer size to + * OS default (e.g: usually 8 KB on desktop platforms). + * + * Default: 64 KB when video is enabled, otherwise zero (OS default) + */ +#ifndef PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE +#if PJMEDIA_HAS_VIDEO +#define PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE (64 * 1024) +#else +#define PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE 0 +#endif +#endif + +/** + * Specify if libyuv is available. + * + * Default: 0 (disable) + */ +#ifndef PJMEDIA_HAS_LIBYUV +#define PJMEDIA_HAS_LIBYUV 0 +#endif + +/** + * Specify if dtmf flash in RFC 2833 is available. + */ +#ifndef PJMEDIA_HAS_DTMF_FLASH +#define PJMEDIA_HAS_DTMF_FLASH 1 +#endif + +/** + * Specify the number of keyframe needed to be sent after the stream is + * created. Setting this to 0 will disable it. + * + * Default : 5 + */ +#ifndef PJMEDIA_VID_STREAM_START_KEYFRAME_CNT +#define PJMEDIA_VID_STREAM_START_KEYFRAME_CNT 5 +#endif + +/** + * Specify the interval to send keyframe after the stream is created, in msec. + * + * Default : 1000 + */ +#ifndef PJMEDIA_VID_STREAM_START_KEYFRAME_INTERVAL_MSEC +#define PJMEDIA_VID_STREAM_START_KEYFRAME_INTERVAL_MSEC 1000 +#endif + +/** + * Specify the minimum interval to send video keyframe, in msec. + * + * Default : 1000 + */ +#ifndef PJMEDIA_VID_STREAM_MIN_KEYFRAME_INTERVAL_MSEC +#define PJMEDIA_VID_STREAM_MIN_KEYFRAME_INTERVAL_MSEC 1000 +#endif + +/** + * Specify minimum delay of video decoding, in milliseconds. Lower value may + * degrade video quality significantly in a bad network environment (e.g: + * with persistent late and out-of-order RTP packets). Note that the value + * must be lower than jitter buffer maximum delay (configurable via + * pjmedia_stream_info.jb_max or pjsua_media_config.jb_max). + * + * Default : 100 + */ +#ifndef PJMEDIA_VID_STREAM_DECODE_MIN_DELAY_MSEC +#define PJMEDIA_VID_STREAM_DECODE_MIN_DELAY_MSEC 100 +#endif + +/** + * Perform RTP payload type checking in the video stream. Normally the peer + * MUST send RTP with payload type as we specified in our SDP. Certain + * agents may not be able to follow this hence the only way to have + * communication is to disable this check. + * + * Default: PJMEDIA_STREAM_CHECK_RTP_PT (follow audio stream's setting) + */ +#ifndef PJMEDIA_VID_STREAM_CHECK_RTP_PT +#define PJMEDIA_VID_STREAM_CHECK_RTP_PT PJMEDIA_STREAM_CHECK_RTP_PT +#endif + +/** + * @} + */ + +#endif /* __PJMEDIA_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h new file mode 100755 index 000000000..ca1aa92b7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_ERRNO_H__ +#define __PJMEDIA_ERRNO_H__ + +/** + * @file errno.h Error Codes + * @brief PJMEDIA specific error codes. + */ + +#include +#include + +/** + * @defgroup PJMEDIA_ERRNO Error Codes + * @ingroup PJMEDIA_BASE + * @brief PJMEDIA specific error codes. + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + */ +#define PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE) +#define PJMEDIA_ERRNO_END (PJMEDIA_ERRNO_START + PJ_ERRNO_SPACE_SIZE - 1) + +/** + * Mapping from PortAudio error codes to pjmedia error space. + */ +#define PJMEDIA_PORTAUDIO_ERRNO_START (PJMEDIA_ERRNO_END - 10000) +#define PJMEDIA_PORTAUDIO_ERRNO_END (PJMEDIA_PORTAUDIO_ERRNO_START + 10000 - 1) +/** + * Convert PortAudio error code to PJMEDIA error code. + * PortAudio error code range: 0 >= err >= -10000 + */ +#define PJMEDIA_ERRNO_FROM_PORTAUDIO(err) ((int)PJMEDIA_PORTAUDIO_ERRNO_START - err) + +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + +/** + * Mapping from LibSRTP error codes to pjmedia error space. + */ +#define PJMEDIA_LIBSRTP_ERRNO_START (PJMEDIA_ERRNO_END - 10200) +#define PJMEDIA_LIBSRTP_ERRNO_END (PJMEDIA_LIBSRTP_ERRNO_START + 200 - 1) +/** + * Convert LibSRTP error code to PJMEDIA error code. + * LibSRTP error code range: 0 <= err < 200 + */ +#define PJMEDIA_ERRNO_FROM_LIBSRTP(err) (PJMEDIA_LIBSRTP_ERRNO_START + err) + +#endif + +/************************************************************ + * GENERIC/GENERAL PJMEDIA ERRORS + ***********************************************************/ +/** + * @hideinitializer + * General/unknown PJMEDIA error. + */ +#define PJMEDIA_ERROR (PJMEDIA_ERRNO_START + 1) /* 220001 */ + +/************************************************************ + * SDP ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Generic invalid SDP descriptor. + */ +#define PJMEDIA_SDP_EINSDP (PJMEDIA_ERRNO_START + 20) /* 220020 */ +/** + * @hideinitializer + * Invalid SDP version. + */ +#define PJMEDIA_SDP_EINVER (PJMEDIA_ERRNO_START + 21) /* 220021 */ +/** + * @hideinitializer + * Invalid SDP origin (o=) line. + */ +#define PJMEDIA_SDP_EINORIGIN (PJMEDIA_ERRNO_START + 22) /* 220022 */ +/** + * @hideinitializer + * Invalid SDP time (t=) line. + */ +#define PJMEDIA_SDP_EINTIME (PJMEDIA_ERRNO_START + 23) /* 220023 */ +/** + * @hideinitializer + * Empty SDP subject/name (s=) line. + */ +#define PJMEDIA_SDP_EINNAME (PJMEDIA_ERRNO_START + 24) /* 220024 */ +/** + * @hideinitializer + * Invalid SDP connection info (c=) line. + */ +#define PJMEDIA_SDP_EINCONN (PJMEDIA_ERRNO_START + 25) /* 220025 */ +/** + * @hideinitializer + * Missing SDP connection info line. + */ +#define PJMEDIA_SDP_EMISSINGCONN (PJMEDIA_ERRNO_START + 26) /* 220026 */ +/** + * @hideinitializer + * Invalid attribute (a=) line. + */ +#define PJMEDIA_SDP_EINATTR (PJMEDIA_ERRNO_START + 27) /* 220027 */ +/** + * @hideinitializer + * Invalid rtpmap attribute. + */ +#define PJMEDIA_SDP_EINRTPMAP (PJMEDIA_ERRNO_START + 28) /* 220028 */ +/** + * @hideinitializer + * rtpmap attribute is too long. + */ +#define PJMEDIA_SDP_ERTPMAPTOOLONG (PJMEDIA_ERRNO_START + 29) /* 220029 */ +/** + * @hideinitializer + * rtpmap is missing for dynamic payload type. + */ +#define PJMEDIA_SDP_EMISSINGRTPMAP (PJMEDIA_ERRNO_START + 30) /* 220030 */ +/** + * @hideinitializer + * Invalid SDP media (m=) line. + */ +#define PJMEDIA_SDP_EINMEDIA (PJMEDIA_ERRNO_START + 31) /* 220031 */ +/** + * @hideinitializer + * No payload format in the media stream. + */ +#define PJMEDIA_SDP_ENOFMT (PJMEDIA_ERRNO_START + 32) /* 220032 */ +/** + * @hideinitializer + * Invalid payload type in media. + */ +#define PJMEDIA_SDP_EINPT (PJMEDIA_ERRNO_START + 33) /* 220033 */ +/** + * @hideinitializer + * Invalid SDP "fmtp" attribute. + */ +#define PJMEDIA_SDP_EINFMTP (PJMEDIA_ERRNO_START + 34) /* 220034 */ +/** + * @hideinitializer + * Invalid SDP "rtcp" attribute. + */ +#define PJMEDIA_SDP_EINRTCP (PJMEDIA_ERRNO_START + 35) /* 220035 */ +/** + * @hideinitializer + * Invalid SDP media transport protocol. + */ +#define PJMEDIA_SDP_EINPROTO (PJMEDIA_ERRNO_START + 36) /* 220036 */ +/** + * @hideinitializer + * Invalid SDP bandwidth info (b=) line. + */ +#define PJMEDIA_SDP_EINBANDW (PJMEDIA_ERRNO_START + 37) /* 220037 */ +/** + * @hideinitializer + * Invalid SDP "ssrc" attribute. + */ +#define PJMEDIA_SDP_EINSSRC (PJMEDIA_ERRNO_START + 38) /* 220038 */ + +/************************************************************ + * SDP NEGOTIATOR ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Invalid state to perform the specified operation. + */ +#define PJMEDIA_SDPNEG_EINSTATE (PJMEDIA_ERRNO_START + 40) /* 220040 */ +/** + * @hideinitializer + * No initial local SDP. + */ +#define PJMEDIA_SDPNEG_ENOINITIAL (PJMEDIA_ERRNO_START + 41) /* 220041 */ +/** + * @hideinitializer + * No currently active SDP. + */ +#define PJMEDIA_SDPNEG_ENOACTIVE (PJMEDIA_ERRNO_START + 42) /* 220042 */ +/** + * @hideinitializer + * No current offer or answer. + */ +#define PJMEDIA_SDPNEG_ENONEG (PJMEDIA_ERRNO_START + 43) /* 220043 */ +/** + * @hideinitializer + * Media count mismatch in offer and answer. + */ +#define PJMEDIA_SDPNEG_EMISMEDIA (PJMEDIA_ERRNO_START + 44) /* 220044 */ +/** + * @hideinitializer + * Media type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSMEDIA (PJMEDIA_ERRNO_START + 45) /* 220045 */ +/** + * @hideinitializer + * Transport type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSTP (PJMEDIA_ERRNO_START + 46) /* 220046 */ +/** + * @hideinitializer + * No common media payload is provided in the answer. + */ +#define PJMEDIA_SDPNEG_EANSNOMEDIA (PJMEDIA_ERRNO_START + 47) /* 220047 */ +/** + * @hideinitializer + * No media is active after negotiation. + */ +#define PJMEDIA_SDPNEG_ENOMEDIA (PJMEDIA_ERRNO_START + 48) /* 220048 */ +/** + * @hideinitializer + * No suitable codec for remote offer. + */ +#define PJMEDIA_SDPNEG_NOANSCODEC (PJMEDIA_ERRNO_START + 49) /* 220049 */ +/** + * @hideinitializer + * No suitable telephone-event for remote offer. + */ +#define PJMEDIA_SDPNEG_NOANSTELEVENT (PJMEDIA_ERRNO_START + 50) /* 220050 */ +/** + * @hideinitializer + * No suitable answer for unknown remote offer. + */ +#define PJMEDIA_SDPNEG_NOANSUNKNOWN (PJMEDIA_ERRNO_START + 51) /* 220051 */ + +/************************************************************ + * SDP COMPARISON STATUS + ***********************************************************/ +/** + * @hideinitializer + * SDP media stream not equal. + */ +#define PJMEDIA_SDP_EMEDIANOTEQUAL (PJMEDIA_ERRNO_START + 60) /* 220060 */ +/** + * @hideinitializer + * Port number in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_EPORTNOTEQUAL (PJMEDIA_ERRNO_START + 61) /* 220061 */ +/** + * @hideinitializer + * Transport in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_ETPORTNOTEQUAL (PJMEDIA_ERRNO_START + 62) /* 220062 */ +/** + * @hideinitializer + * Media format in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_EFORMATNOTEQUAL (PJMEDIA_ERRNO_START + 63) /* 220063 */ +/** + * @hideinitializer + * SDP connection description not equal. + */ +#define PJMEDIA_SDP_ECONNNOTEQUAL (PJMEDIA_ERRNO_START + 64) /* 220064 */ +/** + * @hideinitializer + * SDP attributes not equal. + */ +#define PJMEDIA_SDP_EATTRNOTEQUAL (PJMEDIA_ERRNO_START + 65) /* 220065 */ +/** + * @hideinitializer + * SDP media direction not equal. + */ +#define PJMEDIA_SDP_EDIRNOTEQUAL (PJMEDIA_ERRNO_START + 66) /* 220066 */ +/** + * @hideinitializer + * SDP fmtp attribute not equal. + */ +#define PJMEDIA_SDP_EFMTPNOTEQUAL (PJMEDIA_ERRNO_START + 67) /* 220067 */ +/** + * @hideinitializer + * SDP ftpmap attribute not equal. + */ +#define PJMEDIA_SDP_ERTPMAPNOTEQUAL (PJMEDIA_ERRNO_START + 68) /* 220068 */ +/** + * @hideinitializer + * SDP session descriptor not equal. + */ +#define PJMEDIA_SDP_ESESSNOTEQUAL (PJMEDIA_ERRNO_START + 69) /* 220069 */ +/** + * @hideinitializer + * SDP origin not equal. + */ +#define PJMEDIA_SDP_EORIGINNOTEQUAL (PJMEDIA_ERRNO_START + 70) /* 220070 */ +/** + * @hideinitializer + * SDP name/subject not equal. + */ +#define PJMEDIA_SDP_ENAMENOTEQUAL (PJMEDIA_ERRNO_START + 71) /* 220071 */ +/** + * @hideinitializer + * SDP time not equal. + */ +#define PJMEDIA_SDP_ETIMENOTEQUAL (PJMEDIA_ERRNO_START + 72) /* 220072 */ + +/************************************************************ + * CODEC + ***********************************************************/ +/** + * @hideinitializer + * Unsupported codec. + */ +#define PJMEDIA_CODEC_EUNSUP (PJMEDIA_ERRNO_START + 80) /* 220080 */ +/** + * @hideinitializer + * Codec internal creation error. + */ +#define PJMEDIA_CODEC_EFAILED (PJMEDIA_ERRNO_START + 81) /* 220081 */ +/** + * @hideinitializer + * Codec frame is too short. + */ +#define PJMEDIA_CODEC_EFRMTOOSHORT (PJMEDIA_ERRNO_START + 82) /* 220082 */ +/** + * @hideinitializer + * PCM buffer is too short. + */ +#define PJMEDIA_CODEC_EPCMTOOSHORT (PJMEDIA_ERRNO_START + 83) /* 220083 */ +/** + * @hideinitializer + * Invalid codec frame length. + */ +#define PJMEDIA_CODEC_EFRMINLEN (PJMEDIA_ERRNO_START + 84) /* 220084 */ +/** + * @hideinitializer + * Invalid PCM frame length. + */ +#define PJMEDIA_CODEC_EPCMFRMINLEN (PJMEDIA_ERRNO_START + 85) /* 220085 */ +/** + * @hideinitializer + * Invalid mode. + */ +#define PJMEDIA_CODEC_EINMODE (PJMEDIA_ERRNO_START + 86) /* 220086 */ +/** + * @hideinitializer + * Bad or corrupted bitstream. + */ +#define PJMEDIA_CODEC_EBADBITSTREAM (PJMEDIA_ERRNO_START + 87) /* 220087 */ + +/************************************************************ + * MEDIA + ***********************************************************/ +/** + * @hideinitializer + * Invalid remote IP address (in SDP). + */ +#define PJMEDIA_EINVALIDIP (PJMEDIA_ERRNO_START + 100) /* 220100 */ +/** + * @hideinitializer + * Asymetric codec is not supported. + */ +#define PJMEDIA_EASYMCODEC (PJMEDIA_ERRNO_START + 101) /* 220101 */ +/** + * @hideinitializer + * Invalid payload type. + */ +#define PJMEDIA_EINVALIDPT (PJMEDIA_ERRNO_START + 102) /* 220102 */ +/** + * @hideinitializer + * Missing rtpmap. + */ +#define PJMEDIA_EMISSINGRTPMAP (PJMEDIA_ERRNO_START + 103) /* 220103 */ +/** + * @hideinitializer + * Invalid media type. + */ +#define PJMEDIA_EINVALIMEDIATYPE (PJMEDIA_ERRNO_START + 104) /* 220104 */ +/** + * @hideinitializer + * Remote does not support DTMF. + */ +#define PJMEDIA_EREMOTENODTMF (PJMEDIA_ERRNO_START + 105) /* 220105 */ +/** + * @hideinitializer + * Invalid DTMF digit. + */ +#define PJMEDIA_RTP_EINDTMF (PJMEDIA_ERRNO_START + 106) /* 220106 */ +/** + * @hideinitializer + * Remote does not support RFC 2833 + */ +#define PJMEDIA_RTP_EREMNORFC2833 (PJMEDIA_ERRNO_START + 107) /* 220107 */ +/** + * @hideinitializer + * Invalid or bad format + */ +#define PJMEDIA_EBADFMT (PJMEDIA_ERRNO_START + 108) /* 220108 */ +/** + * @hideinitializer + * Unsupported media type. + */ +#define PJMEDIA_EUNSUPMEDIATYPE (PJMEDIA_ERRNO_START + 109) /* 220109 */ + +/************************************************************ + * RTP SESSION ERRORS + ***********************************************************/ +/** + * @hideinitializer + * General invalid RTP packet error. + */ +#define PJMEDIA_RTP_EINPKT (PJMEDIA_ERRNO_START + 120) /* 220120 */ +/** + * @hideinitializer + * Invalid RTP packet packing. + */ +#define PJMEDIA_RTP_EINPACK (PJMEDIA_ERRNO_START + 121) /* 220121 */ +/** + * @hideinitializer + * Invalid RTP packet version. + */ +#define PJMEDIA_RTP_EINVER (PJMEDIA_ERRNO_START + 122) /* 220122 */ +/** + * @hideinitializer + * RTP SSRC id mismatch. + */ +#define PJMEDIA_RTP_EINSSRC (PJMEDIA_ERRNO_START + 123) /* 220123 */ +/** + * @hideinitializer + * RTP payload type mismatch. + */ +#define PJMEDIA_RTP_EINPT (PJMEDIA_ERRNO_START + 124) /* 220124 */ +/** + * @hideinitializer + * Invalid RTP packet length. + */ +#define PJMEDIA_RTP_EINLEN (PJMEDIA_ERRNO_START + 125) /* 220125 */ +/** + * @hideinitializer + * RTP session restarted. + */ +#define PJMEDIA_RTP_ESESSRESTART (PJMEDIA_ERRNO_START + 130) /* 220130 */ +/** + * @hideinitializer + * RTP session in probation + */ +#define PJMEDIA_RTP_ESESSPROBATION (PJMEDIA_ERRNO_START + 131) /* 220131 */ +/** + * @hideinitializer + * Bad RTP sequence number + */ +#define PJMEDIA_RTP_EBADSEQ (PJMEDIA_ERRNO_START + 132) /* 220132 */ +/** + * @hideinitializer + * RTP media port destination is not configured + */ +#define PJMEDIA_RTP_EBADDEST (PJMEDIA_ERRNO_START + 133) /* 220133 */ +/** + * @hideinitializer + * RTP is not configured. + */ +#define PJMEDIA_RTP_ENOCONFIG (PJMEDIA_ERRNO_START + 134) /* 220134 */ + +/************************************************************ + * PORT ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Generic incompatible port error. + */ +#define PJMEDIA_ENOTCOMPATIBLE (PJMEDIA_ERRNO_START + 160) /* 220160 */ +/** + * @hideinitializer + * Incompatible clock rate + */ +#define PJMEDIA_ENCCLOCKRATE (PJMEDIA_ERRNO_START + 161) /* 220161 */ +/** + * @hideinitializer + * Incompatible samples per frame + */ +#define PJMEDIA_ENCSAMPLESPFRAME (PJMEDIA_ERRNO_START + 162) /* 220162 */ +/** + * @hideinitializer + * Incompatible media type + */ +#define PJMEDIA_ENCTYPE (PJMEDIA_ERRNO_START + 163) /* 220163 */ +/** + * @hideinitializer + * Incompatible bits per sample + */ +#define PJMEDIA_ENCBITS (PJMEDIA_ERRNO_START + 164) /* 220164 */ +/** + * @hideinitializer + * Incompatible bytes per frame + */ +#define PJMEDIA_ENCBYTES (PJMEDIA_ERRNO_START + 165) /* 220165 */ +/** + * @hideinitializer + * Incompatible number of channels + */ +#define PJMEDIA_ENCCHANNEL (PJMEDIA_ERRNO_START + 166) /* 220166 */ + +/************************************************************ + * FILE ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Not a valid WAVE file. + */ +#define PJMEDIA_ENOTVALIDWAVE (PJMEDIA_ERRNO_START + 180) /* 220180 */ +/** + * @hideinitializer + * Unsupported WAVE file. + */ +#define PJMEDIA_EWAVEUNSUPP (PJMEDIA_ERRNO_START + 181) /* 220181 */ +/** + * @hideinitializer + * Wave file too short. + */ +#define PJMEDIA_EWAVETOOSHORT (PJMEDIA_ERRNO_START + 182) /* 220182 */ +/** + * @hideinitializer + * Sound frame is too large for file buffer. + */ +#define PJMEDIA_EFRMFILETOOBIG (PJMEDIA_ERRNO_START + 183) /* 220183 */ +/** + * @hideinitializer + * Unsupported AVI file. + */ +#define PJMEDIA_EAVIUNSUPP (PJMEDIA_ERRNO_START + 191) /* 220191 */ + +/************************************************************ + * SOUND DEVICE ERRORS + ***********************************************************/ +/** + * @hideinitializer + * No suitable audio capture device. + */ +#define PJMEDIA_ENOSNDREC (PJMEDIA_ERRNO_START + 200) /* 220200 */ +/** + * @hideinitializer + * No suitable audio playback device. + */ +#define PJMEDIA_ENOSNDPLAY (PJMEDIA_ERRNO_START + 201) /* 220201 */ +/** + * @hideinitializer + * Invalid sound device ID. + */ +#define PJMEDIA_ESNDINDEVID (PJMEDIA_ERRNO_START + 202) /* 220202 */ +/** + * @hideinitializer + * Invalid sample format for sound device. + */ +#define PJMEDIA_ESNDINSAMPLEFMT (PJMEDIA_ERRNO_START + 203) /* 220203 */ + +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) +/************************************************************ + * SRTP TRANSPORT ERRORS + ***********************************************************/ +/** + * @hideinitializer + * SRTP crypto-suite name not match the offerer tag. + */ +#define PJMEDIA_SRTP_ECRYPTONOTMATCH (PJMEDIA_ERRNO_START + 220) /* 220220 */ +/** + * @hideinitializer + * Invalid SRTP key length for specific crypto. + */ +#define PJMEDIA_SRTP_EINKEYLEN (PJMEDIA_ERRNO_START + 221) /* 220221 */ +/** + * @hideinitializer + * Unsupported SRTP crypto-suite. + */ +#define PJMEDIA_SRTP_ENOTSUPCRYPTO (PJMEDIA_ERRNO_START + 222) /* 220222 */ +/** + * @hideinitializer + * SRTP SDP contains ambigue answer. + */ +#define PJMEDIA_SRTP_ESDPAMBIGUEANS (PJMEDIA_ERRNO_START + 223) /* 220223 */ +/** + * @hideinitializer + * Duplicated crypto tag. + */ +#define PJMEDIA_SRTP_ESDPDUPCRYPTOTAG (PJMEDIA_ERRNO_START + 224) /* 220224 */ +/** + * @hideinitializer + * Invalid crypto attribute. + */ +#define PJMEDIA_SRTP_ESDPINCRYPTO (PJMEDIA_ERRNO_START + 225) /* 220225 */ +/** + * @hideinitializer + * Invalid crypto tag. + */ +#define PJMEDIA_SRTP_ESDPINCRYPTOTAG (PJMEDIA_ERRNO_START + 226) /* 220226 */ +/** + * @hideinitializer + * Invalid SDP media transport for SRTP. + */ +#define PJMEDIA_SRTP_ESDPINTRANSPORT (PJMEDIA_ERRNO_START + 227) /* 220227 */ +/** + * @hideinitializer + * SRTP crypto attribute required in SDP. + */ +#define PJMEDIA_SRTP_ESDPREQCRYPTO (PJMEDIA_ERRNO_START + 228) /* 220228 */ +/** + * @hideinitializer + * Secure transport required in SDP media descriptor. + */ +#define PJMEDIA_SRTP_ESDPREQSECTP (PJMEDIA_ERRNO_START + 229) /* 220229 */ +/** + * @hideinitializer + * SRTP parameters negotiation still in progress. + */ +#define PJMEDIA_SRTP_EKEYNOTREADY (PJMEDIA_ERRNO_START + 230) /* 220230 */ + +/** + * @hideinitializer + * No matching SRTP crypto-suite after DTLS nego. + */ +#define PJMEDIA_SRTP_DTLS_ENOCRYPTO (PJMEDIA_ERRNO_START + 240) /* 220240 */ + +/** + * @hideinitializer + * No certificate supplied by peer in DTLS nego. + */ +#define PJMEDIA_SRTP_DTLS_EPEERNOCERT (PJMEDIA_ERRNO_START + 241) /* 220241 */ + +/** + * @hideinitializer + * Fingerprint from signalling not match to actual fingerprint. + */ +#define PJMEDIA_SRTP_DTLS_EFPNOTMATCH (PJMEDIA_ERRNO_START + 242) /* 220242 */ + +/** + * @hideinitializer + * Fingerprint not found. + */ +#define PJMEDIA_SRTP_DTLS_ENOFPRINT (PJMEDIA_ERRNO_START + 243) /* 220243 */ + +/** + * @hideinitializer + * No valid SRTP protection profile for DTLS. + */ +#define PJMEDIA_SRTP_DTLS_ENOPROFILE (PJMEDIA_ERRNO_START + 244) /* 220244 */ + +#endif /* PJMEDIA_HAS_SRTP */ + +/** + * Get error message for the specified error code. Note that this + * function is only able to decode PJMEDIA specific error code. + * Application should use pj_strerror(), which should be able to + * decode all error codes belonging to all subsystems (e.g. pjlib, + * pjmedia, pjsip, etc). + * + * @param status The error code. + * @param buffer The buffer where to put the error message. + * @param bufsize Size of the buffer. + * + * @return The error message as NULL terminated string, + * wrapped with pj_str_t. + */ +PJ_DECL(pj_str_t) pjmedia_strerror(pj_status_t status, char *buffer, pj_size_t bufsize); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h new file mode 100755 index 000000000..2fb0db3cc --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_SDP_H__ +#define __PJMEDIA_SDP_H__ + +/** + * @file sdp.h + * @brief SDP header file. + */ +#include +#include + +/** + * @defgroup PJMEDIA_SDP SDP Parsing and Data Structure + * @ingroup PJMEDIA_SESSION + * @brief SDP data structure representation and parsing + * @{ + * + * The basic SDP session descriptor and elements are described in header + * file . This file contains declaration for + * SDP session descriptor and SDP media descriptor, along with their + * attributes. This file also declares functions to parse SDP message. + */ + +PJ_BEGIN_DECL + +/** + * The PJMEDIA_MAX_SDP_FMT macro defines maximum format in a media line. + */ +#ifndef PJMEDIA_MAX_SDP_FMT +#define PJMEDIA_MAX_SDP_FMT 32 +#endif + +/** + * The PJMEDIA_MAX_SDP_BANDW macro defines maximum bandwidth information + * lines in a media line. + */ +#ifndef PJMEDIA_MAX_SDP_BANDW +#define PJMEDIA_MAX_SDP_BANDW 4 +#endif + +/** + * The PJMEDIA_MAX_SDP_ATTR macro defines maximum SDP attributes in media and + * session descriptor. + */ +#ifndef PJMEDIA_MAX_SDP_ATTR +#define PJMEDIA_MAX_SDP_ATTR (PJMEDIA_MAX_SDP_FMT * 2 + 4) +#endif + +/** + * The PJMEDIA_MAX_SDP_MEDIA macro defines maximum SDP media lines in a + * SDP session descriptor. + */ +#ifndef PJMEDIA_MAX_SDP_MEDIA +#define PJMEDIA_MAX_SDP_MEDIA 16 +#endif + +/* ************************************************************************** + * SDP ATTRIBUTES + *************************************************************************** + */ + +/** + * Generic representation of attribute. + */ +struct pjmedia_sdp_attr { + pj_str_t name; /**< Attribute name. */ + pj_str_t value; /**< Attribute value. */ +}; + +/** + * @see pjmedia_sdp_attr + */ +typedef struct pjmedia_sdp_attr pjmedia_sdp_attr; + +/** + * Create SDP attribute. + * + * @param pool Pool to create the attribute. + * @param name Attribute name. + * @param value Optional attribute value. + * + * @return The new SDP attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_create(pj_pool_t *pool, const char *name, const pj_str_t *value); + +/** + * Clone attribute + * + * @param pool Pool to be used. + * @param attr The attribute to clone. + * + * @return New attribute as cloned from the attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_clone(pj_pool_t *pool, const pjmedia_sdp_attr *attr); + +/** + * Find the first attribute with the specified type. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * @param fmt Optional string to indicate which payload format + * to find for \a rtpmap and \a fmt attributes. For other + * types of attributes, the value should be NULL. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find2, pjmedia_sdp_media_find_attr, + * pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find(unsigned count, pjmedia_sdp_attr *const attr_array[], const pj_str_t *name, const pj_str_t *fmt); + +/** + * Find the first attribute with the specified type. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * @param fmt Optional string to indicate which payload format + * to find for \a rtpmap and \a fmt attributes. For other + * types of attributes, the value should be NULL. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find, pjmedia_sdp_media_find_attr, + * pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find2(unsigned count, pjmedia_sdp_attr *const attr_array[], const char *name, const pj_str_t *fmt); + +/** + * Add a new attribute to array of attributes. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param attr The attribute to add. + * + * @return PJ_SUCCESS or the error code. + * + * @see pjmedia_sdp_media_add_attr + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr); + +/** + * Remove all attributes with the specified name in array of attributes. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * + * @return Number of attributes removed. + * + * @see pjmedia_sdp_media_remove_all_attr + */ +PJ_DECL(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, pjmedia_sdp_attr *attr_array[], const char *name); + +/** + * Remove the specified attribute from the attribute array. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param attr The attribute instance to remove. + * + * @return PJ_SUCCESS when attribute has been removed, or + * PJ_ENOTFOUND when the attribute can not be found. + * + * @see pjmedia_sdp_media_remove_attr + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_remove(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr); + +/** + * This structure declares SDP \a rtpmap attribute. + */ +struct pjmedia_sdp_rtpmap { + pj_str_t pt; /**< Payload type. */ + pj_str_t enc_name; /**< Encoding name. */ + unsigned clock_rate; /**< Clock rate. */ + pj_str_t param; /**< Parameter. */ +}; + +/** + * @see pjmedia_sdp_rtpmap + */ +typedef struct pjmedia_sdp_rtpmap pjmedia_sdp_rtpmap; + +/** + * Convert generic attribute to SDP \a rtpmap. This function allocates + * a new attribute and call #pjmedia_sdp_attr_get_rtpmap(). + * + * @param pool Pool used to create the rtpmap attribute. + * @param attr Generic attribute to be converted to rtpmap, which + * name must be "rtpmap". + * @param p_rtpmap Pointer to receive SDP rtpmap attribute. + * + * @return PJ_SUCCESS if the attribute can be successfully + * converted to \a rtpmap type. + * + * @see pjmedia_sdp_attr_get_rtpmap + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap **p_rtpmap); + +/** + * Get the rtpmap representation of the same SDP attribute. + * + * @param attr Generic attribute to be converted to rtpmap, which + * name must be "rtpmap". Attribute value must be + * terminated with a NULL, CR, or LF character. + * @param rtpmap SDP \a rtpmap attribute to be initialized. + * + * @return PJ_SUCCESS if the attribute can be successfully + * converted to \a rtpmap attribute. + * + * @see pjmedia_sdp_attr_to_rtpmap + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtpmap(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap *rtpmap); + +/** + * Convert \a rtpmap attribute to generic attribute. + * + * @param pool Pool to be used. + * @param rtpmap The \a rtpmap attribute. + * @param p_attr Pointer to receive the generic SDP attribute. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, const pjmedia_sdp_rtpmap *rtpmap, pjmedia_sdp_attr **p_attr); + +/** + * This structure describes SDP \a fmtp attribute. + */ +typedef struct pjmedia_sdp_fmtp { + pj_str_t fmt; /**< Format type. */ + pj_str_t fmt_param; /**< Format specific parameter. */ +} pjmedia_sdp_fmtp; + +/** + * Get the fmtp representation of the same SDP attribute. + * + * @param attr Generic attribute to be converted to fmtp, which + * name must be "fmtp". + * @param fmtp SDP fmtp attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_fmtp(const pjmedia_sdp_attr *attr, pjmedia_sdp_fmtp *fmtp); + +/** + * This structure describes SDP \a rtcp attribute. + */ +typedef struct pjmedia_sdp_rtcp_attr { + unsigned port; /**< RTCP port number. */ + pj_str_t net_type; /**< Optional network type. */ + pj_str_t addr_type; /**< Optional address type. */ + pj_str_t addr; /**< Optional address. */ +} pjmedia_sdp_rtcp_attr; + +/** + * Parse a generic SDP attribute to get SDP rtcp attribute values. + * + * @param attr Generic attribute to be converted to rtcp, which + * name must be "rtcp". + * @param rtcp SDP rtcp attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtcp_attr *rtcp); + +/** + * Create a=rtcp attribute. + * + * @param pool Pool to create the attribute. + * @param a Socket address. + * + * @return SDP RTCP attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, const pj_sockaddr *a); + +/** + * This structure describes SDP \a ssrc attribute. + */ +typedef struct pjmedia_sdp_ssrc_attr { + pj_uint32_t ssrc; /**< RTP SSRC. */ + pj_str_t cname; /**< RTCP CNAME. */ +} pjmedia_sdp_ssrc_attr; + +/** + * Parse a generic SDP attribute to get SDP ssrc attribute values. + * + * @param attr Generic attribute to be converted to ssrc, which + * name must be "ssrc". + * @param rtcp SDP ssrc attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_ssrc(const pjmedia_sdp_attr *attr, pjmedia_sdp_ssrc_attr *rtcp); + +/** + * Create a=ssrc attribute. + * + * @param pool Pool to create the attribute. + * @param ssrc SSRC identifier. + * @param cname CNAME. + * + * @return SDP SSRC attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_ssrc(pj_pool_t *pool, pj_uint32_t ssrc, const pj_str_t *cname); + +/* ************************************************************************** + * SDP CONNECTION INFO + **************************************************************************** + */ + +/** + * This structure describes SDP connection info ("c=" line). + */ +struct pjmedia_sdp_conn { + pj_str_t net_type; /**< Network type ("IN"). */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6"). */ + pj_str_t addr; /**< The address. */ +}; + +/** + * @see pjmedia_sdp_conn + */ +typedef struct pjmedia_sdp_conn pjmedia_sdp_conn; + +/** + * Clone connection info. + * + * @param pool Pool to allocate memory for the new connection info. + * @param rhs The connection into to clone. + * + * @return The new connection info. + */ +PJ_DECL(pjmedia_sdp_conn *) pjmedia_sdp_conn_clone(pj_pool_t *pool, const pjmedia_sdp_conn *rhs); + +/** + * Compare connection info. + * + * @param conn1 The first connection info to compare. + * @param conn2 The second connection info to compare. + * @param option Comparison option, which should be zero for now. + * + * @return PJ_SUCCESS when both connection info are equal, otherwise + * returns PJMEDIA_SDP_ECONNNOTEQUAL. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_conn_cmp(const pjmedia_sdp_conn *conn1, const pjmedia_sdp_conn *conn2, unsigned option); + +/* ************************************************************************** + * SDP BANDWIDTH INFO + **************************************************************************** + */ + +/** + * This structure describes SDP bandwidth info ("b=" line). + */ +typedef struct pjmedia_sdp_bandw { + pj_str_t modifier; /**< Bandwidth modifier. */ + pj_uint32_t value; /**< Bandwidth value. */ +} pjmedia_sdp_bandw; + +/** + * Clone bandwidth info. + * + * @param pool Pool to allocate memory for the new bandwidth info. + * @param rhs The bandwidth into to clone. + * + * @return The new bandwidth info. + */ +PJ_DECL(pjmedia_sdp_bandw *) +pjmedia_sdp_bandw_clone(pj_pool_t *pool, const pjmedia_sdp_bandw *rhs); + +/* ************************************************************************** + * SDP MEDIA INFO/LINE + **************************************************************************** + */ + +/** + * This structure describes SDP media descriptor. A SDP media descriptor + * starts with "m=" line and contains the media attributes and optional + * connection line. + */ +struct pjmedia_sdp_media { + /** Media descriptor line ("m=" line) */ + struct { + pj_str_t media; /**< Media type ("audio", "video") */ + pj_uint16_t port; /**< Port number. */ + unsigned port_count; /**< Port count, used only when >2 */ + pj_str_t transport; /**< Transport ("RTP/AVP") */ + unsigned fmt_count; /**< Number of formats. */ + pj_str_t fmt[PJMEDIA_MAX_SDP_FMT]; /**< Media formats. */ + } desc; + + pjmedia_sdp_conn *conn; /**< Optional connection info. */ + unsigned bandw_count; /**< Number of bandwidth info. */ + pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW]; /**< Bandwidth info. */ + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes. */ +}; + +/** + * @see pjmedia_sdp_media + */ +typedef struct pjmedia_sdp_media pjmedia_sdp_media; + +/** + * Print media description to a buffer. + * + * @param media The media description. + * @param buf The buffer. + * @param size The buffer length. + * + * @return the length printed, or -1 if the buffer is too + * short. + */ +PJ_DECL(int) pjmedia_sdp_media_print(const pjmedia_sdp_media *media, char *buf, pj_size_t size); + +/** + * Clone SDP media description. + * + * @param pool Pool to allocate memory for the new media description. + * @param rhs The media descriptin to clone. + * + * @return New media description. + */ +PJ_DECL(pjmedia_sdp_media *) +pjmedia_sdp_media_clone(pj_pool_t *pool, const pjmedia_sdp_media *rhs); + +/** + * Find the first occurence of the specified attribute name in the media + * descriptor. Optionally the format may be specified. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload type to match in the + * attribute list, when the attribute is \a rtpmap + * or \a fmtp. For other types of SDP attributes, this + * value should be NULL. + * + * @return The first instance of the specified attribute or NULL. + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, const pj_str_t *name, const pj_str_t *fmt); + +/** + * Find the first occurence of the specified attribute name in the SDP media + * descriptor. Optionally the format may be specified. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload type to match in the + * attribute list, when the attribute is \a rtpmap + * or \a fmtp. For other types of SDP attributes, this + * value should be NULL. + * + * @return The first instance of the specified attribute or NULL. + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, const char *name, const pj_str_t *fmt); + +/** + * Add new attribute to the media descriptor. + * + * @param m The SDP media description. + * @param attr Attribute to add. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_add_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr); + +/** + * Remove all attributes with the specified name from the SDP media + * descriptor. + * + * @param m The SDP media description. + * @param name Attribute name to remove. + * + * @return The number of attributes removed. + */ +PJ_DECL(unsigned) +pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, const char *name); + +/** + * Remove the occurence of the specified attribute from the SDP media + * descriptor. + * + * @param m The SDP media descriptor. + * @param attr The attribute to find and remove. + * + * @return PJ_SUCCESS if the attribute can be found and has + * been removed from the array. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr); + +/** + * Compare two SDP media for equality. + * + * @param sd1 The first SDP media to compare. + * @param sd2 The second SDP media to compare. + * @param option Comparison option, which should be zero for now. + * + * @return PJ_SUCCESS when both SDP medias are equal, or the + * appropriate status code describing which part of + * the descriptors that are not equal. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_cmp(const pjmedia_sdp_media *sd1, const pjmedia_sdp_media *sd2, unsigned option); + +/** + * Compare two media transports for compatibility. + * + * @param t1 The first media transport to compare. + * @param t2 The second media transport to compare. + * + * @return PJ_SUCCESS when both media transports are compatible, + * otherwise returns PJMEDIA_SDP_ETPORTNOTEQUAL. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_transport_cmp(const pj_str_t *t1, const pj_str_t *t2); + +/** + * Get media transport protocol info, i.e: base transport and profiles, + * from the provided SDP media transport name string. + * + * @param tp The SDP media transport name. + * + * @return Media transport info, combination of transport protocol + * and profile bit flag defined in pjmedia_tp_proto. + */ +PJ_DECL(pj_uint32_t) pjmedia_sdp_transport_get_proto(const pj_str_t *tp); + +/** + * Deactivate SDP media. + * + * @param pool Memory pool to allocate memory from. + * @param m The SDP media to deactivate. + * + * @return PJ_SUCCESS when SDP media successfully deactivated, + * otherwise appropriate status code returned. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool, pjmedia_sdp_media *m); + +/** + * Clone SDP media description and deactivate the new SDP media. + * + * @param pool Memory pool to allocate memory for the clone. + * @param rhs The SDP media to clone. + * + * @return New media descrption with deactivated indication. + */ +PJ_DECL(pjmedia_sdp_media *) pjmedia_sdp_media_clone_deactivate(pj_pool_t *pool, const pjmedia_sdp_media *rhs); + +/* ************************************************************************** + * SDP SESSION DESCRIPTION + **************************************************************************** + */ + +/** + * This structure describes SDP session description. A SDP session descriptor + * contains complete information about a session, and normally is exchanged + * with remote media peer using signaling protocol such as SIP. + */ +struct pjmedia_sdp_session { + /** Session origin (o= line) */ + struct { + pj_str_t user; /**< User */ + pj_uint32_t id; /**< Session ID */ + pj_uint32_t version; /**< Session version */ + pj_str_t net_type; /**< Network type ("IN") */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6") */ + pj_str_t addr; /**< The address. */ + } origin; + + pj_str_t name; /**< Subject line (s=) */ + pjmedia_sdp_conn *conn; /**< Connection line (c=) */ + unsigned bandw_count; /**< Number of bandwidth info (b=) */ + pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW]; + /**< Bandwidth info array (b=) */ + + /** Session time (t= line) */ + struct { + pj_uint32_t start; /**< Start time. */ + pj_uint32_t stop; /**< Stop time. */ + } time; + + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes array. */ + + unsigned media_count; /**< Number of media. */ + pjmedia_sdp_media *media[PJMEDIA_MAX_SDP_MEDIA]; /**< Media array. */ +}; + +/** + * @see pjmedia_sdp_session + */ +typedef struct pjmedia_sdp_session pjmedia_sdp_session; + +/** + * Parse SDP message. + * + * Note that the input message buffer MUST be NULL terminated and have + * length at least len+1 (len MUST NOT include the NULL terminator). + * + * @param pool The pool to allocate SDP session description. + * @param buf The message buffer, MUST be NULL terminated. + * @param len The length of the message, excluding NULL terminator. + * @param p_sdp Pointer to receive the SDP session descriptor. + * + * @return PJ_SUCCESS if message was successfully parsed into + * SDP session descriptor. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_parse(pj_pool_t *pool, char *buf, pj_size_t len, pjmedia_sdp_session **p_sdp); + +/** + * Print SDP description to a buffer. + * + * @param sdp The SDP session description. + * @param buf The buffer. + * @param size The buffer length. + * + * @return the length printed, or -1 if the buffer is too + * short. + */ +PJ_DECL(int) pjmedia_sdp_print(const pjmedia_sdp_session *sdp, char *buf, pj_size_t size); + +/** + * Perform semantic validation for the specified SDP session descriptor. + * This function perform validation beyond just syntactic verification, + * such as to verify the value of network type and address type, check + * the connection line, and verify that \a rtpmap attribute is present + * when dynamic payload type is used. + * + * @param sdp The SDP session descriptor to validate. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp); + +/** + * Perform semantic validation for the specified SDP session descriptor. + * This function perform validation beyond just syntactic verification, + * such as to verify the value of network type and address type, check + * the connection line, and verify that \a rtpmap attribute is present + * when dynamic payload type is used. + * + * @param sdp The SDP session descriptor to validate. + * @param strict Flag whether the check should be strict, i.e: allow + * media without connection line when port is zero. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_validate2(const pjmedia_sdp_session *sdp, pj_bool_t strict); + +/** + * Clone SDP session descriptor. + * + * @param pool The pool used to clone the session. + * @param sdp The SDP session to clone. + * + * @return New SDP session. + */ +PJ_DECL(pjmedia_sdp_session *) +pjmedia_sdp_session_clone(pj_pool_t *pool, const pjmedia_sdp_session *sdp); + +/** + * Compare two SDP session for equality. + * + * @param sd1 The first SDP session to compare. + * @param sd2 The second SDP session to compare. + * @param option Must be zero for now. + * + * @return PJ_SUCCESS when both SDPs are equal, or otherwise + * the status code indicates which part of the session + * descriptors are not equal. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_session_cmp(const pjmedia_sdp_session *sd1, const pjmedia_sdp_session *sd2, unsigned option); + +/** + * Add new attribute to the session descriptor. + * + * @param s The SDP session description. + * @param attr Attribute to add. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s, pjmedia_sdp_attr *attr); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_SDP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h new file mode 100755 index 000000000..d0809505d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_SDP_NEG_H__ +#define __PJMEDIA_SDP_NEG_H__ + +/** + * @file sdp_neg.h + * @brief SDP negotiator header file. + */ +/** + * @defgroup PJMEDIA_SDP_NEG SDP Negotiation State Machine (Offer/Answer Model, RFC 3264) + * @ingroup PJMEDIA_SESSION + * @brief SDP Negotiation State Machine (Offer/Answer Model, RFC 3264) + * @{ + * + * The header file contains the declaration + * of SDP offer and answer negotiator. SDP offer and answer model is described + * in RFC 3264 "An Offer/Answer Model with Session Description Protocol + * (SDP)". + * + * The SDP negotiator is represented with opaque type \a pjmedia_sdp_neg. + * This structure contains negotiation state and several SDP session + * descriptors currently being used in the negotiation. + * + * + * \section sdpneg_state_dia SDP Negotiator State Diagram + * + * The following diagram describes the state transition diagram of the + * SDP negotiator. + * + *
+ *
+ *                                              modify_local_offer()
+ *     create_w_local_offer()  +-------------+  send_local_offer()
+ *     ----------------------->| LOCAL_OFFER |<-----------------------
+ *    |                        +-------------+______                  |
+ *    |                               |             \_____________    |
+ *    |           set_remote_answer() |           cancel_offer()  \   |
+ *    |                               V                            v  |
+ * +--+---+                     +-----------+     negotiate()     +-~----+
+ * | NULL |                     | WAIT_NEGO |-------------------->| DONE |
+ * +------+                     +-----------+                     +------+
+ *    |                               A      ______________________^  |
+ *    |            set_local_answer() |     /     cancel_offer()      |
+ *    |                               |    /                          |
+ *    |                        +--------------+   set_remote_offer()  |
+ *     ----------------------->| REMOTE_OFFER |<----------------------
+ *     create_w_remote_offer() +--------------+
+ *
+ * 
+ * + * + * + * \section sdpneg_offer_answer SDP Offer/Answer Model with Negotiator + * + * \subsection sdpneg_create_offer Creating Initial Offer + * + * Application creates an offer by manualy building the SDP session descriptor + * (pjmedia_sdp_session), or request PJMEDIA endpoint (pjmedia_endpt) to + * create SDP session descriptor based on capabilities that present in the + * endpoint by calling #pjmedia_endpt_create_sdp(). + * + * Application then creates SDP negotiator instance by calling + * #pjmedia_sdp_neg_create_w_local_offer(), passing the SDP offer in the + * function arguments. The SDP negotiator keeps a copy of current local offer, + * and update its state to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER. + * + * Application can then send the initial SDP offer that it creates to + * remote peer using signaling protocol such as SIP. + * + * + * \subsection sdpneg_generate_offer Generating Subsequent Offer + * + * The negotiator can only create subsequent offer after it has finished + * the negotiation process of previous offer/answer session (i.e. the + * negotiator state is PJMEDIA_SDP_NEG_STATE_DONE). + * + * If any previous negotiation process was successfull (i.e. the return + * value of #pjmedia_sdp_neg_negotiate() was PJ_SUCCESS), the negotiator + * keeps both active local and active remote SDP. + * + * If application does not want send modified offer, it can just send + * the active local SDP as the offer. In this case, application calls + * #pjmedia_sdp_neg_send_local_offer() to get the active local SDP. + * + * If application wants to modify it's local offer, it MUST inform + * the negotiator about the modified SDP by calling + * #pjmedia_sdp_neg_modify_local_offer(). + * + * In both cases, the negotiator will internally create a copy of the offer, + * and move it's state to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it + * waits until application passes the remote answer. + * + * + * \subsection sdpneg_receive_offer Receiving Initial Offer + * + * Application receives an offer in the incoming request from remote to + * establish multimedia session, such as incoming INVITE message with SDP + * body. + * + * Initially, when the initial offer is received, application creates the + * SDP negotiator by calling #pjmedia_sdp_neg_create_w_remote_offer(), + * specifying the remote SDP offer in one of the argument. + * + * At this stage, application may or may not ready to create an answer. + * For example, a SIP B2BUA needs to make outgoing call and receive SDP + * from the outgoing call leg in order to create a SDP answer to the + * incoming call leg. + * + * If application is not ready to create an answer, it passes NULL as + * the local SDP when it calls #pjmedia_sdp_neg_create_w_remote_offer(). + * + * The section @ref sdpneg_create_answer describes the case when + * application is ready to create a SDP answer. + * + * + * \subsection sdpneg_subseq_offer Receiving Subsequent Offer + * + * Application passes subsequent SDP offer received from remote by + * calling #pjmedia_sdp_neg_set_remote_offer(). + * + * The negotiator can only receive subsequent offer after it has finished + * the negotiation process of previous offer/answer session (i.e. the + * negotiator state is PJMEDIA_SDP_NEG_STATE_DONE). + * + * + * \subsection sdpneg_recv_answer Receiving SDP Answer + * + * When application receives SDP answer from remote, it informs the + * negotiator by calling #pjmedia_sdp_neg_set_remote_answer(). The + * negotiator validates the answer (#pjmedia_sdp_validate()), and if + * succeeds, it moves it's state to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO. + * + * Application then instruct the negotiator to negotiate the remote + * answer by calling #pjmedia_sdp_neg_negotiate(). The purpose of + * this negotiation is to verify remote answer, and update the initial + * offer according to the answer. For example, the initial offer may + * specify that a stream is \a sendrecv, while the answer specifies + * that remote stream is \a inactive. In this case, the negotiator + * will update the stream in the local active media as \a inactive + * too. + * + * If #pjmedia_sdp_neg_negotiate() returns PJ_SUCCESS, the negotiator will + * keep the updated local answer and remote answer internally. These two + * SDPs are called active local SDP and active remote SDP, as it describes + * currently active session. + * + * Application can retrieve the active local SDP by calling + * #pjmedia_sdp_neg_get_active_local(), and active remote SDP by calling + * #pjmedia_sdp_neg_get_active_remote(). + * + * If #pjmedia_sdp_neg_negotiate() returns failure (i.e. not PJ_SUCCESS), + * it WILL NOT update its active local and active remote SDP. + * + * Regardless of the return status of the #pjmedia_sdp_neg_negotiate(), + * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. + * + * + * \subsection sdpneg_cancel_offer Cancelling an Offer + * + * In other case, after an offer is generated (negotiator state is in + * PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER), the answer may not be received, and + * application wants the negotiator to reset itself to its previous state. + * Consider this example: + * + * - media has been established, and negotiator state is + * PJMEDIA_SDP_NEG_STATE_DONE. + * - application generates a new offer for re-INVITE, so in this case + * it would either call #pjmedia_sdp_neg_send_local_offer() or + * #pjmedia_sdp_neg_modify_local_offer() + * - the negotiator state moves to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER + * - the re-INVITE was rejected with an error + * + * Since an answer is not received, it is necessary to reset the negotiator + * state back to PJMEDIA_SDP_NEG_STATE_DONE so that the negotiator can + * create or receive new offer. + * + * This can be accomplished by calling #pjmedia_sdp_neg_cancel_offer(), + * to reset the negotiator state back to PJMEDIA_SDP_NEG_STATE_DONE. In + * this case, both active local and active remote will not be modified. + * + * \subsection sdpneg_create_answer Generating SDP Answer + * + * After remote offer has been set in the negotiator, application can + * request the SDP negotiator to generate appropriate answer based on local + * capability. + * + * To do this, first the application MUST have an SDP describing its local + * capabilities. This SDP can be built manually, or application can generate + * SDP to describe local media endpoint capability by calling + * #pjmedia_endpt_create_sdp(). When the application is a SIP B2BUA, + * application can treat the SDP received from the outgoing call leg as if + * it was it's local capability. + * + * The local SDP session descriptor DOES NOT have to match the SDP offer. + * For example, it can have more or less media lines than the offer, or + * their order may be different than the offer. The negotiator is capable + * to match and reorder local SDP according to remote offer, and create + * an answer that is suitable for the offer. + * + * After local SDP capability has been acquired, application can create + * a SDP answer. + * + * If application does not already have the negotiator instance, it creates + * one by calling #pjmedia_sdp_neg_create_w_remote_offer(), specifying + * both remote SDP offer and local SDP as the arguments. The SDP negotiator + * validates both remote and local SDP by calling #pjmedia_sdp_validate(), + * and if both SDPs are valid, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO where it is ready to negotiate the + * offer and answer. + * + * If application already has the negotiator instance, it sets the local + * SDP in the negotiator by calling #pjmedia_sdp_neg_set_local_answer(). + * The SDP negotiator then validates local SDP (#pjmedia_sdp_validate() ), + * and if it is valid, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO where it is ready to negotiate the + * offer and answer. + * + * After the SDP negotiator state has moved to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + * application calls #pjmedia_sdp_neg_negotiate() to instruct the SDP + * negotiator to negotiate both offer and answer. This function returns + * PJ_SUCCESS if an answer can be generated AND at least one media stream + * is active in the session. + * + * If #pjmedia_sdp_neg_negotiate() returns PJ_SUCCESS, the negotiator will + * keep the remote offer and local answer internally. These two SDPs are + * called active local SDP and active remote SDP, as it describes currently + * active session. + * + * Application can retrieve the active local SDP by calling + * #pjmedia_sdp_neg_get_active_local(), and send this SDP to remote as the + * SDP answer. + * + * If #pjmedia_sdp_neg_negotiate() returns failure (i.e. not PJ_SUCCESS), + * it WILL NOT update its active local and active remote SDP. + * + * Regardless of the return status of the #pjmedia_sdp_neg_negotiate(), + * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. + * + * + */ + +#include + +PJ_BEGIN_DECL + +/** + * This enumeration describes SDP negotiation state. + */ +enum pjmedia_sdp_neg_state { + /** + * This is the state of SDP negoator before it is initialized. + */ + PJMEDIA_SDP_NEG_STATE_NULL, + + /** + * This state occurs when SDP negotiator has sent our offer to remote and + * it is waiting for answer. + */ + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + + /** + * This state occurs when SDP negotiator has received offer from remote + * and currently waiting for local answer. + */ + PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, + + /** + * This state occurs when an offer (either local or remote) has been + * provided with answer. The SDP negotiator is ready to negotiate both + * session descriptors. Application can call #pjmedia_sdp_neg_negotiate() + * immediately to begin negotiation process. + */ + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + + /** + * This state occurs when SDP negotiation has completed, either + * successfully or not. + */ + PJMEDIA_SDP_NEG_STATE_DONE +}; + +/** + * @see pjmedia_sdp_neg_state + */ +typedef enum pjmedia_sdp_neg_state pjmedia_sdp_neg_state; + +/** + * Opaque declaration of SDP negotiator. + */ +typedef struct pjmedia_sdp_neg pjmedia_sdp_neg; + +/** + * Flags to be given to pjmedia_sdp_neg_modify_local_offer2(). + */ +typedef enum pjmedia_mod_offer_flag { + /** + * Allow media type in the SDP to be changed. + * When generating a new offer, in the case that a media line doesn't match + * the active SDP, the new media line will be considered to replace the + * existing media at the same position. + */ + PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE = 1 + +} pjmedia_mod_offer_flag; + +/** + * Get the state string description of the specified state. + * + * @param state Negotiator state. + * + * @return String description of the state. + */ +PJ_DECL(const char *) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state); + +/** + * Create the SDP negotiator with local offer. The SDP negotiator then + * will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER state, where it waits + * until it receives answer from remote. When SDP answer from remote is + * received, application must call #pjmedia_sdp_neg_set_remote_answer(). + * + * After calling this function, application should send the local SDP offer + * to remote party using signaling protocol such as SIP and wait for SDP + * answer. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param local The initial local capability. + * @param p_neg Pointer to receive the negotiator instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer(pj_pool_t *pool, const pjmedia_sdp_session *local, pjmedia_sdp_neg **p_neg); + +/** + * Initialize the SDP negotiator with remote offer, and optionally + * specify the initial local capability, if known. Application normally + * calls this function when it receives initial offer from remote. + * + * If local media capability is specified, this capability will be set as + * initial local capability of the negotiator, and after this function is + * called, the SDP negotiator state will move to state + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and the negotiation function can be + * called. + * + * If local SDP is not specified, the negotiator will not have initial local + * capability, and after this function is called the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER state. Application MUST supply + * local answer later with #pjmedia_sdp_neg_set_local_answer(), before + * calling the negotiation function. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param initial Optional initial local capability. + * @param remote The remote offer. + * @param p_neg Pointer to receive the negotiator instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *remote, pjmedia_sdp_neg **p_neg); + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should rather use the codec preference as set by + * remote, or should it rather use the codec preference as specified by + * local endpoint. + * + * For example, suppose incoming call has codec order "8 0 3", while + * local codec order is "3 0 8". If remote codec order is preferable, + * the selected codec will be 8, while if local codec order is preferable, + * the selected codec will be 3. + * + * By default, the value in PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER will + * be used. + * + * @param neg The SDP negotiator instance. + * @param prefer_remote If non-zero, the negotiator will use the codec + * order as specified in remote offer. If zero, it + * will prefer to use the local codec order. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_prefer_remote_codec_order(pjmedia_sdp_neg *neg, pj_bool_t prefer_remote); + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should answer with multiple formats or not. + * + * By default, the value in PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS will + * be used. + * + * @param neg The SDP negotiator instance. + * @param answer_multiple + * If non-zero, the negotiator will respond with + * multiple formats. If zero only a single format + * will be returned. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_answer_multiple_codecs(pjmedia_sdp_neg *neg, pj_bool_t answer_multiple); + +/** + * Get SDP negotiator state. + * + * @param neg The SDP negotiator instance. + * + * @return The negotiator state. + */ +PJ_DECL(pjmedia_sdp_neg_state) +pjmedia_sdp_neg_get_state(pjmedia_sdp_neg *neg); + +/** + * Get the currently active local SDP. Application can only call this + * function after negotiation has been done, or otherwise there won't be + * active SDPs. Calling this function will not change the state of the + * negotiator. + * + * @param neg The SDP negotiator instance. + * @param local Pointer to receive the local active SDP. + * + * @return PJ_SUCCESS if local active SDP is present. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_active_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local); + +/** + * Get the currently active remote SDP. Application can only call this + * function after negotiation has been done, or otherwise there won't be + * active SDPs. Calling this function will not change the state of the + * negotiator. + * + * @param neg The SDP negotiator instance. + * @param remote Pointer to receive the remote active SDP. + * + * @return PJ_SUCCESS if remote active SDP is present. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_active_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote); + +/** + * Determine whether remote sent answer (as opposed to offer) on the + * last negotiation. This function can only be called in state + * PJMEDIA_SDP_NEG_STATE_DONE. + * + * @param neg The SDP negotiator instance. + * + * @return Non-zero if it was remote who sent answer, + * otherwise zero if it was local who supplied + * answer. + */ +PJ_DECL(pj_bool_t) +pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg); + +/** + * Get the current remote SDP offer or answer. Application can only + * call this function in state PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER or + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be remote + * SDP offer/answer. Calling this function will not change the state + * of the negotiator. + * + * @param neg The SDP negotiator instance. + * @param remote Pointer to receive the current remote offer or + * answer. + * + * @return PJ_SUCCESS if the negotiator currently has + * remote offer or answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_neg_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote); + +/** + * Get the current local SDP offer or answer. Application can only + * call this function in state PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER or + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be local + * SDP offer/answer. Calling this function will not change the state + * of the negotiator. + * + * @param neg The SDP negotiator instance. + * @param local Pointer to receive the current local offer or + * answer. + * + * @return PJ_SUCCESS if the negotiator currently has + * local offer or answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_neg_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local); + +/** + * Modify local session with a new SDP and treat this as a new offer. + * This function can only be called in state PJMEDIA_SDP_NEG_STATE_DONE. + * After calling this function, application can send the SDP as offer + * to remote party, using signaling protocol such as SIP. + * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + * where it waits for SDP answer from remote. See also + * #pjmedia_sdp_neg_modify_local_offer2() + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param local The new local SDP. + * + * @return PJ_SUCCESS on success, or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_modify_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local); + +/** + * Modify local session with a new SDP and treat this as a new offer. + * This function can only be called in state PJMEDIA_SDP_NEG_STATE_DONE. + * After calling this function, application can send the SDP as offer + * to remote party, using signaling protocol such as SIP. + * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + * where it waits for SDP answer from remote. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param flags Bitmask from pjmedia_mod_offer_flag. + * @param local The new local SDP. + * + * @return PJ_SUCCESS on success, or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_modify_local_offer2(pj_pool_t *pool, pjmedia_sdp_neg *neg, unsigned flags, + const pjmedia_sdp_session *local); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function to retrieve currently active + * local SDP, and then send the SDP to remote as an offer. The negotiator + * state will then move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it waits + * for SDP answer from remote. + * + * When SDP answer has been received from remote, application must call + * #pjmedia_sdp_neg_set_remote_answer(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param offer Pointer to receive active local SDP to be + * offered to remote. + * + * @return PJ_SUCCESS if local offer can be created. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_send_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session **offer); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER + * state, i.e. after application calls #pjmedia_sdp_neg_send_local_offer() + * function. Application calls this function when it receives SDP answer + * from remote. After this function is called, the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the + * negotiation function #pjmedia_sdp_neg_negotiate(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param remote The remote answer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_remote_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function when it receives SDP offer from remote. + * After this function is called, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, and application MUST call the + * #pjmedia_sdp_neg_set_local_answer() to set local answer before it can + * call the negotiation function. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param remote The remote offer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_remote_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER + * state, i.e. after application calls #pjmedia_sdp_neg_set_remote_offer() + * function. After this function is called, the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the + * negotiation function #pjmedia_sdp_neg_negotiate(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param local Optional local answer. If negotiator has initial + * local capability, application can specify NULL on + * this argument; in this case, the negotiator will + * create answer by by negotiating remote offer with + * initial local capability. If negotiator doesn't have + * initial local capability, application MUST specify + * local answer here. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_local_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local); + +/** + * Call this function when the negotiator is in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO + * state to see if it was local who is answering the offer (instead of + * remote). + * + * @param neg The negotiator. + * + * @return PJ_TRUE if it is local is answering an offer, PJ_FALSE + * if remote has answered local offer. + */ +PJ_DECL(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg); + +/** + * Cancel any pending offer, whether the offer is initiated by local or + * remote, and move negotiator state back to previous stable state + * (PJMEDIA_SDP_NEG_STATE_DONE). The negotiator must be in + * PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER or PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER + * state. + * + * @param neg The negotiator. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg); + +/** + * Negotiate local and remote answer. Before calling this function, the + * SDP negotiator must be in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO state. + * After calling this function, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_DONE regardless whether the negotiation has + * been successfull or not. + * + * If the negotiation succeeds (i.e. the return value is PJ_SUCCESS), + * the active local and remote SDP will be replaced with the new SDP + * from the negotiation process. + * + * If the negotiation fails, the active local and remote SDP will not + * change. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param allow_asym Should be zero. + * + * @return PJ_SUCCESS when there is at least one media + * is actuve common in both offer and answer, or + * failure code when negotiation has failed. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_negotiate(pj_pool_t *pool, pjmedia_sdp_neg *neg, pj_bool_t allow_asym); + +/** + * Enumeration of customized SDP format matching option flags. See + * #pjmedia_sdp_neg_register_fmt_match_cb() for more info. + */ +typedef enum pjmedia_sdp_neg_fmt_match_flag { + /** + * In generating answer, the SDP fmtp in the answer candidate may need + * to be modified by the customized SDP format matching callback to + * achieve flexible SDP negotiation, e.g: AMR fmtp 'octet-align' field + * can be adjusted with the offer when the codec implementation support + * both packetization modes octet-aligned and bandwidth-efficient. + */ + PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER = 1, + +} pjmedia_sdp_neg_fmt_match_flag; + +/** + * The declaration of customized SDP format matching callback. See + * #pjmedia_sdp_neg_register_fmt_match_cb() for more info. + * + * @param pool The memory pool. + * @param offer The SDP media offer. + * @param o_fmt_idx Index of the format in the SDP media offer. + * @param answer The SDP media answer. + * @param a_fmt_idx Index of the format in the SDP media answer. + * @param option The format matching option, see + * #pjmedia_sdp_neg_fmt_match_flag. + * + * @return PJ_SUCCESS when the formats in offer and answer match. + */ +typedef pj_status_t (*pjmedia_sdp_neg_fmt_match_cb)(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, + pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option); + +/** + * Register customized SDP format matching callback function for the specified + * format. The customized SDP format matching is needed when the format + * identification in a media stream session cannot be simply determined by + * encoding name and clock rate, but also involves one or more format specific + * parameters, which are specified in SDP fmtp attribute. For example, + * an H.264 video stream is also identified by profile, level, and + * packetization-mode parameters. As those parameters are format specifics, + * the negotiation must be done by the format or codec implementation. + * + * To unregister the callback of specific format, just call this function with + * parameter cb set to NULL. + * + * @param fmt_name The format name, e.g: "H.264", "AMR", "G7221". Note + * that the string buffer must remain valid until the + * callback is unregistered. + * @param cb The customized SDP format negotiation callback or + * NULL to unregister the specified format callback. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(const pj_str_t *fmt_name, pjmedia_sdp_neg_fmt_match_cb cb); + +/** + * Match format in the SDP media offer and answer. The matching mechanism + * will be done by comparing the encoding name, clock rate, and encoding + * parameters (if any), and if the custom format matching callback + * for the specified format is registered, see + * #pjmedia_sdp_neg_register_fmt_match_cb(), it will be called for + * more detail verification, e.g: format parameters specified in SDP fmtp. + * + * @param pool The memory pool. + * @param offer The SDP media offer. + * @param o_fmt_idx Index of the format in the SDP media offer. + * @param answer The SDP media answer. + * @param a_fmt_idx Index of the format in the SDP media answer. + * @param option The format matching option, see + * #pjmedia_sdp_neg_fmt_match_flag. + * + * @return PJ_SUCCESS when the formats in offer and answer match. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_fmt_match(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, + unsigned a_fmt_idx, unsigned option); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_SDP_NEG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h new file mode 100755 index 000000000..96a7528ae --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_TYPES_H__ +#define __PJMEDIA_TYPES_H__ + +/** + * @file pjmedia/types.h Basic Types + * @brief Basic PJMEDIA types. + */ + +#include +#include +#include + +/** + * @defgroup PJMEDIA_PORT Media Ports Framework + * @brief Extensible framework for media terminations + */ + +/** + * @defgroup PJMEDIA_FRAME_OP Audio Manipulation Algorithms + * @brief Algorithms to manipulate audio frames + */ + +/** + * @defgroup PJMEDIA_TYPES Basic Types + * @ingroup PJMEDIA_BASE + * @brief Basic PJMEDIA types and operations. + * @{ + */ + +/** + * Top most media type. See also #pjmedia_type_name(). + */ +typedef enum pjmedia_type { + /** Type is not specified. */ + PJMEDIA_TYPE_NONE, + + /** The media is audio */ + PJMEDIA_TYPE_AUDIO, + + /** The media is video. */ + PJMEDIA_TYPE_VIDEO, + + /** The media is application. */ + PJMEDIA_TYPE_APPLICATION, + + /** The media type is unknown or unsupported. */ + PJMEDIA_TYPE_UNKNOWN + +} pjmedia_type; + +/** + * Media transport protocol and profile. + */ +typedef enum pjmedia_tp_proto { + /* Basic transports */ + + /** No transport type */ + PJMEDIA_TP_PROTO_NONE = 0, + + /** Transport unknown */ + PJMEDIA_TP_PROTO_UNKNOWN = (1 << 0), + + /** UDP transport */ + PJMEDIA_TP_PROTO_UDP = (1 << 1), + + /** RTP transport */ + PJMEDIA_TP_PROTO_RTP = (1 << 2), + + /** DTLS transport */ + PJMEDIA_TP_PROTO_DTLS = (1 << 3), + + /* Basic profiles */ + + /** RTCP Feedback profile */ + PJMEDIA_TP_PROFILE_RTCP_FB = (1 << 13), + + /** Secure RTP profile */ + PJMEDIA_TP_PROFILE_SRTP = (1 << 14), + + /** Audio/video profile */ + PJMEDIA_TP_PROFILE_AVP = (1 << 15), + + /* Predefined transport profiles (commonly used) */ + + /** RTP using A/V profile */ + PJMEDIA_TP_PROTO_RTP_AVP = (PJMEDIA_TP_PROTO_RTP | PJMEDIA_TP_PROFILE_AVP), + + /** Secure RTP using A/V profile */ + PJMEDIA_TP_PROTO_RTP_SAVP = (PJMEDIA_TP_PROTO_RTP_AVP | PJMEDIA_TP_PROFILE_SRTP), + + /** Secure RTP using A/V profile and DTLS-SRTP keying */ + PJMEDIA_TP_PROTO_DTLS_SRTP = (PJMEDIA_TP_PROTO_DTLS | PJMEDIA_TP_PROTO_RTP_SAVP), + + /** RTP using A/V and RTCP feedback profile */ + PJMEDIA_TP_PROTO_RTP_AVPF = (PJMEDIA_TP_PROTO_RTP_AVP | PJMEDIA_TP_PROFILE_RTCP_FB), + + /** Secure RTP using A/V and RTCP feedback profile */ + PJMEDIA_TP_PROTO_RTP_SAVPF = (PJMEDIA_TP_PROTO_RTP_SAVP | PJMEDIA_TP_PROFILE_RTCP_FB), + + /** Secure RTP using A/V and RTCP feedback profile and DTLS-SRTP keying */ + PJMEDIA_TP_PROTO_DTLS_SRTPF = (PJMEDIA_TP_PROTO_DTLS_SRTP | PJMEDIA_TP_PROFILE_RTCP_FB), + +} pjmedia_tp_proto; + +/** + * Macro helper for checking if a transport protocol contains specific + * transport and profile flags. + */ +#define PJMEDIA_TP_PROTO_HAS_FLAG(TP_PROTO, FLAGS) (((TP_PROTO) & (FLAGS)) == (FLAGS)) + +/** + * Macro helper for excluding specific flags in transport protocol. + */ +#define PJMEDIA_TP_PROTO_TRIM_FLAG(TP_PROTO, FLAGS) ((TP_PROTO) &= ~(FLAGS)) + +/** + * Media direction. + */ +typedef enum pjmedia_dir { + /** None */ + PJMEDIA_DIR_NONE = 0, + + /** Encoding (outgoing to network) stream, also known as capture */ + PJMEDIA_DIR_ENCODING = 1, + + /** Same as encoding direction. */ + PJMEDIA_DIR_CAPTURE = PJMEDIA_DIR_ENCODING, + + /** Decoding (incoming from network) stream, also known as playback. */ + PJMEDIA_DIR_DECODING = 2, + + /** Same as decoding. */ + PJMEDIA_DIR_PLAYBACK = PJMEDIA_DIR_DECODING, + + /** Same as decoding. */ + PJMEDIA_DIR_RENDER = PJMEDIA_DIR_DECODING, + + /** Incoming and outgoing stream, same as PJMEDIA_DIR_CAPTURE_PLAYBACK */ + PJMEDIA_DIR_ENCODING_DECODING = 3, + + /** Same as ENCODING_DECODING */ + PJMEDIA_DIR_CAPTURE_PLAYBACK = PJMEDIA_DIR_ENCODING_DECODING, + + /** Same as ENCODING_DECODING */ + PJMEDIA_DIR_CAPTURE_RENDER = PJMEDIA_DIR_ENCODING_DECODING + +} pjmedia_dir; + +/** + * Opaque declaration of media endpoint. + */ +typedef struct pjmedia_endpt pjmedia_endpt; + +/* + * Forward declaration for stream (needed by transport). + */ +typedef struct pjmedia_stream pjmedia_stream; + +/** + * Enumeration for picture coordinate base. + */ +typedef enum pjmedia_coord_base { + /** + * This specifies that the pixel [0, 0] location is at the left-top + * position. + */ + PJMEDIA_COORD_BASE_LEFT_TOP, + + /** + * This specifies that the pixel [0, 0] location is at the left-bottom + * position. + */ + PJMEDIA_COORD_BASE_LEFT_BOTTOM + +} pjmedia_coord_base; + +/** + * This structure is used to represent rational numbers. + */ +typedef struct pjmedia_ratio { + int num; /** < Numerator. */ + int denum; /** < Denumerator. */ +} pjmedia_ratio; + +/** + * This structure represent a coordinate. + */ +typedef struct pjmedia_coord { + int x; /**< X position of the coordinate */ + int y; /**< Y position of the coordinate */ +} pjmedia_coord; + +/** + * This structure represents rectangle size. + */ +typedef struct pjmedia_rect_size { + unsigned w; /**< The width. */ + unsigned h; /**< The height. */ +} pjmedia_rect_size; + +/** + * This structure describes a rectangle. + */ +typedef struct pjmedia_rect { + pjmedia_coord coord; /**< The position. */ + pjmedia_rect_size size; /**< The size. */ +} pjmedia_rect; + +/** + * Enumeration for video/picture orientation. + */ +typedef enum pjmedia_orient { + /** + * Unknown orientation. + */ + PJMEDIA_ORIENT_UNKNOWN, + + /** + * Natural orientation, i.e. the original orientation video will be + * displayed/captured without rotation. + */ + PJMEDIA_ORIENT_NATURAL, + + /** + * Specifies that the video/picture needs to be rotated 90 degrees + * from its natural orientation in clockwise direction from the user's + * perspective. + * Note that for devices with back cameras (which faces away + * from the user), the video will actually need to be rotated + * 270 degrees clockwise instead. + */ + PJMEDIA_ORIENT_ROTATE_90DEG, + + /** + * Specifies that the video/picture needs to be rotated 180 degrees + * from its natural orientation. + */ + PJMEDIA_ORIENT_ROTATE_180DEG, + + /** + * Specifies that the video/picture needs to be rotated 270 degrees + * from its natural orientation in clockwise direction from the user's + * perspective. + * Note that for devices with back cameras (which faces away + * from the user), the video will actually need to be rotated + * 90 degrees clockwise instead. + */ + PJMEDIA_ORIENT_ROTATE_270DEG + +} pjmedia_orient; + +/** + * Macro for packing format from a four character code, similar to FOURCC. + */ +#define PJMEDIA_FOURCC(C1, C2, C3, C4) (C4 << 24 | C3 << 16 | C2 << 8 | C1) + +/** + * Utility function to return the string name for a pjmedia_type. + * + * @param t The media type. + * + * @return String. + */ +PJ_DECL(const char *) pjmedia_type_name(pjmedia_type t); + +/** + * Utility function to return the media type for a media name string. + * + * @param name The media name string. + * + * @return media type. + */ +PJ_DECL(pjmedia_type) pjmedia_get_type(const pj_str_t *name); + +/** + * A utility function to convert fourcc type of value to four letters string. + * + * @param sig The fourcc value. + * @param buf Buffer to store the string, which MUST be at least + * five bytes long. + * + * @return The string. + */ +PJ_INLINE(const char *) pjmedia_fourcc_name(pj_uint32_t sig, char buf[]) +{ + buf[3] = (char)((sig >> 24) & 0xFF); + buf[2] = (char)((sig >> 16) & 0xFF); + buf[1] = (char)((sig >> 8) & 0xFF); + buf[0] = (char)((sig >> 0) & 0xFF); + buf[4] = '\0'; + return buf; +} + +/** + * @} + */ + +#endif /* __PJMEDIA_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c new file mode 100755 index 000000000..370c5164a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c @@ -0,0 +1,1655 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + SKIP_WS = 0, + SYNTAX_ERROR = 1, +}; +// New token definition from RFC 4566 (SDP) +#define TOKEN "!#$%&'*+-.^_`{|}~" +//#define TOKEN "-.!%*_=`'~" +//#define TOKEN "'`-./:?\"#$&*;=@[]^_`{|}+~!" +#define NTP_OFFSET ((pj_uint32_t)2208988800) +#define THIS_FILE "sdp.c" + +typedef struct parse_context { + pj_status_t last_error; +} parse_context; + +/* + * Prototypes for line parser. + */ +static void parse_version(pj_scanner *scanner, volatile parse_context *ctx); +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx); +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx); +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, volatile parse_context *ctx); +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, volatile parse_context *ctx); +static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw, volatile parse_context *ctx); +static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner, volatile parse_context *ctx); +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, volatile parse_context *ctx); +static void on_scanner_error(pj_scanner *scanner); + +/* + * Scanner character specification. + */ +static int is_initialized; +static pj_cis_buf_t cis_buf; +static pj_cis_t cs_digit, cs_token; + +static void init_sdp_parser(void) +{ + if (is_initialized != 0) + return; + + pj_enter_critical_section(); + + if (is_initialized != 0) { + pj_leave_critical_section(); + return; + } + + pj_cis_buf_init(&cis_buf); + + pj_cis_init(&cis_buf, &cs_token); + pj_cis_add_alpha(&cs_token); + pj_cis_add_num(&cs_token); + pj_cis_add_str(&cs_token, TOKEN); + + pj_cis_init(&cis_buf, &cs_digit); + pj_cis_add_num(&cs_digit); + + is_initialized = 1; + pj_leave_critical_section(); +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_create(pj_pool_t *pool, const char *name, const pj_str_t *value) +{ + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && name, NULL); + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + pj_strdup2(pool, &attr->name, name); + + if (value) + pj_strdup_with_null(pool, &attr->value, value); + else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + return attr; +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_clone(pj_pool_t *pool, const pjmedia_sdp_attr *rhs) +{ + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + + pj_strdup(pool, &attr->name, &rhs->name); + pj_strdup_with_null(pool, &attr->value, &rhs->value); + + return attr; +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find(unsigned count, pjmedia_sdp_attr *const attr_array[], const pj_str_t *name, const pj_str_t *c_fmt) +{ + unsigned i; + unsigned c_pt = 0xFFFF; + + PJ_ASSERT_RETURN(count <= PJMEDIA_MAX_SDP_ATTR, NULL); + + if (c_fmt) + c_pt = pj_strtoul(c_fmt); + + for (i = 0; i < count; ++i) { + if (pj_strcmp(&attr_array[i]->name, name) == 0) { + const pjmedia_sdp_attr *a = attr_array[i]; + if (c_fmt) { + unsigned pt = (unsigned)pj_strtoul2(&a->value, NULL, 10); + if (pt == c_pt) { + return (pjmedia_sdp_attr *)a; + } + } else + return (pjmedia_sdp_attr *)a; + } + } + return NULL; +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find2(unsigned count, pjmedia_sdp_attr *const attr_array[], const char *c_name, const pj_str_t *c_fmt) +{ + pj_str_t name; + + name.ptr = (char *)c_name; + name.slen = pj_ansi_strlen(c_name); + + return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr) +{ + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count < PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + attr_array[*count] = attr; + (*count)++; + + return PJ_SUCCESS; +} + +PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, pjmedia_sdp_attr *attr_array[], const char *name) +{ + unsigned i, removed = 0; + pj_str_t attr_name; + + PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL); + PJ_ASSERT_RETURN(*count <= PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + attr_name.ptr = (char *)name; + attr_name.slen = pj_ansi_strlen(name); + + for (i = 0; i < *count;) { + if (pj_strcmp(&attr_array[i]->name, &attr_name) == 0) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr *), *count, i); + --(*count); + ++removed; + } else { + ++i; + } + } + + return removed; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr) +{ + unsigned i, removed = 0; + + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count <= PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + for (i = 0; i < *count;) { + if (attr_array[i] == attr) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr *), *count, i); + --(*count); + ++removed; + } else { + ++i; + } + } + + return removed ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap *rtpmap) +{ + pj_scanner scanner; + pj_str_t token; + pj_status_t status = -1; + char term = 0; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + init_sdp_parser(); + + /* Check if input is null terminated, and null terminate if + * necessary. Unfortunately this may crash the application if + * attribute was allocated from a read-only memory location. + * But this shouldn't happen as attribute's value normally is + * null terminated. + */ + if (attr->value.ptr[attr->value.slen] != 0 && attr->value.ptr[attr->value.slen] != '\r' && + attr->value.ptr[attr->value.slen] != '\n') { + pj_assert(!"Shouldn't happen"); + term = attr->value.ptr[attr->value.slen]; + attr->value.ptr[attr->value.slen] = '\0'; + } + + /* The buffer passed to the scanner is not guaranteed to be NULL + * terminated, but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, (char *)attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_scanner_error); + + /* rtpmap sample: + * a=rtpmap:98 L16/16000/2. + */ + + /* Init */ + rtpmap->pt.slen = rtpmap->param.slen = rtpmap->enc_name.slen = 0; + rtpmap->clock_rate = 0; + + /* Parse */ + PJ_TRY + { + + /* Get payload type. */ + pj_scan_get(&scanner, &cs_token, &rtpmap->pt); + + /* Get encoding name. */ + pj_scan_get(&scanner, &cs_token, &rtpmap->enc_name); + + /* Expecting '/' after encoding name. */ + if (pj_scan_get_char(&scanner) != '/') { + status = PJMEDIA_SDP_EINRTPMAP; + goto on_return; + } + + /* Get the clock rate. */ + pj_scan_get(&scanner, &cs_digit, &token); + rtpmap->clock_rate = pj_strtoul(&token); + + /* Expecting either '/' or EOF */ + if (*scanner.curptr == '/') { + /* Skip the '/' */ + pj_scan_get_char(&scanner); + pj_scan_get(&scanner, &cs_token, &rtpmap->param); + } else { + rtpmap->param.slen = 0; + } + + status = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + status = PJMEDIA_SDP_EINRTPMAP; + } + PJ_END; + +on_return: + pj_scan_fini(&scanner); + if (term) { + attr->value.ptr[attr->value.slen] = term; + } + return status; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp(const pjmedia_sdp_attr *attr, pjmedia_sdp_fmtp *fmtp) +{ + const char *p = attr->value.ptr; + const char *end = attr->value.ptr + attr->value.slen; + pj_str_t token; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "fmtp") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + /* fmtp BNF: + * a=fmtp: + */ + + /* Get format. */ + token.ptr = (char *)p; + while (pj_isdigit(*p) && p != end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINFMTP; + + fmtp->fmt = token; + + /* Expecting space after format. */ + if (*p != ' ') + return PJMEDIA_SDP_EINFMTP; + + /* Get space. */ + ++p; + + /* Set the remaining string as fmtp format parameter. */ + fmtp->fmt_param.ptr = (char *)p; + fmtp->fmt_param.slen = end - p; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtcp_attr *rtcp) +{ + pj_scanner scanner; + pj_str_t token; + pj_status_t status = -1; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtcp") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + init_sdp_parser(); + + /* fmtp BNF: + * a=rtcp: [nettype addrtype address] + */ + + /* The buffer passed to the scanner is not guaranteed to be NULL + * terminated, but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, (char *)attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_scanner_error); + + /* Init */ + rtcp->net_type.slen = rtcp->addr_type.slen = rtcp->addr.slen = 0; + + /* Parse */ + PJ_TRY + { + + /* Get the port */ + pj_scan_get(&scanner, &cs_token, &token); + rtcp->port = pj_strtoul(&token); + + /* Have address? */ + if (!pj_scan_is_eof(&scanner)) { + + /* Get network type */ + pj_scan_get(&scanner, &cs_token, &rtcp->net_type); + + /* Get address type */ + pj_scan_get(&scanner, &cs_token, &rtcp->addr_type); + + /* Get the address */ + // pj_scan_get(&scanner, &cs_token, &rtcp->addr); + pj_scan_get_until_chr(&scanner, "/ \t\r\n", &rtcp->addr); + } + + status = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + status = PJMEDIA_SDP_EINRTCP; + } + PJ_END; + + pj_scan_fini(&scanner); + return status; +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, const pj_sockaddr *a) +{ + enum { ATTR_LEN = PJ_INET6_ADDRSTRLEN + 16 }; + char tmp_addr[PJ_INET6_ADDRSTRLEN]; + pjmedia_sdp_attr *attr; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("rtcp"); + attr->value.ptr = (char *)pj_pool_alloc(pool, ATTR_LEN); + if (a->addr.sa_family == pj_AF_INET()) { + attr->value.slen = pj_ansi_snprintf(attr->value.ptr, ATTR_LEN, "%u IN IP4 %s", pj_sockaddr_get_port(a), + pj_sockaddr_print(a, tmp_addr, sizeof(tmp_addr), 0)); + } else if (a->addr.sa_family == pj_AF_INET6()) { + attr->value.slen = pj_ansi_snprintf(attr->value.ptr, ATTR_LEN, "%u IN IP6 %s", pj_sockaddr_get_port(a), + pj_sockaddr_print(a, tmp_addr, sizeof(tmp_addr), 0)); + + } else { + pj_assert(!"Unsupported address family"); + return NULL; + } + + return attr; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_ssrc(const pjmedia_sdp_attr *attr, pjmedia_sdp_ssrc_attr *ssrc) +{ + pj_scanner scanner; + pj_str_t token; + pj_status_t status = -1; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "ssrc") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + init_sdp_parser(); + + /* ssrc BNF: + * a=ssrc: + * a=ssrc: : + */ + + /* The buffer passed to the scanner is not guaranteed to be NULL + * terminated, but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, (char *)attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_scanner_error); + + /* Init */ + pj_bzero(ssrc, sizeof(*ssrc)); + + /* Parse */ + PJ_TRY + { + pj_str_t scan_attr; + + /* Get the ssrc */ + pj_scan_get(&scanner, &cs_digit, &token); + ssrc->ssrc = pj_strtoul(&token); + + pj_scan_get_char(&scanner); + pj_scan_get(&scanner, &cs_token, &scan_attr); + + /* Get cname attribute, if any */ + if (!pj_scan_is_eof(&scanner) && pj_scan_get_char(&scanner) == ':' && pj_strcmp2(&scan_attr, "cname")) { + pj_scan_get(&scanner, &cs_token, &ssrc->cname); + } + + status = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + status = PJMEDIA_SDP_EINSSRC; + } + PJ_END; + + pj_scan_fini(&scanner); + return status; +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_ssrc(pj_pool_t *pool, pj_uint32_t ssrc, const pj_str_t *cname) +{ + pjmedia_sdp_attr *attr; + + if (cname->slen == 0) + return NULL; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("ssrc"); + attr->value.ptr = (char *)pj_pool_alloc(pool, cname->slen + 7 /* " cname:"*/ + + 10 /* 32-bit integer */ + + 1 /* NULL */); + attr->value.slen = + pj_ansi_snprintf(attr->value.ptr, cname->slen + 18, "%u cname:%.*s", ssrc, (int)cname->slen, cname->ptr); + + return attr; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap **p_rtpmap) +{ + PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL); + + *p_rtpmap = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_rtpmap); + PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM); + + return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap); +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, const pjmedia_sdp_rtpmap *rtpmap, pjmedia_sdp_attr **p_attr) +{ + pjmedia_sdp_attr *attr; + char tempbuf[128]; + int len; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && rtpmap && p_attr, PJ_EINVAL); + + /* Check that mandatory attributes are specified. */ + PJ_ASSERT_RETURN(rtpmap->enc_name.slen && rtpmap->clock_rate, PJMEDIA_SDP_EINRTPMAP); + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM); + + attr->name.ptr = "rtpmap"; + attr->name.slen = 6; + + /* Format: ":pt enc_name/clock_rate[/param]" */ + len = pj_ansi_snprintf(tempbuf, sizeof(tempbuf), "%.*s %.*s/%u%s%.*s", (int)rtpmap->pt.slen, rtpmap->pt.ptr, + (int)rtpmap->enc_name.slen, rtpmap->enc_name.ptr, rtpmap->clock_rate, + (rtpmap->param.slen ? "/" : ""), (int)rtpmap->param.slen, rtpmap->param.ptr); + + if (len < 1 || len >= (int)sizeof(tempbuf)) + return PJMEDIA_SDP_ERTPMAPTOOLONG; + + attr->value.slen = len; + attr->value.ptr = (char *)pj_pool_alloc(pool, attr->value.slen + 1); + pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen + 1); + + *p_attr = attr; + return PJ_SUCCESS; +} + +static int print_connection_info(pjmedia_sdp_conn *c, char *buf, int len) +{ + int printed; + + printed = pj_ansi_snprintf(buf, len, "c=%.*s %.*s %.*s\r\n", (int)c->net_type.slen, c->net_type.ptr, + (int)c->addr_type.slen, c->addr_type.ptr, (int)c->addr.slen, c->addr.ptr); + if (printed < 1 || printed >= len) + return -1; + + return printed; +} + +PJ_DEF(pjmedia_sdp_conn *) pjmedia_sdp_conn_clone(pj_pool_t *pool, const pjmedia_sdp_conn *rhs) +{ + pjmedia_sdp_conn *c = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_conn); + if (!c) + return NULL; + + if (!pj_strdup(pool, &c->net_type, &rhs->net_type)) + return NULL; + if (!pj_strdup(pool, &c->addr_type, &rhs->addr_type)) + return NULL; + if (!pj_strdup(pool, &c->addr, &rhs->addr)) + return NULL; + + return c; +} + +PJ_DEF(pjmedia_sdp_bandw *) +pjmedia_sdp_bandw_clone(pj_pool_t *pool, const pjmedia_sdp_bandw *rhs) +{ + pjmedia_sdp_bandw *b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw); + if (!b) + return NULL; + + if (!pj_strdup(pool, &b->modifier, &rhs->modifier)) + return NULL; + b->value = rhs->value; + + return b; +} + +static pj_ssize_t print_bandw(const pjmedia_sdp_bandw *bandw, char *buf, pj_size_t len) +{ + char *p = buf; + + if ((int)len < bandw->modifier.slen + 10 + 5) + return -1; + + *p++ = 'b'; + *p++ = '='; + pj_memcpy(p, bandw->modifier.ptr, bandw->modifier.slen); + p += bandw->modifier.slen; + *p++ = ':'; + p += pj_utoa(bandw->value, p); + + *p++ = '\r'; + *p++ = '\n'; + return p - buf; +} + +static pj_ssize_t print_attr(const pjmedia_sdp_attr *attr, char *buf, pj_size_t len) +{ + char *p = buf; + + if ((int)len < attr->name.slen + attr->value.slen + 10) + return -1; + + *p++ = 'a'; + *p++ = '='; + pj_memcpy(p, attr->name.ptr, attr->name.slen); + p += attr->name.slen; + + if (attr->value.slen) { + *p++ = ':'; + pj_memcpy(p, attr->value.ptr, attr->value.slen); + p += attr->value.slen; + } + + *p++ = '\r'; + *p++ = '\n'; + return p - buf; +} + +static int print_media_desc(const pjmedia_sdp_media *m, char *buf, pj_size_t len) +{ + char *p = buf; + char *end = buf + len; + unsigned i; + int printed; + + /* check length for the "m=" line. */ + if (len < (pj_size_t)m->desc.media.slen + m->desc.transport.slen + 12 + 24) { + return -1; + } + *p++ = 'm'; /* m= */ + *p++ = '='; + pj_memcpy(p, m->desc.media.ptr, m->desc.media.slen); + p += m->desc.media.slen; + *p++ = ' '; + printed = pj_utoa(m->desc.port, p); + p += printed; + if (m->desc.port_count > 1) { + *p++ = '/'; + printed = pj_utoa(m->desc.port_count, p); + p += printed; + } + *p++ = ' '; + pj_memcpy(p, m->desc.transport.ptr, m->desc.transport.slen); + p += m->desc.transport.slen; + for (i = 0; i < m->desc.fmt_count; ++i) { + if (end - p > m->desc.fmt[i].slen) { + *p++ = ' '; + pj_memcpy(p, m->desc.fmt[i].ptr, m->desc.fmt[i].slen); + p += m->desc.fmt[i].slen; + } else { + return -1; + } + } + + if (end - p >= 2) { + *p++ = '\r'; + *p++ = '\n'; + } else { + return -1; + } + + /* print connection info, if present. */ + if (m->conn) { + printed = print_connection_info(m->conn, p, (int)(end - p)); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* print optional bandwidth info. */ + for (i = 0; i < m->bandw_count; ++i) { + printed = (int)print_bandw(m->bandw[i], p, end - p); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* print attributes. */ + for (i = 0; i < m->attr_count; ++i) { + printed = (int)print_attr(m->attr[i], p, end - p); + if (printed < 0) { + return -1; + } + p += printed; + } + + return (int)(p - buf); +} + +PJ_DEF(int) pjmedia_sdp_media_print(const pjmedia_sdp_media *media, char *buf, pj_size_t size) +{ + return print_media_desc(media, buf, size); +} + +PJ_DEF(pjmedia_sdp_media *) pjmedia_sdp_media_clone(pj_pool_t *pool, const pjmedia_sdp_media *rhs) +{ + unsigned int i; + pjmedia_sdp_media *m = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_media); + PJ_ASSERT_RETURN(m != NULL, NULL); + + pj_strdup(pool, &m->desc.media, &rhs->desc.media); + m->desc.port = rhs->desc.port; + m->desc.port_count = rhs->desc.port_count; + pj_strdup(pool, &m->desc.transport, &rhs->desc.transport); + m->desc.fmt_count = rhs->desc.fmt_count; + for (i = 0; i < rhs->desc.fmt_count; ++i) + pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]); + + if (rhs->conn) { + m->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(m->conn != NULL, NULL); + } else { + m->conn = NULL; + } + + m->bandw_count = rhs->bandw_count; + for (i = 0; i < rhs->bandw_count; ++i) { + m->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]); + PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL); + } + + m->attr_count = rhs->attr_count; + for (i = 0; i < rhs->attr_count; ++i) { + m->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]); + PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL); + } + + return m; +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, const pj_str_t *name, const pj_str_t *fmt) +{ + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt); +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, const char *name, const pj_str_t *fmt) +{ + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_media_add_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s, pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_add(&s->attr_count, s->attr, attr); +} + +PJ_DEF(unsigned) pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, const char *name) +{ + return pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, name); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); +} + +static int print_session(const pjmedia_sdp_session *ses, char *buf, pj_ssize_t len) +{ + char *p = buf; + char *end = buf + len; + unsigned i; + int printed; + + /* Check length for v= and o= lines. */ + if (len < 5 + 2 + ses->origin.user.slen + 18 + ses->origin.net_type.slen + ses->origin.addr.slen + 2) { + return -1; + } + + /* SDP version (v= line) */ + pj_memcpy(p, "v=0\r\n", 5); + p += 5; + + /* Owner (o=) line. */ + *p++ = 'o'; + *p++ = '='; + pj_memcpy(p, ses->origin.user.ptr, ses->origin.user.slen); + p += ses->origin.user.slen; + *p++ = ' '; + printed = pj_utoa(ses->origin.id, p); + p += printed; + *p++ = ' '; + printed = pj_utoa(ses->origin.version, p); + p += printed; + *p++ = ' '; + pj_memcpy(p, ses->origin.net_type.ptr, ses->origin.net_type.slen); + p += ses->origin.net_type.slen; + *p++ = ' '; + pj_memcpy(p, ses->origin.addr_type.ptr, ses->origin.addr_type.slen); + p += ses->origin.addr_type.slen; + *p++ = ' '; + pj_memcpy(p, ses->origin.addr.ptr, ses->origin.addr.slen); + p += ses->origin.addr.slen; + *p++ = '\r'; + *p++ = '\n'; + + /* Session name (s=) line. */ + if ((end - p) < 8 + ses->name.slen) { + return -1; + } + *p++ = 's'; + *p++ = '='; + pj_memcpy(p, ses->name.ptr, ses->name.slen); + p += ses->name.slen; + *p++ = '\r'; + *p++ = '\n'; + + /* Connection line (c=) if exist. */ + if (ses->conn) { + printed = print_connection_info(ses->conn, p, (int)(end - p)); + if (printed < 1) { + return -1; + } + p += printed; + } + + /* print optional bandwidth info. */ + for (i = 0; i < ses->bandw_count; ++i) { + printed = (int)print_bandw(ses->bandw[i], p, end - p); + if (printed < 1) { + return -1; + } + p += printed; + } + + /* Time */ + if ((end - p) < 24) { + return -1; + } + *p++ = 't'; + *p++ = '='; + printed = pj_utoa(ses->time.start, p); + p += printed; + *p++ = ' '; + printed = pj_utoa(ses->time.stop, p); + p += printed; + *p++ = '\r'; + *p++ = '\n'; + + /* Print all attribute (a=) lines. */ + for (i = 0; i < ses->attr_count; ++i) { + printed = (int)print_attr(ses->attr[i], p, end - p); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* Print media (m=) lines. */ + for (i = 0; i < ses->media_count; ++i) { + printed = print_media_desc(ses->media[i], p, (int)(end - p)); + if (printed < 0) { + return -1; + } + p += printed; + } + + return (int)(p - buf); +} + +/****************************************************************************** + * PARSERS + */ + +static void parse_version(pj_scanner *scanner, volatile parse_context *ctx) +{ + ctx->last_error = PJMEDIA_SDP_EINVER; + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* check version is 0 */ + if (scanner->curptr + 2 >= scanner->end || *(scanner->curptr + 2) != '0') { + on_scanner_error(scanner); + return; + } + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINORIGIN; + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* o= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* username. */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.user); + pj_scan_get_char(scanner); + + /* id */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->origin.id = pj_strtoul(&str); + pj_scan_get_char(scanner); + + /* version */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->origin.version = pj_strtoul(&str); + pj_scan_get_char(scanner); + + /* network-type */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.net_type); + pj_scan_get_char(scanner); + + /* addr-type */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.addr_type); + pj_scan_get_char(scanner); + + /* address */ + pj_scan_get_until_chr(scanner, " \t\r\n", &ses->origin.addr); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINTIME; + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* t= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* start time */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->time.start = pj_strtoul(&str); + + pj_scan_get_char(scanner); + + /* stop time */ + pj_scan_get_until_chr(scanner, " \t\r\n", &str); + ses->time.stop = pj_strtoul(&str); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, volatile parse_context *ctx) +{ + ctx->last_error = PJMEDIA_SDP_EINSDP; + + /* check equal sign */ + if ((scanner->curptr + 1 >= scanner->end) || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* x= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* get anything until newline (including whitespaces). */ + pj_scan_get_until_chr(scanner, "\r\n", str); + + /* newline. */ + pj_scan_get_newline(scanner); +} + +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, volatile parse_context *ctx) +{ + ctx->last_error = PJMEDIA_SDP_EINCONN; + + /* c= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* network-type */ + pj_scan_get_until_ch(scanner, ' ', &conn->net_type); + pj_scan_get_char(scanner); + + /* addr-type */ + pj_scan_get_until_ch(scanner, ' ', &conn->addr_type); + pj_scan_get_char(scanner); + + /* address. */ + pj_scan_get_until_chr(scanner, "/ \t\r\n", &conn->addr); + PJ_TODO(PARSE_SDP_CONN_ADDRESS_SUBFIELDS); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINBANDW; + + /* b= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* modifier */ + pj_scan_get_until_ch(scanner, ':', &bandw->modifier); + pj_scan_get_char(scanner); + + /* value */ + pj_scan_get_until_chr(scanner, " \t\r\n", &str); + bandw->value = pj_strtoul(&str); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINMEDIA; + + /* check the equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* m= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* type */ + pj_scan_get_until_ch(scanner, ' ', &med->desc.media); + pj_scan_get_char(scanner); + + /* port */ + pj_scan_get(scanner, &cs_token, &str); + med->desc.port = (unsigned short)pj_strtoul(&str); + if (pj_scan_is_eof(scanner)) { + on_scanner_error(scanner); + return; + } + if (*scanner->curptr == '/') { + /* port count */ + pj_scan_get_char(scanner); + pj_scan_get(scanner, &cs_token, &str); + med->desc.port_count = pj_strtoul(&str); + + } else { + med->desc.port_count = 0; + } + + if (pj_scan_get_char(scanner) != ' ') { + on_scanner_error(scanner); + } + + /* transport */ + pj_scan_get_until_chr(scanner, " \t\r\n", &med->desc.transport); + + /* format list */ + med->desc.fmt_count = 0; + while (scanner->curptr < scanner->end && *scanner->curptr == ' ') { + pj_str_t fmt; + + pj_scan_get_char(scanner); + + /* Check again for the end of the line */ + if ((*scanner->curptr == '\r') || (*scanner->curptr == '\n')) + break; + + pj_scan_get(scanner, &cs_token, &fmt); + if (med->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) + med->desc.fmt[med->desc.fmt_count++] = fmt; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding SDP media format %.*s, " + "format is ignored", + (int)fmt.slen, fmt.ptr)); + } + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void on_scanner_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + + PJ_THROW(SYNTAX_ERROR); +} + +static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner, volatile parse_context *ctx) +{ + pjmedia_sdp_attr *attr; + + ctx->last_error = PJMEDIA_SDP_EINATTR; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return NULL; + } + + /* skip a= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* get attr name. */ + pj_scan_get(scanner, &cs_token, &attr->name); + + if (*scanner->curptr && *scanner->curptr != '\r' && *scanner->curptr != '\n') { + /* skip ':' if present. */ + if (*scanner->curptr == ':') + pj_scan_get_char(scanner); + + /* get value */ + if (!pj_scan_is_eof(scanner) && *scanner->curptr != '\r' && *scanner->curptr != '\n') { + pj_scan_get_until_chr(scanner, "\r\n", &attr->value); + } else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + } else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); + + return attr; +} + +/* + * Apply direction attribute in session to all media. + */ +static void apply_media_direction(pjmedia_sdp_session *sdp) +{ + pjmedia_sdp_attr *dir_attr = NULL; + unsigned i; + + const pj_str_t inactive = {"inactive", 8}; + const pj_str_t sendonly = {"sendonly", 8}; + const pj_str_t recvonly = {"recvonly", 8}; + const pj_str_t sendrecv = {"sendrecv", 8}; + + /* Find direction attribute in session, don't need to find default + * direction "sendrecv". + */ + for (i = 0; i < sdp->attr_count && !dir_attr; ++i) { + if (!pj_strcmp(&sdp->attr[i]->name, &sendonly) || !pj_strcmp(&sdp->attr[i]->name, &recvonly) || + !pj_strcmp(&sdp->attr[i]->name, &inactive)) { + dir_attr = sdp->attr[i]; + } + } + + /* Found the direction attribute */ + if (dir_attr) { + /* Remove the direction attribute in session */ + pjmedia_sdp_attr_remove(&sdp->attr_count, sdp->attr, dir_attr); + + /* Apply the direction attribute to all media, but not overriding it + * if media already has direction attribute. + */ + for (i = 0; i < sdp->media_count; ++i) { + pjmedia_sdp_media *m; + unsigned j; + + /* Find direction attribute in this media */ + m = sdp->media[i]; + for (j = 0; j < m->attr_count; ++j) { + if (!pj_strcmp(&m->attr[j]->name, &sendrecv) || !pj_strcmp(&m->attr[j]->name, &sendonly) || + !pj_strcmp(&m->attr[j]->name, &recvonly) || !pj_strcmp(&m->attr[j]->name, &inactive)) { + break; + } + } + + /* Not found, apply direction attribute from session */ + if (j == m->attr_count) + pjmedia_sdp_media_add_attr(m, dir_attr); + } + } +} + +/* + * Parse SDP message. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_parse(pj_pool_t *pool, char *buf, pj_size_t len, pjmedia_sdp_session **p_sdp) +{ + pj_scanner scanner; + pjmedia_sdp_session *session; + pjmedia_sdp_media *media = NULL; + pjmedia_sdp_attr *attr; + pjmedia_sdp_conn *conn; + pjmedia_sdp_bandw *bandw; + pj_str_t dummy; + int cur_name = 254; + volatile parse_context ctx; + PJ_USE_EXCEPTION; + + ctx.last_error = PJ_SUCCESS; + + init_sdp_parser(); + + pj_scan_init(&scanner, buf, len, 0, &on_scanner_error); + session = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); + PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM); + + /* Ignore leading newlines */ + while (*scanner.curptr == '\r' || *scanner.curptr == '\n') + pj_scan_get_char(&scanner); + + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + cur_name = *scanner.curptr; + switch (cur_name) { + case 'a': + attr = parse_attr(pool, &scanner, &ctx); + if (attr) { + if (media) { + if (media->attr_count < PJMEDIA_MAX_SDP_ATTR) + pjmedia_sdp_media_add_attr(media, attr); + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding media attribute, " + "attribute is ignored")); + } else { + if (session->attr_count < PJMEDIA_MAX_SDP_ATTR) + pjmedia_sdp_session_add_attr(session, attr); + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding session attribute" + ", attribute is ignored")); + } + } + break; + case 'o': + parse_origin(&scanner, session, &ctx); + break; + case 's': + parse_generic_line(&scanner, &session->name, &ctx); + break; + case 'c': + conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + parse_connection_info(&scanner, conn, &ctx); + if (media) { + media->conn = conn; + } else { + session->conn = conn; + } + break; + case 't': + parse_time(&scanner, session, &ctx); + break; + case 'm': + media = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + parse_media(&scanner, media, &ctx); + if (session->media_count < PJMEDIA_MAX_SDP_MEDIA) + session->media[session->media_count++] = media; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, "Error adding media, media is ignored")); + break; + case 'v': + parse_version(&scanner, &ctx); + break; + case 13: + case 10: + pj_scan_get_char(&scanner); + /* Allow empty newlines at the end of the message */ + while (!pj_scan_is_eof(&scanner)) { + if (*scanner.curptr != 13 && *scanner.curptr != 10) { + ctx.last_error = PJMEDIA_SDP_EINSDP; + on_scanner_error(&scanner); + } + pj_scan_get_char(&scanner); + } + break; + case 'b': + bandw = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_bandw); + parse_bandwidth_info(&scanner, bandw, &ctx); + if (media) { + if (media->bandw_count < PJMEDIA_MAX_SDP_BANDW) + media->bandw[media->bandw_count++] = bandw; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding media bandwidth " + "info, info is ignored")); + } else { + if (session->bandw_count < PJMEDIA_MAX_SDP_BANDW) + session->bandw[session->bandw_count++] = bandw; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding session bandwidth " + "info, info is ignored")); + } + break; + default: + if (cur_name >= 'a' && cur_name <= 'z') + parse_generic_line(&scanner, &dummy, &ctx); + else { + ctx.last_error = PJMEDIA_SDP_EINSDP; + on_scanner_error(&scanner); + } + break; + } + } + + ctx.last_error = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + PJ_PERROR(4, (THIS_FILE, ctx.last_error, "Error parsing SDP in line %d col %d", scanner.line, + pj_scan_get_col(&scanner))); + + session = NULL; + + pj_assert(ctx.last_error != PJ_SUCCESS); + } + PJ_END; + + pj_scan_fini(&scanner); + + if (session) + apply_media_direction(session); + + *p_sdp = session; + return ctx.last_error; +} + +/* + * Print SDP description. + */ +PJ_DEF(int) pjmedia_sdp_print(const pjmedia_sdp_session *desc, char *buf, pj_size_t size) +{ + return print_session(desc, buf, size); +} + +/* + * Clone session + */ +PJ_DEF(pjmedia_sdp_session *) pjmedia_sdp_session_clone(pj_pool_t *pool, const pjmedia_sdp_session *rhs) +{ + pjmedia_sdp_session *sess; + unsigned i; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + sess = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); + PJ_ASSERT_RETURN(sess != NULL, NULL); + + /* Clone origin line. */ + pj_strdup(pool, &sess->origin.user, &rhs->origin.user); + sess->origin.id = rhs->origin.id; + sess->origin.version = rhs->origin.version; + pj_strdup(pool, &sess->origin.net_type, &rhs->origin.net_type); + pj_strdup(pool, &sess->origin.addr_type, &rhs->origin.addr_type); + pj_strdup(pool, &sess->origin.addr, &rhs->origin.addr); + + /* Clone subject line. */ + pj_strdup(pool, &sess->name, &rhs->name); + + /* Clone connection line */ + if (rhs->conn) { + sess->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(sess->conn != NULL, NULL); + } + + /* Duplicate bandwidth info */ + sess->bandw_count = rhs->bandw_count; + for (i = 0; i < rhs->bandw_count; ++i) { + sess->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]); + } + + /* Clone time line. */ + sess->time.start = rhs->time.start; + sess->time.stop = rhs->time.stop; + + /* Duplicate session attributes. */ + sess->attr_count = rhs->attr_count; + for (i = 0; i < rhs->attr_count; ++i) { + sess->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]); + } + + /* Duplicate media descriptors. */ + sess->media_count = rhs->media_count; + for (i = 0; i < rhs->media_count; ++i) { + sess->media[i] = pjmedia_sdp_media_clone(pool, rhs->media[i]); + } + + return sess; +} + +#define CHECK(exp, ret) \ + do { \ + /*pj_assert(exp);*/ \ + if (!(exp)) \ + return ret; \ + } while (0) + +/* Validate SDP connetion info. */ +static pj_status_t validate_sdp_conn(const pjmedia_sdp_conn *c) +{ + CHECK(c, PJ_EINVAL); + CHECK(pj_strcmp2(&c->net_type, "IN") == 0, PJMEDIA_SDP_EINCONN); + CHECK(pj_strcmp2(&c->addr_type, "IP4") == 0 || pj_strcmp2(&c->addr_type, "IP6") == 0, PJMEDIA_SDP_EINCONN); + CHECK(c->addr.slen != 0, PJMEDIA_SDP_EINCONN); + + return PJ_SUCCESS; +} + +/* Validate SDP session descriptor. */ +PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp) +{ + return pjmedia_sdp_validate2(sdp, PJ_TRUE); +} + +/* Validate SDP session descriptor. */ +PJ_DEF(pj_status_t) pjmedia_sdp_validate2(const pjmedia_sdp_session *sdp, pj_bool_t strict) +{ + unsigned i; + const pj_str_t STR_RTPMAP = {"rtpmap", 6}; + + CHECK(sdp != NULL, PJ_EINVAL); + + /* Validate origin line. */ + CHECK(sdp->origin.user.slen != 0, PJMEDIA_SDP_EINORIGIN); + CHECK(pj_strcmp2(&sdp->origin.net_type, "IN") == 0, PJMEDIA_SDP_EINORIGIN); + CHECK(pj_strcmp2(&sdp->origin.addr_type, "IP4") == 0 || pj_strcmp2(&sdp->origin.addr_type, "IP6") == 0, + PJMEDIA_SDP_EINORIGIN); + CHECK(sdp->origin.addr.slen != 0, PJMEDIA_SDP_EINORIGIN); + + /* Validate subject line. */ + CHECK(sdp->name.slen != 0, PJMEDIA_SDP_EINNAME); + + /* Ignore start and stop time. */ + + /* If session level connection info is present, validate it. */ + if (sdp->conn) { + pj_status_t status = validate_sdp_conn(sdp->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* Validate each media. */ + for (i = 0; i < sdp->media_count; ++i) { + const pjmedia_sdp_media *m = sdp->media[i]; + unsigned j; + + /* Validate the m= line. */ + CHECK(m->desc.media.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK(m->desc.transport.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK(m->desc.fmt_count != 0 || m->desc.port == 0, PJMEDIA_SDP_ENOFMT); + + /* If media level connection info is present, validate it. */ + if (m->conn) { + pj_status_t status = validate_sdp_conn(m->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* If media doesn't have connection info, then connection info + * must be present in the session. + */ + if (m->conn == NULL) { + if (sdp->conn == NULL) + if (strict || m->desc.port != 0) + return PJMEDIA_SDP_EMISSINGCONN; + } + + /* Verify payload type. */ + for (j = 0; j < m->desc.fmt_count; ++j) { + + /* Arrgh noo!! Payload type can be non-numeric!! + * RTC based programs sends "null" for instant messaging! + */ + if (pj_isdigit(*m->desc.fmt[j].ptr)) { + unsigned long pt; + pj_status_t status = pj_strtoul3(&m->desc.fmt[j], &pt, 10); + + /* Payload type is between 0 and 127. + */ + CHECK(status == PJ_SUCCESS && pt <= 127, PJMEDIA_SDP_EINPT); + + /* If port is not zero, then for each dynamic payload type, an + * rtpmap attribute must be specified. + */ + if (m->desc.port != 0 && pt >= 96) { + const pjmedia_sdp_attr *a; + + a = pjmedia_sdp_media_find_attr(m, &STR_RTPMAP, &m->desc.fmt[j]); + CHECK(a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP); + } + } + } + } + + /* Looks good. */ + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_transport_cmp(const pj_str_t *t1, const pj_str_t *t2) +{ + pj_uint32_t t1_proto, t2_proto; + + /* Exactly equal? */ + if (pj_stricmp(t1, t2) == 0) + return PJ_SUCCESS; + + /* Check if boths are RTP/AVP based */ + t1_proto = pjmedia_sdp_transport_get_proto(t1); + t2_proto = pjmedia_sdp_transport_get_proto(t2); + if (PJMEDIA_TP_PROTO_HAS_FLAG(t1_proto, PJMEDIA_TP_PROTO_RTP_AVP) && + PJMEDIA_TP_PROTO_HAS_FLAG(t2_proto, PJMEDIA_TP_PROTO_RTP_AVP)) { + return PJ_SUCCESS; + } + + /* Compatible? */ + //{ + // static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; + // static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 }; + // if ((!pj_stricmp(t1, &ID_RTP_AVP) || !pj_stricmp(t1, &ID_RTP_SAVP)) && + // (!pj_stricmp(t2, &ID_RTP_AVP) || !pj_stricmp(t2, &ID_RTP_SAVP))) + // return PJ_SUCCESS; + //} + + return PJMEDIA_SDP_ETPORTNOTEQUAL; +} + +/* + * Get media transport info, e.g: protocol and profile. + */ +PJ_DEF(pj_uint32_t) pjmedia_sdp_transport_get_proto(const pj_str_t *tp) +{ + pj_str_t token, rest = {0}; + pj_ssize_t idx; + + PJ_ASSERT_RETURN(tp, PJMEDIA_TP_PROTO_NONE); + + idx = pj_strtok2(tp, "/", &token, 0); + if (idx != tp->slen) + pj_strset(&rest, tp->ptr + token.slen + 1, tp->slen - token.slen - 1); + + if (pj_stricmp2(&token, "RTP") == 0) { + /* Starts with "RTP" */ + + /* RTP/AVP */ + if (pj_stricmp2(&rest, "AVP") == 0) + return PJMEDIA_TP_PROTO_RTP_AVP; + + /* RTP/SAVP */ + if (pj_stricmp2(&rest, "SAVP") == 0) + return PJMEDIA_TP_PROTO_RTP_SAVP; + + /* RTP/AVPF */ + if (pj_stricmp2(&rest, "AVPF") == 0) + return PJMEDIA_TP_PROTO_RTP_AVPF; + + /* RTP/SAVPF */ + if (pj_stricmp2(&rest, "SAVPF") == 0) + return PJMEDIA_TP_PROTO_RTP_SAVPF; + + } else if (pj_stricmp2(&token, "UDP") == 0) { + /* Starts with "UDP" */ + + /* Plain UDP */ + if (rest.slen == 0) + return PJMEDIA_TP_PROTO_UDP; + + /* DTLS-SRTP */ + if (pj_stricmp2(&rest, "TLS/RTP/SAVP") == 0) + return PJMEDIA_TP_PROTO_DTLS_SRTP; + + /* DTLS-SRTP with RTCP-FB */ + if (pj_stricmp2(&rest, "TLS/RTP/SAVPF") == 0) + return PJMEDIA_TP_PROTO_DTLS_SRTPF; + } + + /* Unknown transport */ + return PJMEDIA_TP_PROTO_UNKNOWN; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool, pjmedia_sdp_media *m) +{ + PJ_ASSERT_RETURN(m, PJ_EINVAL); + PJ_UNUSED_ARG(pool); + + /* Set port to zero */ + m->desc.port = 0; + + /* And remove attributes */ + m->attr_count = 0; + + return PJ_SUCCESS; +} + +PJ_DEF(pjmedia_sdp_media *) pjmedia_sdp_media_clone_deactivate(pj_pool_t *pool, const pjmedia_sdp_media *rhs) +{ + unsigned int i; + pjmedia_sdp_media *m; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + pj_memcpy(m, rhs, sizeof(*m)); + + /* Clone the media line only */ + pj_strdup(pool, &m->desc.media, &rhs->desc.media); + pj_strdup(pool, &m->desc.transport, &rhs->desc.transport); + for (i = 0; i < rhs->desc.fmt_count; ++i) + pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]); + + if (rhs->conn) { + m->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(m->conn != NULL, NULL); + } + + m->bandw_count = rhs->bandw_count; + for (i = 0; i < rhs->bandw_count; ++i) { + m->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]); + PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL); + } + + /* And deactivate it */ + pjmedia_sdp_media_deactivate(pool, m); + + return m; +} diff --git a/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c new file mode 100755 index 000000000..b33c8639e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* Compare connection line. */ +static pj_status_t compare_conn(const pjmedia_sdp_conn *c1, const pjmedia_sdp_conn *c2) +{ + /* Compare network type. */ + if (pj_strcmp(&c1->net_type, &c2->net_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address type. */ + if (pj_strcmp(&c1->addr_type, &c2->addr_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address. */ + if (pj_strcmp(&c1->addr, &c2->addr) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + return PJ_SUCCESS; +} + +/* Compare attributes array. */ +static pj_status_t compare_attr_imp(unsigned count1, pjmedia_sdp_attr *const attr1[], unsigned count2, + pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + unsigned i; + const pj_str_t inactive = {"inactive", 8}; + const pj_str_t sendrecv = {"sendrecv", 8}; + const pj_str_t sendonly = {"sendonly", 8}; + const pj_str_t recvonly = {"recvonly", 8}; + const pj_str_t fmtp = {"fmtp", 4}; + const pj_str_t rtpmap = {"rtpmap", 6}; + + /* For simplicity, we only compare the following attributes, and ignore + * the others: + * - direction, eg. inactive, sendonly, recvonly, sendrecv + * - fmtp for each payload. + * - rtpmap for each payload. + */ + for (i = 0; i < count1; ++i) { + const pjmedia_sdp_attr *a1 = attr1[i]; + + if (pj_strcmp(&a1->name, &inactive) == 0 || pj_strcmp(&a1->name, &sendrecv) == 0 || + pj_strcmp(&a1->name, &sendonly) == 0 || pj_strcmp(&a1->name, &recvonly) == 0) { + /* For inactive, sendrecv, sendonly, and recvonly attributes, + * the same attribute must be present on the other SDP. + */ + const pjmedia_sdp_attr *a2; + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL); + if (!a2) + return PJMEDIA_SDP_EDIRNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &fmtp) == 0) { + /* For fmtp attribute, find the fmtp attribute in the other SDP + * for the same payload type, and compare the fmtp param/value. + */ + pjmedia_sdp_fmtp fmtp1, fmtp2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt); + if (!a2) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &rtpmap) == 0) { + /* For rtpmap attribute, find rtpmap attribute on the other SDP + * for the same payload type, and compare both rtpmap atribute + * values. + */ + pjmedia_sdp_rtpmap r1, r2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_rtpmap(a1, &r1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt); + if (!a2) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + status = pjmedia_sdp_attr_get_rtpmap(a2, &r2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + if (pj_strcmp(&r1.pt, &r2.pt) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (r1.clock_rate != r2.clock_rate) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.param, &r2.param) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + } + } + + return PJ_SUCCESS; +} + +/* Compare attributes array. */ +static pj_status_t compare_attr(unsigned count1, pjmedia_sdp_attr *const attr1[], unsigned count2, + pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + + status = compare_attr_imp(count1, attr1, count2, attr2); + if (status != PJ_SUCCESS) + return status; + + status = compare_attr_imp(count2, attr2, count1, attr1); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +/* Compare media descriptor */ +PJ_DEF(pj_status_t) pjmedia_sdp_media_cmp(const pjmedia_sdp_media *sd1, const pjmedia_sdp_media *sd2, unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option == 0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare media type. */ + if (pj_strcmp(&sd1->desc.media, &sd2->desc.media) != 0) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + /* Compare port number. */ + if (sd1->desc.port != sd2->desc.port) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare port count. */ + if (sd1->desc.port_count != sd2->desc.port_count) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare transports. */ + if (pj_strcmp(&sd1->desc.transport, &sd2->desc.transport) != 0) + return PJMEDIA_SDP_ETPORTNOTEQUAL; + + /* For zeroed port media, stop comparing here */ + if (sd1->desc.port == 0) + return PJ_SUCCESS; + + /* Compare number of formats. */ + if (sd1->desc.fmt_count != sd2->desc.fmt_count) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + + /* Compare formats, in order. */ + for (i = 0; i < sd1->desc.fmt_count; ++i) { + if (pj_strcmp(&sd1->desc.fmt[i], &sd2->desc.fmt[i]) != 0) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Compare connection line, if they exist. */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + } else { + if (sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + } + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Looks equal */ + return PJ_SUCCESS; +} + +/* + * Compare two SDP session for equality. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_session_cmp(const pjmedia_sdp_session *sd1, const pjmedia_sdp_session *sd2, unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option == 0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare the origin line. */ + if (pj_strcmp(&sd1->origin.user, &sd2->origin.user) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.id != sd2->origin.id) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.version != sd2->origin.version) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.net_type, &sd2->origin.net_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr_type, &sd2->origin.addr_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr, &sd2->origin.addr) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + /* Compare the subject line. */ + if (pj_strcmp(&sd1->name, &sd2->name) != 0) + return PJMEDIA_SDP_ENAMENOTEQUAL; + + /* Compare connection line, when they exist */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + if (status != PJ_SUCCESS) + return status; + } else { + if (sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + } + + /* Compare time line. */ + if (sd1->time.start != sd2->time.start) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + if (sd1->time.stop != sd2->time.stop) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Compare media lines. */ + if (sd1->media_count != sd2->media_count) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + for (i = 0; i < sd1->media_count; ++i) { + status = pjmedia_sdp_media_cmp(sd1->media[i], sd2->media[i], 0); + if (status != PJ_SUCCESS) + return status; + } + + /* Looks equal. */ + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_conn_cmp(const pjmedia_sdp_conn *conn1, const pjmedia_sdp_conn *conn2, unsigned option) +{ + PJ_UNUSED_ARG(option); + return compare_conn(conn1, conn2); +} diff --git a/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c new file mode 100755 index 000000000..f65d0eb9d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c @@ -0,0 +1,1600 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * This structure describes SDP media negotiator. + */ +struct pjmedia_sdp_neg { + pjmedia_sdp_neg_state state; /**< Negotiator state. */ + pj_bool_t prefer_remote_codec_order; + pj_bool_t answer_with_multiple_codecs; + pj_bool_t has_remote_answer; + pj_bool_t answer_was_remote; + + pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */ + *initial_sdp_tmp, /**< Temporary initial local SDP */ + *active_local_sdp, /**< Currently active local SDP. */ + *active_remote_sdp, /**< Currently active remote's. */ + *neg_local_sdp, /**< Temporary local SDP. */ + *neg_remote_sdp; /**< Temporary remote SDP. */ +}; + +static const char *state_str[] = { + "STATE_NULL", "STATE_LOCAL_OFFER", "STATE_REMOTE_OFFER", "STATE_WAIT_NEGO", "STATE_DONE", +}; + +/* Definition of customized SDP format negotiation callback */ +struct fmt_match_cb_t { + pj_str_t fmt_name; + pjmedia_sdp_neg_fmt_match_cb cb; +}; + +/* Number of registered customized SDP format negotiation callbacks */ +static unsigned fmt_match_cb_cnt; + +/* The registered customized SDP format negotiation callbacks */ +static struct fmt_match_cb_t fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB]; + +/* Redefining a very long identifier name, just for convenience */ +#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER + +static pj_status_t custom_fmt_match(pj_pool_t *pool, const pj_str_t *fmt_name, pjmedia_sdp_media *offer, + unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option); + +/* + * Get string representation of negotiator state. + */ +PJ_DEF(const char *) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state) +{ + if ((int)state >= 0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str)) + return state_str[state]; + + return ""; +} + +/* + * Create with local offer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer(pj_pool_t *pool, const pjmedia_sdp_session *local, pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate local offer. */ + PJ_ASSERT_RETURN((status = pjmedia_sdp_validate(local)) == PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; + neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Create with remote offer and initial local offer/answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *remote, pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate remote offer and initial answer */ + status = pjmedia_sdp_validate2(remote, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + /* Create and initialize negotiator. */ + neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; + neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + if (initial) { + PJ_ASSERT_RETURN((status = pjmedia_sdp_validate(initial)) == PJ_SUCCESS, status); + + neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial); + + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + + } else { + + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + } + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Set codec order preference. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order(pjmedia_sdp_neg *neg, pj_bool_t prefer_remote) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + neg->prefer_remote_codec_order = prefer_remote; + return PJ_SUCCESS; +} + +/* + * Set multiple codec answering. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_answer_multiple_codecs(pjmedia_sdp_neg *neg, pj_bool_t answer_multiple) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + neg->answer_with_multiple_codecs = answer_multiple; + return PJ_SUCCESS; +} + +/* + * Get SDP negotiator state. + */ +PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state(pjmedia_sdp_neg *neg) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL); + return neg->state; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *local = neg->active_local_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *remote = neg->active_remote_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_FALSE); + + return neg->answer_was_remote; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG); + + *remote = neg->neg_remote_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG); + + *local = neg->neg_local_sdp; + return PJ_SUCCESS; +} + +static pjmedia_sdp_media *sdp_media_clone_deactivate(pj_pool_t *pool, const pjmedia_sdp_media *rem_med, + const pjmedia_sdp_media *local_med, + const pjmedia_sdp_session *local_sess) +{ + pjmedia_sdp_media *res; + + res = pjmedia_sdp_media_clone_deactivate(pool, rem_med); + if (!res) + return NULL; + + if (!res->conn && (!local_sess || !local_sess->conn)) { + if (local_med && local_med->conn) + res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn); + else { + res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + res->conn->net_type = pj_str("IN"); + res->conn->addr_type = pj_str("IP4"); + res->conn->addr = pj_str("127.0.0.1"); + } + } + + return res; +} + +/* + * Modify local SDP and wait for remote answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_modify_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local) +{ + return pjmedia_sdp_neg_modify_local_offer2(pool, neg, 0, local); +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_modify_local_offer2(pj_pool_t *pool, pjmedia_sdp_neg *neg, unsigned flags, + const pjmedia_sdp_session *local) +{ + pjmedia_sdp_session *new_offer; + pjmedia_sdp_session *old_offer; + unsigned oi; /* old offer media index */ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_DONE. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, PJMEDIA_SDPNEG_EINSTATE); + + /* Validate the new offer */ + status = pjmedia_sdp_validate(local); + if (status != PJ_SUCCESS) + return status; + + /* Change state to STATE_LOCAL_OFFER */ + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + + /* When there is no active local SDP in state PJMEDIA_SDP_NEG_STATE_DONE, + * it means that the previous initial SDP nego must have been failed, + * so we'll just set the local SDP offer here. + */ + if (!neg->active_local_sdp) { + neg->initial_sdp_tmp = NULL; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + return PJ_SUCCESS; + } + + /* Init vars */ + old_offer = neg->active_local_sdp; + new_offer = pjmedia_sdp_session_clone(pool, local); + + /* RFC 3264 Section 8: When issuing an offer that modifies the session, + * the "o=" line of the new SDP MUST be identical to that in the + * previous SDP, except that the version in the origin field MUST + * increment by one from the previous SDP. + */ + pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user); + new_offer->origin.id = old_offer->origin.id; + + pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type); + pj_strdup(pool, &new_offer->origin.addr_type, &old_offer->origin.addr_type); + pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr); + + if ((flags & PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE) == 0) { + /* Generating the new offer, in the case media lines doesn't match the + * active SDP (e.g. current/active SDP's have m=audio and m=video lines, + * and the new offer only has m=audio line), the negotiator will fix + * the new offer by reordering and adding the missing media line with + * port number set to zero. + */ + for (oi = 0; oi < old_offer->media_count; ++oi) { + pjmedia_sdp_media *om; + pjmedia_sdp_media *nm; + unsigned ni; /* new offer media index */ + pj_bool_t found = PJ_FALSE; + + om = old_offer->media[oi]; + for (ni = oi; ni < new_offer->media_count; ++ni) { + nm = new_offer->media[ni]; + if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) { + if (ni != oi) { + /* The same media found but the position unmatched to + * the old offer, so let's put this media in the right + * place, and keep the order of the rest. + */ + pj_array_insert(new_offer->media, /* array */ + sizeof(new_offer->media[0]), /* elmt size*/ + ni, /* count */ + oi, /* pos */ + &nm); /* new elmt */ + } + found = PJ_TRUE; + break; + } + } + if (!found) { + pjmedia_sdp_media *m; + + m = sdp_media_clone_deactivate(pool, om, om, local); + + pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); + } + } + } else { + /* If media type change is allowed, the negotiator only needs to fix + * the new offer by adding the missing media line(s) with port number + * set to zero. + */ + for (oi = new_offer->media_count; oi < old_offer->media_count; ++oi) { + pjmedia_sdp_media *m; + + m = sdp_media_clone_deactivate(pool, old_offer->media[oi], old_offer->media[oi], local); + + pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); + } + } + + /* New_offer fixed */ +#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION + new_offer->origin.version = old_offer->origin.version; + + if (pjmedia_sdp_session_cmp(new_offer, neg->initial_sdp, 0) != PJ_SUCCESS) { + ++new_offer->origin.version; + } +#else + new_offer->origin.version = old_offer->origin.version + 1; +#endif + + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = new_offer; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_send_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session **offer) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL); + + *offer = NULL; + + /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE || neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) { + /* If in STATE_DONE, set the active SDP as the offer. */ + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + /* Retain initial SDP */ + if (neg->initial_sdp) { + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + } + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->active_local_sdp); + +#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION + if (pjmedia_sdp_session_cmp(neg->neg_local_sdp, neg->initial_sdp, 0) != PJ_SUCCESS) { + neg->neg_local_sdp->origin.version++; + } +#else + neg->neg_local_sdp->origin.version++; +#endif + + *offer = neg->neg_local_sdp; + + } else { + /* We assume that we're in STATE_LOCAL_OFFER. + * In this case set the neg_local_sdp as the offer. + */ + *offer = neg->neg_local_sdp; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_remote_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_LOCAL_OFFER. + * If we haven't provided local offer, then rx_remote_offer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->has_remote_answer = PJ_TRUE; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_remote_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_DONE. + * If we already provide local offer, then rx_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_REMOTE_OFFER. */ + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_local_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_REMOTE_OFFER or WAIT_NEGO. + * If we already provide local offer, then set_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER || neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_WAIT_NEGO. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + if (local) { + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + if (neg->initial_sdp) { + /* Retain initial_sdp value. */ + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + + /* I don't think there is anything in RFC 3264 that mandates + * answerer to place the same origin (and increment version) + * in the answer, but probably it won't hurt either. + * Note that the version will be incremented in + * pjmedia_sdp_neg_negotiate() + */ + neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id; + } else { + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + } + } else { + PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL); + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg) +{ + pj_assert(neg && neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO); + return !neg->has_remote_answer; +} + +/* Swap string. */ +static void str_swap(pj_str_t *str1, pj_str_t *str2) +{ + pj_str_t tmp = *str1; + *str1 = *str2; + *str2 = tmp; +} + +static void remove_all_media_directions(pjmedia_sdp_media *m) +{ + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); +} + +/* Update media direction based on peer's media direction */ +static void update_media_direction(pj_pool_t *pool, const pjmedia_sdp_media *remote, pjmedia_sdp_media *local) +{ + pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING, new_dir; + + /* Get the media direction of local SDP */ + if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL)) + old_dir = PJMEDIA_DIR_ENCODING; + else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL)) + old_dir = PJMEDIA_DIR_DECODING; + else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL)) + old_dir = PJMEDIA_DIR_NONE; + + new_dir = old_dir; + + /* Adjust local media direction based on remote media direction */ + if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { + /* If remote has "a=inactive", then local is inactive too */ + + new_dir = PJMEDIA_DIR_NONE; + + } else if (pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { + /* If remote has "a=sendonly", then set local to "recvonly" if + * it is currently "sendrecv". Otherwise if local is NOT "recvonly", + * then set local direction to "inactive". + */ + switch (old_dir) { + case PJMEDIA_DIR_ENCODING_DECODING: + new_dir = PJMEDIA_DIR_DECODING; + break; + case PJMEDIA_DIR_DECODING: + /* No change */ + break; + default: + new_dir = PJMEDIA_DIR_NONE; + break; + } + + } else if (pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { + /* If remote has "a=recvonly", then set local to "sendonly" if + * it is currently "sendrecv". Otherwise if local is NOT "sendonly", + * then set local direction to "inactive" + */ + + switch (old_dir) { + case PJMEDIA_DIR_ENCODING_DECODING: + new_dir = PJMEDIA_DIR_ENCODING; + break; + case PJMEDIA_DIR_ENCODING: + /* No change */ + break; + default: + new_dir = PJMEDIA_DIR_NONE; + break; + } + + } else { + /* Remote indicates "sendrecv" capability. No change to local + * direction + */ + } + + if (new_dir != old_dir) { + pjmedia_sdp_attr *a = NULL; + + remove_all_media_directions(local); + + switch (new_dir) { + case PJMEDIA_DIR_NONE: + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + break; + case PJMEDIA_DIR_ENCODING: + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + break; + case PJMEDIA_DIR_DECODING: + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + break; + default: + /* sendrecv */ + break; + } + + if (a) { + pjmedia_sdp_media_add_attr(local, a); + } + } +} + +/* Update single local media description to after receiving answer + * from remote. + */ +static pj_status_t process_m_answer(pj_pool_t *pool, pjmedia_sdp_media *offer, pjmedia_sdp_media *answer, + pj_bool_t allow_asym) +{ + unsigned i; + + /* Check that the media type match our offer. */ + + if (pj_strcmp(&answer->desc.media, &offer->desc.media) != 0) { + /* The media type in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSMEDIA; + } + + /* Check if remote has rejected our offer */ + if (answer->desc.port == 0) { + + /* Remote has rejected our offer. + * Deactivate our media too. + */ + pjmedia_sdp_media_deactivate(pool, offer); + + /* Don't need to proceed */ + return PJ_SUCCESS; + } + + /* Check that transport in the answer match our offer. */ + + /* At this point, transport type must be compatible, + * the transport instance will do more validation later. + */ + if (pjmedia_sdp_transport_cmp(&answer->desc.transport, &offer->desc.transport) != PJ_SUCCESS) { + return PJMEDIA_SDPNEG_EINVANSTP; + } + + /* Ticket #1148: check if remote answer does not set port to zero when + * offered with port zero. Let's just tolerate it. + */ + if (offer->desc.port == 0) { + /* Don't need to proceed */ + return PJ_SUCCESS; + } + + /* Process direction attributes */ + update_media_direction(pool, answer, offer); + + /* If asymetric media is allowed, then just check that remote answer has + * codecs that are within the offer. + * + * Otherwise if asymetric media is not allowed, then we will choose only + * one codec in our initial offer to match the answer. + */ + if (allow_asym) { + for (i = 0; i < answer->desc.fmt_count; ++i) { + unsigned j; + pj_str_t *rem_fmt = &answer->desc.fmt[i]; + + for (j = 0; j < offer->desc.fmt_count; ++j) { + if (pj_strcmp(rem_fmt, &answer->desc.fmt[j]) == 0) + break; + } + + if (j != offer->desc.fmt_count) { + /* Found at least one common codec. */ + break; + } + } + + if (i == answer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED); + + } else { + /* Offer format priority based on answer format index/priority */ + unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT]; + + /* Remove all format in the offer that has no matching answer */ + for (i = 0; i < offer->desc.fmt_count;) { + unsigned pt; + pj_uint32_t j; + pj_str_t *fmt = &offer->desc.fmt[i]; + + /* Find matching answer */ + pt = pj_strtoul(fmt); + + if (pt < 96) { + for (j = 0; j < answer->desc.fmt_count; ++j) { + if (pj_strcmp(fmt, &answer->desc.fmt[j]) == 0) + break; + } + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or_; + + /* Get the rtpmap for the payload type in the offer. */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(a, &or_); + + /* Find paylaod in answer SDP with matching + * encoding name and clock rate. + */ + for (j = 0; j < answer->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap ar; + pjmedia_sdp_attr_get_rtpmap(a, &ar); + + /* See if encoding name, clock rate, and channel + * count match + */ + if (!pj_stricmp(&or_.enc_name, &ar.enc_name) && or_.clock_rate == ar.clock_rate && + (pj_stricmp(&or_.param, &ar.param) == 0 || (ar.param.slen == 1 && *ar.param.ptr == '1'))) { + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, offer, i, answer, j, 0) == PJ_SUCCESS) { + /* Match! */ + break; + } + } + } + } + } + + if (j == answer->desc.fmt_count) { + /* This format has no matching answer. + * Remove it from our offer. + */ + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove this format from offer's array */ + pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]), offer->desc.fmt_count, i); + --offer->desc.fmt_count; + + } else { + offer_fmt_prior[i] = j; + ++i; + } + } + + if (0 == offer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + /* Post process: + * - Resort offer formats so the order match to the answer. + * - Remove answer formats that unmatches to the offer. + */ + + /* Resort offer formats */ + for (i = 0; i < offer->desc.fmt_count; ++i) { + unsigned j; + for (j = i + 1; j < offer->desc.fmt_count; ++j) { + if (offer_fmt_prior[i] > offer_fmt_prior[j]) { + unsigned tmp = offer_fmt_prior[i]; + offer_fmt_prior[i] = offer_fmt_prior[j]; + offer_fmt_prior[j] = tmp; + str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]); + } + } + } + + /* Remove unmatched answer formats */ + { + unsigned del_cnt = 0; + for (i = 0; i < answer->desc.fmt_count;) { + /* The offer is ordered now, also the offer_fmt_prior */ + if (i >= offer->desc.fmt_count || offer_fmt_prior[i] - del_cnt != i) { + pj_str_t *fmt = &answer->desc.fmt[i]; + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(answer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(answer, a); + + /* Remove this format from answer's array */ + pj_array_erase(answer->desc.fmt, sizeof(answer->desc.fmt[0]), answer->desc.fmt_count, i); + --answer->desc.fmt_count; + + ++del_cnt; + } else { + ++i; + } + } + } + } + + /* Looks okay */ + return PJ_SUCCESS; +} + +/* Update local media session (offer) to create active local session + * after receiving remote answer. + */ +static pj_status_t process_answer(pj_pool_t *pool, pjmedia_sdp_session *local_offer, pjmedia_sdp_session *answer, + pj_bool_t allow_asym, pjmedia_sdp_session **p_active) +{ + unsigned omi = 0; /* Offer media index */ + unsigned ami = 0; /* Answer media index */ + pj_bool_t has_active = PJ_FALSE; + pjmedia_sdp_session *offer; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && local_offer && answer && p_active, PJ_EINVAL); + + /* Duplicate local offer SDP. */ + offer = pjmedia_sdp_session_clone(pool, local_offer); + + /* Check that media count match between offer and answer */ + // Ticket #527, different media count is allowed for more interoperability, + // however, the media order must be same between offer and answer. + // if (offer->media_count != answer->media_count) + // return PJMEDIA_SDPNEG_EMISMEDIA; + + /* Now update each media line in the offer with the answer. */ + for (; omi < offer->media_count; ++omi) { + if (ami == answer->media_count) { + /* The answer has less media than the offer */ + pjmedia_sdp_media *am; + + /* Generate matching-but-disabled-media for the answer */ + am = sdp_media_clone_deactivate(pool, offer->media[omi], offer->media[omi], offer); + answer->media[answer->media_count++] = am; + ++ami; + + /* Deactivate our media offer too */ + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + + /* No answer media to be negotiated */ + continue; + } + + status = process_m_answer(pool, offer->media[omi], answer->media[ami], allow_asym); + + /* If media type is mismatched, just disable the media. */ + if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) { + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + continue; + } + /* No common format in the answer media. */ + else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) { + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + pjmedia_sdp_media_deactivate(pool, answer->media[ami]); + } + /* Return the error code, for other errors. */ + else if (status != PJ_SUCCESS) { + return status; + } + + if (offer->media[omi]->desc.port != 0) + has_active = PJ_TRUE; + + ++ami; + } + + *p_active = offer; + + return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA; +} + +/* Internal function to rewrite the format string in SDP attribute rtpmap + * and fmtp. + */ +PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val, const pj_str_t *old_pt, const pj_str_t *new_pt) +{ + int len_diff = (int)(new_pt->slen - old_pt->slen); + + /* Note that attribute value should be null-terminated. */ + if (len_diff > 0) { + pj_str_t new_val; + new_val.ptr = (char *)pj_pool_alloc(pool, attr_val->slen + len_diff + 1); + new_val.slen = attr_val->slen + len_diff; + pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1); + *attr_val = new_val; + } else if (len_diff < 0) { + attr_val->slen += len_diff; + pj_memmove(attr_val->ptr, attr_val->ptr - len_diff, attr_val->slen + 1); + } + pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen); +} + +/* Internal function to apply symmetric PT for the local answer. */ +static void apply_answer_symmetric_pt(pj_pool_t *pool, pjmedia_sdp_media *answer, unsigned pt_cnt, + const pj_str_t pt_offer[], const pj_str_t pt_answer[]) +{ + pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR]; + unsigned i, a_tmp_cnt = 0; + + /* Rewrite the payload types in the answer if different to + * the ones in the offer. + */ + for (i = 0; i < pt_cnt; ++i) { + pjmedia_sdp_attr *a; + + /* Skip if the PTs are the same already, e.g: static PT. */ + if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0) + continue; + + /* Rewrite payload type in the answer to match to the offer */ + pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]); + + /* Also update payload type in rtpmap */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]); + if (a) { + rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); + /* Temporarily remove the attribute in case the new payload + * type is being used by another format in the media. + */ + pjmedia_sdp_media_remove_attr(answer, a); + a_tmp[a_tmp_cnt++] = a; + } + + /* Also update payload type in fmtp */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]); + if (a) { + rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); + /* Temporarily remove the attribute in case the new payload + * type is being used by another format in the media. + */ + pjmedia_sdp_media_remove_attr(answer, a); + a_tmp[a_tmp_cnt++] = a; + } + } + + /* Return back 'rtpmap' and 'fmtp' attributes */ + for (i = 0; i < a_tmp_cnt; ++i) + pjmedia_sdp_media_add_attr(answer, a_tmp[i]); +} + +/* Try to match offer with answer. */ +static pj_status_t match_offer(pj_pool_t *pool, pj_bool_t prefer_remote_codec_order, + pj_bool_t answer_with_multiple_codecs, const pjmedia_sdp_media *offer, + const pjmedia_sdp_media *preanswer, const pjmedia_sdp_session *preanswer_sdp, + pjmedia_sdp_media **p_answer) +{ + unsigned i; + pj_bool_t master_has_codec = 0, master_has_other = 0, found_matching_codec = 0, found_matching_telephone_event = 0, + found_matching_other = 0; + unsigned pt_answer_count = 0; + pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT]; + pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT]; + pjmedia_sdp_media *answer; + const pjmedia_sdp_media *master, *slave; + unsigned nclockrate = 0, clockrate[PJMEDIA_MAX_SDP_FMT]; + unsigned ntel_clockrate = 0, tel_clockrate[PJMEDIA_MAX_SDP_FMT]; + + /* If offer has zero port, just clone the offer */ + if (offer->desc.port == 0) { + answer = sdp_media_clone_deactivate(pool, offer, preanswer, preanswer_sdp); + *p_answer = answer; + return PJ_SUCCESS; + } + + /* If the preanswer define zero port, this media is being rejected, + * just clone the preanswer. + */ + if (preanswer->desc.port == 0) { + answer = pjmedia_sdp_media_clone(pool, preanswer); + *p_answer = answer; + return PJ_SUCCESS; + } + + /* Set master/slave negotiator based on prefer_remote_codec_order. */ + if (prefer_remote_codec_order) { + master = offer; + slave = preanswer; + } else { + master = preanswer; + slave = offer; + } + + /* With the addition of telephone-event and dodgy MS RTC SDP, + * the answer generation algorithm looks really shitty... + */ + for (i = 0; i < master->desc.fmt_count; ++i) { + unsigned j; + + if (pj_isdigit(*master->desc.fmt[i].ptr)) { + /* This is normal/standard payload type, where it's identified + * by payload number. + */ + unsigned pt; + + pt = pj_strtoul(&master->desc.fmt[i]); + + if (pt < 96) { + /* For static payload type, it's enough to compare just + * the payload number. + */ + + master_has_codec = 1; + + /* We just need to select one codec if not allowing multiple. + * Continue if we have selected matching codec for previous + * payload. + */ + if (!answer_with_multiple_codecs && found_matching_codec) + continue; + + /* Find matching codec in local descriptor. */ + for (j = 0; j < slave->desc.fmt_count; ++j) { + unsigned p; + p = pj_strtoul(&slave->desc.fmt[j]); + if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) { + unsigned k; + + found_matching_codec = 1; + pt_offer[pt_answer_count] = slave->desc.fmt[j]; + pt_answer[pt_answer_count++] = slave->desc.fmt[j]; + + /* Take note of clock rate for tel-event. Note: for + * static PT, we assume the clock rate is 8000. + */ + for (k = 0; k < nclockrate; ++k) + if (clockrate[k] == 8000) + break; + if (k == nclockrate) + clockrate[nclockrate++] = 8000; + break; + } + } + + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or_; + pj_bool_t is_codec = 0; + + /* Get the rtpmap for the payload type in the master. */ + a = pjmedia_sdp_media_find_attr2(master, "rtpmap", &master->desc.fmt[i]); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJMEDIA_SDP_EMISSINGRTPMAP; + } + pjmedia_sdp_attr_get_rtpmap(a, &or_); + + if (pj_stricmp2(&or_.enc_name, "telephone-event")) { + master_has_codec = 1; + if (!answer_with_multiple_codecs && found_matching_codec) + continue; + is_codec = 1; + } + + /* Find paylaod in our initial SDP with matching + * encoding name and clock rate. + */ + for (j = 0; j < slave->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(slave, "rtpmap", &slave->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap lr; + pjmedia_sdp_attr_get_rtpmap(a, &lr); + + /* See if encoding name, clock rate, and + * channel count match + */ + if (!pj_stricmp(&or_.enc_name, &lr.enc_name) && or_.clock_rate == lr.clock_rate && + (pj_stricmp(&or_.param, &lr.param) == 0 || + (lr.param.slen == 0 && or_.param.slen == 1 && *or_.param.ptr == '1') || + (or_.param.slen == 0 && lr.param.slen == 1 && *lr.param.ptr == '1'))) { + /* Match! */ + if (is_codec) { + pjmedia_sdp_media *o_med, *a_med; + unsigned o_fmt_idx, a_fmt_idx; + unsigned k; + + o_med = (pjmedia_sdp_media *)offer; + a_med = (pjmedia_sdp_media *)preanswer; + o_fmt_idx = prefer_remote_codec_order ? i : j; + a_fmt_idx = prefer_remote_codec_order ? j : i; + + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, o_med, o_fmt_idx, a_med, a_fmt_idx, + ALLOW_MODIFY_ANSWER) != PJ_SUCCESS) { + continue; + } + found_matching_codec = 1; + + /* Take note of clock rate for tel-event */ + for (k = 0; k < nclockrate; ++k) + if (clockrate[k] == or_.clock_rate) + break; + if (k == nclockrate) + clockrate[nclockrate++] = or_.clock_rate; + } else { + unsigned k; + + /* Keep track of tel-event clock rate, + * to prevent duplicate. + */ + for (k = 0; k < ntel_clockrate; ++k) + if (tel_clockrate[k] == or_.clock_rate) + break; + if (k < ntel_clockrate) + continue; + + tel_clockrate[ntel_clockrate++] = or_.clock_rate; + found_matching_telephone_event = 1; + } + + pt_offer[pt_answer_count] = + prefer_remote_codec_order ? offer->desc.fmt[i] : offer->desc.fmt[j]; + pt_answer[pt_answer_count++] = + prefer_remote_codec_order ? preanswer->desc.fmt[j] : preanswer->desc.fmt[i]; + break; + } + } + } + } + + } else { + /* This is a non-standard, brain damaged SDP where the payload + * type is non-numeric. It exists e.g. in Microsoft RTC based + * UA, to indicate instant messaging capability. + * Example: + * - m=x-ms-message 5060 sip null + */ + master_has_other = 1; + if (found_matching_other) + continue; + + for (j = 0; j < slave->desc.fmt_count; ++j) { + if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) { + /* Match */ + found_matching_other = 1; + pt_offer[pt_answer_count] = prefer_remote_codec_order ? offer->desc.fmt[i] : offer->desc.fmt[j]; + pt_answer[pt_answer_count++] = + prefer_remote_codec_order ? preanswer->desc.fmt[j] : preanswer->desc.fmt[i]; + break; + } + } + } + } + + /* See if all types of master can be matched. */ + if (master_has_codec && !found_matching_codec) { + return PJMEDIA_SDPNEG_NOANSCODEC; + } + + /* If this comment is removed, negotiation will fail if remote has offered + telephone-event and local is not configured with telephone-event + + if (offer_has_telephone_event && !found_matching_telephone_event) { + return PJMEDIA_SDPNEG_NOANSTELEVENT; + } + */ + + if (master_has_other && !found_matching_other) { + return PJMEDIA_SDPNEG_NOANSUNKNOWN; + } + + /* Seems like everything is in order. */ + + /* Remove unwanted telephone-event formats. */ + if (found_matching_telephone_event) { + pj_str_t first_televent_offer = {0}; + pj_str_t first_televent_answer = {0}; + unsigned matched_cnt = 0; + + for (i = 0; i < pt_answer_count;) { + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap r; + unsigned j; + + /* Skip static PT, as telephone-event uses dynamic PT */ + if (!pj_isdigit(*pt_answer[i].ptr) || pj_strtol(&pt_answer[i]) < 96) { + ++i; + continue; + } + + /* Get the rtpmap for format. */ + a = pjmedia_sdp_media_find_attr2(preanswer, "rtpmap", &pt_answer[i]); + pj_assert(a); + pjmedia_sdp_attr_get_rtpmap(a, &r); + + /* Only care for telephone-event format */ + if (pj_stricmp2(&r.enc_name, "telephone-event")) { + ++i; + continue; + } + + if (first_televent_offer.slen == 0) { + first_televent_offer = pt_offer[i]; + first_televent_answer = pt_answer[i]; + } + + for (j = 0; j < nclockrate; ++j) { + if (r.clock_rate == clockrate[j]) + break; + } + + /* This tel-event's clockrate is unwanted, remove the tel-event */ + if (j == nclockrate) { + pj_array_erase(pt_answer, sizeof(pt_answer[0]), pt_answer_count, i); + pj_array_erase(pt_offer, sizeof(pt_offer[0]), pt_answer_count, i); + pt_answer_count--; + } else { + ++matched_cnt; + ++i; + } + } + + /* Tel-event is wanted, but no matched clock rate (to the selected + * audio codec), just put back any first matched tel-event formats. + */ + if (!matched_cnt) { + pt_offer[pt_answer_count] = first_televent_offer; + pt_answer[pt_answer_count++] = first_televent_answer; + } + } + + /* Build the answer by cloning from preanswer, and reorder the payload + * to suit the offer. + */ + answer = pjmedia_sdp_media_clone(pool, preanswer); + for (i = 0; i < pt_answer_count; ++i) { + unsigned j; + for (j = i; j < answer->desc.fmt_count; ++j) { + if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i])) + break; + } + pj_assert(j != answer->desc.fmt_count); + str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]); + } + + /* Remove unwanted local formats. */ + for (i = pt_answer_count; i < answer->desc.fmt_count; ++i) { + pjmedia_sdp_attr *a; + + /* Remove rtpmap for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + + /* Remove fmtp for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + } + answer->desc.fmt_count = pt_answer_count; + +#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT + apply_answer_symmetric_pt(pool, answer, pt_answer_count, pt_offer, pt_answer); +#endif + + /* Update media direction. */ + update_media_direction(pool, offer, answer); + + *p_answer = answer; + return PJ_SUCCESS; +} + +/* Create complete answer for remote's offer. */ +static pj_status_t create_answer(pj_pool_t *pool, pj_bool_t prefer_remote_codec_order, + pj_bool_t answer_with_multiple_codecs, const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *offer, pjmedia_sdp_session **p_answer) +{ + pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA; + pj_bool_t has_active = PJ_FALSE; + pjmedia_sdp_session *answer; + char media_used[PJMEDIA_MAX_SDP_MEDIA]; + unsigned i; + + /* Validate remote offer. + * This should have been validated before. + */ + PJ_ASSERT_RETURN((status = pjmedia_sdp_validate(offer)) == PJ_SUCCESS, status); + + /* Create initial answer by duplicating initial SDP, + * but clear all media lines. The media lines will be filled up later. + */ + answer = pjmedia_sdp_session_clone(pool, initial); + PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM); + + answer->media_count = 0; + + pj_bzero(media_used, sizeof(media_used)); + + /* For each media line, create our answer based on our initial + * capability. + */ + for (i = 0; i < offer->media_count; ++i) { + const pjmedia_sdp_media *om; /* offer */ + const pjmedia_sdp_media *im; /* initial media */ + pjmedia_sdp_media *am = NULL; /* answer/result */ + unsigned j; + + om = offer->media[i]; + + /* Find media description in our initial capability that matches + * the media type and transport type of offer's media, has + * matching codec, and has not been used to answer other offer. + */ + for (im = NULL, j = 0; j < initial->media_count; ++j) { + im = initial->media[j]; + if (pj_strcmp(&om->desc.media, &im->desc.media) == 0 && + pj_strcmp(&om->desc.transport, &im->desc.transport) == 0 && media_used[j] == 0) { + pj_status_t status2; + + /* See if it has matching codec. */ + status2 = + match_offer(pool, prefer_remote_codec_order, answer_with_multiple_codecs, om, im, initial, &am); + if (status2 == PJ_SUCCESS) { + /* Mark media as used. */ + media_used[j] = 1; + break; + } else { + status = status2; + } + } + } + + if (j == initial->media_count) { + /* No matching media. + * Reject the offer by setting the port to zero in the answer. + */ + /* For simplicity in the construction of the answer, we'll + * just clone the media from the offer. Anyway receiver will + * ignore anything in the media once it sees that the port + * number is zero. + */ + am = sdp_media_clone_deactivate(pool, om, om, answer); + } else { + /* The answer is in am */ + pj_assert(am != NULL); + } + + /* Add the media answer */ + answer->media[answer->media_count++] = am; + + /* Check if this media is active.*/ + if (am->desc.port != 0) + has_active = PJ_TRUE; + } + + *p_answer = answer; + + return has_active ? PJ_SUCCESS : status; +} + +/* Cancel offer */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + + /* Must be in LOCAL_OFFER state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || + neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && neg->active_local_sdp) { + /* Increment next version number. This happens if for example + * the reinvite offer is rejected by 488. If we don't increment + * the version here, the next offer will have the same version. + */ + neg->active_local_sdp->origin.version++; + } + + /* Revert back initial SDP */ + if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + neg->initial_sdp = neg->initial_sdp_tmp; + + /* Clear temporary SDP */ + neg->initial_sdp_tmp = NULL; + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + /* Reset state to done */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + return PJ_SUCCESS; +} + +/* The best bit: SDP negotiation function! */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate(pj_pool_t *pool, pjmedia_sdp_neg *neg, pj_bool_t allow_asym) +{ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL); + + /* Must be in STATE_WAIT_NEGO state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, PJMEDIA_SDPNEG_EINSTATE); + + /* Must have remote offer. */ + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG); + + if (neg->has_remote_answer) { + pjmedia_sdp_session *active; + status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp, allow_asym, &active); + if (status == PJ_SUCCESS) { + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = active; + neg->active_remote_sdp = neg->neg_remote_sdp; + } + } else { + pjmedia_sdp_session *answer = NULL; + + status = create_answer(pool, neg->prefer_remote_codec_order, neg->answer_with_multiple_codecs, + neg->neg_local_sdp, neg->neg_remote_sdp, &answer); + if (status == PJ_SUCCESS) { + pj_uint32_t active_ver; + + if (neg->active_local_sdp) + active_ver = neg->active_local_sdp->origin.version; + else + active_ver = neg->initial_sdp->origin.version; + +#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION + answer->origin.version = active_ver; + + if ((neg->active_local_sdp == NULL) || + (pjmedia_sdp_session_cmp(answer, neg->active_local_sdp, 0) != PJ_SUCCESS)) { + ++answer->origin.version; + } +#else + answer->origin.version = active_ver + 1; +#endif + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = answer; + neg->active_remote_sdp = neg->neg_remote_sdp; + } + } + + /* State is DONE regardless */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Save state */ + neg->answer_was_remote = neg->has_remote_answer; + + /* Revert back initial SDP if nego fails */ + if (status != PJ_SUCCESS) + neg->initial_sdp = neg->initial_sdp_tmp; + + /* Clear temporary SDP */ + neg->initial_sdp_tmp = NULL; + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + return status; +} + +static pj_status_t custom_fmt_match(pj_pool_t *pool, const pj_str_t *fmt_name, pjmedia_sdp_media *offer, + unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option) +{ + unsigned i; + + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) { + pj_assert(fmt_match_cb[i].cb); + return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx, answer, a_fmt_idx, option); + } + } + + /* Not customized format matching found, should be matched */ + return PJ_SUCCESS; +} + +/* Register customized SDP format negotiation callback function. */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(const pj_str_t *fmt_name, pjmedia_sdp_neg_fmt_match_cb cb) +{ + struct fmt_match_cb_t *f = NULL; + unsigned i; + + PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL); + + /* Check if the callback for the format name has been registered */ + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) + break; + } + + /* Unregistration */ + + if (cb == NULL) { + if (i == fmt_match_cb_cnt) + return PJ_ENOTFOUND; + + pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]), fmt_match_cb_cnt, i); + fmt_match_cb_cnt--; + + return PJ_SUCCESS; + } + + /* Registration */ + + if (i < fmt_match_cb_cnt) { + /* The same format name has been registered before */ + if (cb != fmt_match_cb[i].cb) + return PJ_EEXISTS; + else + return PJ_SUCCESS; + } + + if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb)) + return PJ_ETOOMANY; + + f = &fmt_match_cb[fmt_match_cb_cnt++]; + f->fmt_name = *fmt_name; + f->cb = cb; + + return PJ_SUCCESS; +} + +/* Match format in the SDP media offer and answer. */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_fmt_match(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, + unsigned a_fmt_idx, unsigned option) +{ + const pjmedia_sdp_attr *attr; + pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap; + unsigned o_pt; + unsigned a_pt; + + o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]); + a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]); + + if (o_pt < 96 || a_pt < 96) { + if (o_pt == a_pt) + return PJ_SUCCESS; + else + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Get the format rtpmap from the offer. */ + attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap", &offer->desc.fmt[o_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Offer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap); + + /* Get the format rtpmap from the answer. */ + attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[a_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Answer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap); + + if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 || (o_rtpmap.clock_rate != a_rtpmap.clock_rate) || + (!(pj_stricmp(&o_rtpmap.param, &a_rtpmap.param) == 0 || + (a_rtpmap.param.slen == 0 && o_rtpmap.param.slen == 1 && *o_rtpmap.param.ptr == '1') || + (o_rtpmap.param.slen == 0 && a_rtpmap.param.slen == 1 && *a_rtpmap.param.ptr == '1')))) { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + return custom_fmt_match(pool, &o_rtpmap.enc_name, offer, o_fmt_idx, answer, a_fmt_idx, option); +} diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath.h new file mode 100755 index 000000000..387b381eb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJNATH_H__ +#define __PJNATH_H__ + +/** + * @file pjnath.h + * @brief PJNATH main header file. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* UPnP */ +#include + +#endif diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h new file mode 100755 index 000000000..800ecaf67 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_CONFIG_H__ +#define __PJNATH_CONFIG_H__ + +/** + * @file config.h + * @brief Compile time settings + */ + +#include + +/** + * @defgroup PJNATH_CONFIG Compile-time configurations + * @brief Various compile time settings + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/* ************************************************************************** + * GENERAL + */ + +/** + * The log level for PJNATH error display. + * + * default 1 + */ +#ifndef PJNATH_ERROR_LEVEL +#define PJNATH_ERROR_LEVEL 1 +#endif + +/* ************************************************************************** + * STUN CONFIGURATION + */ + +/** + * Maximum number of attributes in the STUN packet (for the new STUN + * library). + * + * Default: 16 + */ +#ifndef PJ_STUN_MAX_ATTR +#define PJ_STUN_MAX_ATTR 16 +#endif + +/** + * The default initial STUN round-trip time estimation (the RTO value + * in RFC 3489-bis), in miliseconds. + * This value is used to control the STUN request + * retransmit time. The initial value of retransmission interval + * would be set to this value, and will be doubled after each + * retransmission. + */ +#ifndef PJ_STUN_RTO_VALUE +#define PJ_STUN_RTO_VALUE 100 +#endif + +/** + * The STUN transaction timeout value, in miliseconds. + * After the last retransmission is sent and if no response is received + * after this time, the STUN transaction will be considered to have failed. + * + * The default value is 16x RTO (as per RFC 3489-bis). + */ +#ifndef PJ_STUN_TIMEOUT_VALUE +#define PJ_STUN_TIMEOUT_VALUE (16 * PJ_STUN_RTO_VALUE) +#endif + +/** + * Maximum number of STUN transmission count. + * + * Default: 7 (as per RFC 3489-bis) + */ +#ifndef PJ_STUN_MAX_TRANSMIT_COUNT +#define PJ_STUN_MAX_TRANSMIT_COUNT 7 +#endif + +/** + * Duration to keep response in the cache, in msec. + * + * Default: 10000 (as per RFC 3489-bis) + */ +#ifndef PJ_STUN_RES_CACHE_DURATION +#define PJ_STUN_RES_CACHE_DURATION 10000 +#endif + +/** + * Maximum size of STUN message. + */ +#ifndef PJ_STUN_MAX_PKT_LEN +#define PJ_STUN_MAX_PKT_LEN 800 +#endif + +/** + * Default STUN port as defined by RFC 3489. + */ +#define PJ_STUN_PORT 3478 + +/** + * Padding character for string attributes. + * + * Default: ASCII 0 + */ +#ifndef PJ_STUN_STRING_ATTR_PAD_CHR +#define PJ_STUN_STRING_ATTR_PAD_CHR 0 +#endif + +/** + * Enable pre-RFC3489bis-07 style of STUN MESSAGE-INTEGRITY and FINGERPRINT + * calculation. By default this should be disabled since the calculation is + * not backward compatible with current STUN specification. + */ +#ifndef PJ_STUN_OLD_STYLE_MI_FINGERPRINT +#define PJ_STUN_OLD_STYLE_MI_FINGERPRINT 0 +#endif + +/* ************************************************************************** + * STUN TRANSPORT CONFIGURATION + */ + +/** + * The packet buffer size for the STUN transport. + */ +#ifndef PJ_STUN_SOCK_PKT_LEN +#define PJ_STUN_SOCK_PKT_LEN 2000 +#endif + +/** + * The duration of the STUN keep-alive period, in seconds. + */ +#ifndef PJ_STUN_KEEP_ALIVE_SEC +#define PJ_STUN_KEEP_ALIVE_SEC 15 +#endif + +/* ************************************************************************** + * TURN CONFIGURATION + */ + +/** + * Maximum DNS SRV entries to be processed in the DNS SRV response + */ +#ifndef PJ_TURN_MAX_DNS_SRV_CNT +#define PJ_TURN_MAX_DNS_SRV_CNT 4 +#endif + +/** + * Maximum TURN packet size to be supported. + */ +#ifndef PJ_TURN_MAX_PKT_LEN +#define PJ_TURN_MAX_PKT_LEN 3000 +#endif + +/** + * The TURN permission lifetime setting. This value should be taken from the + * TURN protocol specification. + */ +#ifndef PJ_TURN_PERM_TIMEOUT +#define PJ_TURN_PERM_TIMEOUT 300 +#endif + +/** + * The TURN channel binding lifetime. This value should be taken from the + * TURN protocol specification. + */ +#ifndef PJ_TURN_CHANNEL_TIMEOUT +#define PJ_TURN_CHANNEL_TIMEOUT 600 +#endif + +/** + * Number of seconds to refresh the permission/channel binding before the + * permission/channel binding expires. This value should be greater than + * PJ_TURN_PERM_TIMEOUT setting. + */ +#ifndef PJ_TURN_REFRESH_SEC_BEFORE +#define PJ_TURN_REFRESH_SEC_BEFORE 60 +#endif + +/** + * The TURN session timer heart beat interval. When this timer occurs, the + * TURN session will scan all the permissions/channel bindings to see which + * need to be refreshed. + */ +#ifndef PJ_TURN_KEEP_ALIVE_SEC +#define PJ_TURN_KEEP_ALIVE_SEC 15 +#endif + +/** + * Maximum number of TCP data connection to peer(s) that a TURN client can + * open/accept for each TURN allocation (or TURN control connection). + */ +#ifndef PJ_TURN_MAX_TCP_CONN_CNT +#define PJ_TURN_MAX_TCP_CONN_CNT 8 +#endif + +/* ************************************************************************** + * ICE CONFIGURATION + */ + +/** + * Maximum number of ICE candidates. + * + * Default: 16 + */ +#ifndef PJ_ICE_MAX_CAND +#define PJ_ICE_MAX_CAND 16 +#endif + +/** + * Maximum number of candidates for each ICE stream transport component. + * + * Default: 8 + */ +#ifndef PJ_ICE_ST_MAX_CAND +#define PJ_ICE_ST_MAX_CAND 8 +#endif + +/** + * Maximum number of STUN transports for each ICE stream transport component. + * Valid values are 1 - 64. + * + * Default: 2 + */ +#ifndef PJ_ICE_MAX_STUN +#define PJ_ICE_MAX_STUN 2 +#endif + +/** + * Maximum number of TURN transports for each ICE stream transport component. + * Valid values are 1 - 64. + * + * Default: 2 + */ +#ifndef PJ_ICE_MAX_TURN +#define PJ_ICE_MAX_TURN 3 +#endif + +/** + * The number of bits to represent component IDs. This will affect + * the maximum number of components (PJ_ICE_MAX_COMP) value. + */ +#ifndef PJ_ICE_COMP_BITS +#define PJ_ICE_COMP_BITS 2 +#endif + +/** + * Maximum number of ICE components. + */ +#define PJ_ICE_MAX_COMP (1 << PJ_ICE_COMP_BITS) + +/** + * Use the priority value according to the ice-draft. + */ +#ifndef PJNATH_ICE_PRIO_STD +#define PJNATH_ICE_PRIO_STD 1 +#endif + +/** + * The number of bits to represent candidate type preference. + */ +#ifndef PJ_ICE_CAND_TYPE_PREF_BITS +#if PJNATH_ICE_PRIO_STD +#define PJ_ICE_CAND_TYPE_PREF_BITS 8 +#else +#define PJ_ICE_CAND_TYPE_PREF_BITS 2 +#endif +#endif + +/** + * The number of bits to represent ICE candidate's local preference. The + * local preference is used to specify preference among candidates with + * the same type, and ICE draft suggests 65535 as the default local + * preference, which means we need 16 bits to represent the value. But + * since we don't have the facility to specify local preference, we'll + * just disable this feature and let the preference sorted by the + * type only. + * + * Default: 0 + */ +#ifndef PJ_ICE_LOCAL_PREF_BITS +#define PJ_ICE_LOCAL_PREF_BITS 0 +#endif + +/** + * Maximum number of ICE checks. + * + * Default: 32 + */ +#ifndef PJ_ICE_MAX_CHECKS +#define PJ_ICE_MAX_CHECKS 32 +#endif + +/** + * Default timer interval (in miliseconds) for starting ICE periodic checks. + * + * Default: 20 + */ +#ifndef PJ_ICE_TA_VAL +#define PJ_ICE_TA_VAL 20 +#endif + +/** + * According to ICE Section 8.2. Updating States, if an In-Progress pair in + * the check list is for the same component as a nominated pair, the agent + * SHOULD cease retransmissions for its check if its pair priority is lower + * than the lowest priority nominated pair for that component. + * + * If a higher priority check is In Progress, this rule would cause that + * check to be performed even when it most likely will fail. + * + * The macro here controls if ICE session should cancel all In Progress + * checks for the same component regardless of its priority. + * + * Default: 1 (yes, cancel all) + */ +#ifndef PJ_ICE_CANCEL_ALL +#define PJ_ICE_CANCEL_ALL 1 +#endif + +/** + * For a controlled agent, specify how long it wants to wait (in milliseconds) + * for the controlling agent to complete sending connectivity check with + * nominated flag set to true for all components after the controlled agent + * has found that all connectivity checks in its checklist have been completed + * and there is at least one successful (but not nominated) check for every + * component. + * + * When selecting the value, bear in mind that the connectivity check from + * controlling agent may be delayed because of delay in receiving SDP answer + * from the controlled agent. + * + * Application may set this value to -1 to disable this timer. + * + * Default: 10000 (milliseconds) + */ +#ifndef ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT +#define ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT 10000 +#endif + +/** + * For controlling agent if it uses regular nomination, specify the delay to + * perform nominated check (connectivity check with USE-CANDIDATE attribute) + * after all components have a valid pair. + * + * Default: 4*PJ_STUN_RTO_VALUE (milliseconds) + */ +#ifndef PJ_ICE_NOMINATED_CHECK_DELAY +#define PJ_ICE_NOMINATED_CHECK_DELAY (4 * PJ_STUN_RTO_VALUE) +#endif + +/** + * Minimum interval value to be used for sending STUN keep-alive on the ICE + * session, in seconds. This minimum interval, plus a random value + * which maximum is PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND, specify the actual interval + * of the STUN keep-alive. + * + * Default: 15 seconds + * + * @see PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND + */ +#ifndef PJ_ICE_SESS_KEEP_ALIVE_MIN +#define PJ_ICE_SESS_KEEP_ALIVE_MIN 20 +#endif + +/* Warn about deprecated macro */ +#ifdef PJ_ICE_ST_KEEP_ALIVE_MIN +#error PJ_ICE_ST_KEEP_ALIVE_MIN is deprecated +#endif + +/** + * To prevent STUN keep-alives to be sent simultaneously, application should + * add random interval to minimum interval (PJ_ICE_SESS_KEEP_ALIVE_MIN). This + * setting specifies the maximum random value to be added to the minimum + * interval, in seconds. + * + * Default: 5 seconds + * + * @see PJ_ICE_SESS_KEEP_ALIVE_MIN + */ +#ifndef PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND +#define PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND 5 +#endif + +/* Warn about deprecated macro */ +#ifdef PJ_ICE_ST_KEEP_ALIVE_MAX_RAND +#error PJ_ICE_ST_KEEP_ALIVE_MAX_RAND is deprecated +#endif + +/** + * This constant specifies the length of random string generated for ICE + * ufrag. + * + * Default: 8 (characters) + */ +#ifndef PJ_ICE_UFRAG_LEN +#define PJ_ICE_UFRAG_LEN 8 +#endif + +/** + * This constant specifies the length of random string generated for ICE + * password. + * + * Default: 24 (characters) + */ +#ifndef PJ_ICE_PWD_LEN +#define PJ_ICE_PWD_LEN 24 +#endif + +/** + * This constant specifies whether ICE stream transport should allow TURN + * client session to automatically renew permission for all remote candidates. + * + * Default: PJ_FALSE + */ +#ifndef PJ_ICE_ST_USE_TURN_PERMANENT_PERM +#define PJ_ICE_ST_USE_TURN_PERMANENT_PERM PJ_FALSE +#endif + +/** + * For trickle ICE, this macro specifies the maximum time of waiting for + * end-of-candidates indication from remote once ICE connectivity checks + * is started, in seconds. When the timer expires, ICE will assume that + * end-of-candidates indication is received so any further remote candidate + * update will be ignored. + * + * Note that without remote end-of-candidates indication, ICE will not be + * able to conclude that the ICE negotiation has failed when all pair checks + * are completed but there is no valid pair (on the other hand, the ICE + * negotiation may be completed as successful before the end-of-candidates + * indication is received when valid pairs are found very quickly). + * + * Also note that the ICE connectivity checks should only be started after + * both agents have started trickling ICE candidates (e.g: both have sent + * their SDPs, either via normal SDP offer/answer or SIP INFO). + * + * Default: 40 seconds. + */ +#ifndef PJ_TRICKLE_ICE_END_OF_CAND_TIMEOUT +#define PJ_TRICKLE_ICE_END_OF_CAND_TIMEOUT 40 +#endif + +/** ICE session pool initial size. */ +#ifndef PJNATH_POOL_LEN_ICE_SESS +#define PJNATH_POOL_LEN_ICE_SESS 512 +#endif + +/** ICE session pool increment size */ +#ifndef PJNATH_POOL_INC_ICE_SESS +#define PJNATH_POOL_INC_ICE_SESS 512 +#endif + +/** ICE stream transport pool initial size. */ +#ifndef PJNATH_POOL_LEN_ICE_STRANS +#define PJNATH_POOL_LEN_ICE_STRANS 1000 +#endif + +/** ICE stream transport pool increment size */ +#ifndef PJNATH_POOL_INC_ICE_STRANS +#define PJNATH_POOL_INC_ICE_STRANS 512 +#endif + +/** NAT detect pool initial size */ +#ifndef PJNATH_POOL_LEN_NATCK +#define PJNATH_POOL_LEN_NATCK 512 +#endif + +/** NAT detect pool increment size */ +#ifndef PJNATH_POOL_INC_NATCK +#define PJNATH_POOL_INC_NATCK 512 +#endif + +/** STUN session pool initial size */ +#ifndef PJNATH_POOL_LEN_STUN_SESS +#define PJNATH_POOL_LEN_STUN_SESS 1000 +#endif + +/** STUN session pool increment size */ +#ifndef PJNATH_POOL_INC_STUN_SESS +#define PJNATH_POOL_INC_STUN_SESS 1000 +#endif + +/** STUN session transmit data pool initial size */ +#ifndef PJNATH_POOL_LEN_STUN_TDATA +#define PJNATH_POOL_LEN_STUN_TDATA 1000 +#endif + +/** STUN session transmit data pool increment size */ +#ifndef PJNATH_POOL_INC_STUN_TDATA +#define PJNATH_POOL_INC_STUN_TDATA 1000 +#endif + +/** TURN session initial pool size */ +#ifndef PJNATH_POOL_LEN_TURN_SESS +#define PJNATH_POOL_LEN_TURN_SESS 1000 +#endif + +/** TURN session pool increment size */ +#ifndef PJNATH_POOL_INC_TURN_SESS +#define PJNATH_POOL_INC_TURN_SESS 1000 +#endif + +/** TURN socket initial pool size */ +#ifndef PJNATH_POOL_LEN_TURN_SOCK +#define PJNATH_POOL_LEN_TURN_SOCK 1000 +#endif + +/** TURN socket pool increment size */ +#ifndef PJNATH_POOL_INC_TURN_SOCK +#define PJNATH_POOL_INC_TURN_SOCK 1000 +#endif + +//#define PJNATH_STUN_SOFTWARE_NAME "tuya_p2p_sdk_v3.4.120" +/** Default STUN software name */ +#ifndef PJNATH_STUN_SOFTWARE_NAME +/** Create STUN software name */ +#define PJNATH_MAKE_SW_NAME(a, b, c, d) "pjnath-" #a "." #b "." #c d +/** Create STUN software name */ +#define PJNATH_MAKE_SW_NAME2(a, b, c, d) PJNATH_MAKE_SW_NAME(a, b, c, d) +/** Default STUN software name */ +#define PJNATH_STUN_SOFTWARE_NAME \ + PJNATH_MAKE_SW_NAME2(PJ_VERSION_NUM_MAJOR, PJ_VERSION_NUM_MINOR, PJ_VERSION_NUM_REV, PJ_VERSION_NUM_EXTRA) +#endif + +/* ************************************************************************** + * UPnP + */ + +/** Default duration for searching UPnP Internet Gateway Devices (in seconds). + * Default: 5 seconds + */ +#ifndef PJ_UPNP_DEFAULT_SEARCH_TIME +#define PJ_UPNP_DEFAULT_SEARCH_TIME 5 +#endif + +/** + * @} + */ + +#endif /* __PJNATH_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h new file mode 100755 index 000000000..d02090870 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_ERRNO_H__ +#define __PJNATH_ERRNO_H__ + +/** + * @file errno.h + * @brief PJNATH specific error codes + */ + +#include + +/** + * @defgroup PJNATH_ERROR NAT Helper Library Error Codes + * @brief PJNATH specific error code constants + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + * This value is 370000. + */ +#define PJNATH_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 4) + +/************************************************************ + * STUN MESSAGING ERRORS + ***********************************************************/ + +/** + * Map STUN error code (300-699) into pj_status_t error space. + */ +#define PJ_STATUS_FROM_STUN_CODE(code) (PJNATH_ERRNO_START + code) + +/** + * @hideinitializer + * Invalid STUN message + */ +#define PJNATH_EINSTUNMSG (PJNATH_ERRNO_START + 1) /* 370001 */ +/** + * @hideinitializer + * Invalid STUN message length. + */ +#define PJNATH_EINSTUNMSGLEN (PJNATH_ERRNO_START + 2) /* 370002 */ +/** + * @hideinitializer + * Invalid or unexpected STUN message type + */ +#define PJNATH_EINSTUNMSGTYPE (PJNATH_ERRNO_START + 3) /* 370003 */ +/** + * @hideinitializer + * STUN transaction has timed out + */ +#define PJNATH_ESTUNTIMEDOUT (PJNATH_ERRNO_START + 4) /* 370004 */ + +/** + * @hideinitializer + * Too many STUN attributes. + */ +#define PJNATH_ESTUNTOOMANYATTR (PJNATH_ERRNO_START + 21) /* 370021 */ +/** + * @hideinitializer + * Invalid STUN attribute length. + */ +#define PJNATH_ESTUNINATTRLEN (PJNATH_ERRNO_START + 22) /* 370022 */ +/** + * @hideinitializer + * Found duplicate STUN attribute. + */ +#define PJNATH_ESTUNDUPATTR (PJNATH_ERRNO_START + 23) /* 370023 */ + +/** + * @hideinitializer + * STUN FINGERPRINT verification failed + */ +#define PJNATH_ESTUNFINGERPRINT (PJNATH_ERRNO_START + 30) /* 370030 */ +/** + * @hideinitializer + * Invalid STUN attribute after MESSAGE-INTEGRITY. + */ +#define PJNATH_ESTUNMSGINTPOS (PJNATH_ERRNO_START + 31) /* 370031 */ +/** + * @hideinitializer + * Invalid STUN attribute after FINGERPRINT. + */ +#define PJNATH_ESTUNFINGERPOS (PJNATH_ERRNO_START + 33) /* 370033 */ + +/** + * @hideinitializer + * STUN (XOR-)MAPPED-ADDRESS attribute not found + */ +#define PJNATH_ESTUNNOMAPPEDADDR (PJNATH_ERRNO_START + 40) /* 370040 */ +/** + * @hideinitializer + * STUN IPv6 attribute not supported + */ +#define PJNATH_ESTUNIPV6NOTSUPP (PJNATH_ERRNO_START + 41) /* 370041 */ +/** + * @hideinitializer + * Invalid address family value in STUN message. + */ +#define PJNATH_EINVAF (PJNATH_ERRNO_START + 42) /* 370042 */ + +/** + * @hideinitializer + * Invalid STUN server or server not configured. + */ +#define PJNATH_ESTUNINSERVER (PJNATH_ERRNO_START + 50) /* 370050 */ + +/************************************************************ + * STUN SESSION/TRANSPORT ERROR CODES + ***********************************************************/ +/** + * @hideinitializer + * STUN object has been destoyed. + */ +#define PJNATH_ESTUNDESTROYED (PJNATH_ERRNO_START + 60) /* 370060 */ + +/************************************************************ + * ICE ERROR CODES + ***********************************************************/ + +/** + * @hideinitializer + * ICE session not available + */ +#define PJNATH_ENOICE (PJNATH_ERRNO_START + 80) /* 370080 */ +/** + * @hideinitializer + * ICE check is in progress + */ +#define PJNATH_EICEINPROGRESS (PJNATH_ERRNO_START + 81) /* 370081 */ +/** + * @hideinitializer + * This error indicates that ICE connectivity check has failed, because + * there is at least one ICE component that does not have a valid check. + * Normally this happens because the network topology had caused the + * connectivity check to fail (e.g. no route between the two agents), + * however other reasons may include software incompatibility between + * the two agents, or incomplete candidates gathered by the agent(s). + */ +#define PJNATH_EICEFAILED (PJNATH_ERRNO_START + 82) /* 370082 */ +/** + * @hideinitializer + * Default destination does not match any ICE candidates + */ +#define PJNATH_EICEMISMATCH (PJNATH_ERRNO_START + 83) /* 370083 */ +/** + * @hideinitializer + * Invalid ICE component ID + */ +#define PJNATH_EICEINCOMPID (PJNATH_ERRNO_START + 86) /* 370086 */ +/** + * @hideinitializer + * Invalid ICE candidate ID + */ +#define PJNATH_EICEINCANDID (PJNATH_ERRNO_START + 87) /* 370087 */ +/** + * @hideinitializer + * Source address mismatch. This error occurs if the source address + * of the response for ICE connectivity check is different than + * the destination address of the request. + */ +#define PJNATH_EICEINSRCADDR (PJNATH_ERRNO_START + 88) /* 370088 */ +/** + * @hideinitializer + * Missing ICE SDP attribute + */ +#define PJNATH_EICEMISSINGSDP (PJNATH_ERRNO_START + 90) /* 370090 */ +/** + * @hideinitializer + * Invalid SDP "candidate" attribute + */ +#define PJNATH_EICEINCANDSDP (PJNATH_ERRNO_START + 91) /* 370091 */ +/** + * @hideinitializer + * No host candidate associated with srflx. This error occurs when + * a server reflexive candidate is added without the matching + * host candidate. + */ +#define PJNATH_EICENOHOSTCAND (PJNATH_ERRNO_START + 92) /* 370092 */ +/** + * @hideinitializer + * Controlled agent timed-out in waiting for the controlling agent to + * send nominated check after all connectivity checks have completed. + */ +#define PJNATH_EICENOMTIMEOUT (PJNATH_ERRNO_START + 93) /* 370093 */ + +/************************************************************ + * TURN ERROR CODES + ***********************************************************/ +/** + * @hideinitializer + * Invalid or unsupported TURN transport. + */ +#define PJNATH_ETURNINTP (PJNATH_ERRNO_START + 120) /* 370120 */ + +/** + * @} + */ + +#endif /* __PJNATH_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h new file mode 100755 index 000000000..897c27776 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h @@ -0,0 +1,1034 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_ICE_SESSION_H__ +#define __PJNATH_ICE_SESSION_H__ + +/** + * @file ice_session.h + * @brief ICE session management + */ +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @addtogroup PJNATH_ICE_SESSION + * @{ + * + * This module describes #pj_ice_sess, a transport independent ICE session, + * part of PJNATH - the Open Source NAT helper library. + * + * \section pj_ice_sess_sec ICE Session + * + * An ICE session, represented by #pj_ice_sess structure, is the lowest + * abstraction of ICE in PJNATH, and it is used to perform and manage + * connectivity checks of transport address candidates within a + * single media stream (note: this differs from what is described + * in ICE draft, where an ICE session manages the whole media sessions + * rather than just a single stream). + * + * The ICE session described here is independent from any transports, + * meaning that the actual network I/O for this session would have to + * be performed by the application, or higher layer abstraction. + * Using this framework, application would give any incoming packets to + * the ICE session, and it would provide the ICE session with a callback + * to send outgoing message. + * + * For higher abstraction of ICE where transport is included, please + * see \ref PJNATH_ICE_STREAM_TRANSPORT. + * + * \subsection pj_ice_sess_using_sec Using The ICE Session + * + * The steps below describe how to use ICE session. Alternatively application + * can use the higher level ICE API, \ref PJNATH_ICE_STREAM_TRANSPORT, + * which has provided the integration of ICE with socket transport. + * + * The steps to use ICE session is similar for both offerer and + * answerer: + * - create ICE session with #pj_ice_sess_create(). Among other things, + * application needs to specify: + * - STUN configuration (pj_stun_config), containing STUN settings + * such as timeout values and the instances of timer heap and + * ioqueue. + * - Session name, useful for identifying this session in the log. + * - Initial ICE role (#pj_ice_sess_role). The role can be changed + * at later time with #pj_ice_sess_change_role(), and ICE session + * can also change its role automatically when it detects role + * conflict. + * - Number of components in the media session. + * - Callback to receive ICE events (#pj_ice_sess_cb) + * - Optional local ICE username and password. If these arguments + * are NULL, they will be generated randomly. + * - Add local candidates for each component, with #pj_ice_sess_add_cand(). + * A candidate is represented with #pj_ice_sess_cand structure. + * Each component must be provided with at least one candidate, and + * all components must have the same number of candidates. Failing + * to comply with this will cause failure during pairing process. + * - Create offer to describe local ICE candidates. ICE session does not + * provide a function to create such offer, but application should be + * able to create one since it knows about all components and candidates. + * If application uses \ref PJNATH_ICE_STREAM_TRANSPORT, it can + * enumerate local candidates by calling #pj_ice_strans_enum_cands(). + * Application may use #pj_ice_sess_find_default_cand() to let ICE + * session chooses the default transport address to be used in SDP + * c= and m= lines. + * - Send the offer to remote endpoint using signaling such as SIP. + * - Once application has received the answer, it should parse this + * answer, build array of remote candidates, and create check lists by + * calling #pj_ice_sess_create_check_list(). This process is known as + * pairing the candidates, and will result in the creation of check lists. + * - Once checklist has been created, application then can call + * #pj_ice_sess_start_check() to instruct ICE session to start + * performing connectivity checks. The ICE session performs the + * connectivity checks by processing each check in the checklists. + * - Application will be notified about the result of ICE connectivity + * checks via the callback that was given in #pj_ice_sess_create() + * above. + * + * To send data, application calls #pj_ice_sess_send_data(). If ICE + * negotiation has not completed, ICE session would simply drop the data, + * and return error to caller. If ICE negotiation has completed + * successfully, ICE session will in turn call the \a on_tx_pkt + * callback of #pj_ice_sess_cb instance that was previously registered + * in #pj_ice_sess_create() above. + * + * When application receives any packets on the underlying sockets, it + * must call #pj_ice_sess_on_rx_pkt(). The ICE session will inspect the + * packet to decide whether to process it locally (if the packet is a + * STUN message and is part of ICE session) or otherwise pass it back to + * application via \a on_rx_data callback. + */ + +/** + * Forward declaration for checklist. + */ +typedef struct pj_ice_sess_checklist pj_ice_sess_checklist; + +/** + * This enumeration describes the type of an ICE candidate. + */ +typedef enum pj_ice_cand_type { + /** + * ICE host candidate. A host candidate represents the actual local + * transport address in the host. + */ + PJ_ICE_CAND_TYPE_HOST, + + /** + * ICE server reflexive candidate, which represents the public mapped + * address of the local address, and is obtained by sending STUN + * Binding request from the host candidate to a STUN server. + */ + PJ_ICE_CAND_TYPE_SRFLX, + + /** + * ICE peer reflexive candidate, which is the address as seen by peer + * agent during connectivity check. + */ + PJ_ICE_CAND_TYPE_PRFLX, + + /** + * ICE relayed candidate, which represents the address allocated in + * TURN server. + */ + PJ_ICE_CAND_TYPE_RELAYED, + + /** + * Number of defined ICE candidate types. + */ + PJ_ICE_CAND_TYPE_MAX + +} pj_ice_cand_type; + +/** Forward declaration for pj_ice_sess */ +typedef struct pj_ice_sess pj_ice_sess; + +/** Forward declaration for pj_ice_sess_check */ +typedef struct pj_ice_sess_check pj_ice_sess_check; + +/** Forward declaration for pj_ice_sess_cand */ +typedef struct pj_ice_sess_cand pj_ice_sess_cand; + +/** + * This structure describes ICE component. + * A media stream may require multiple components, each of which has + * to work for the media stream as a whole to work. For media streams + * based on RTP, there are two components per media stream - one for RTP, + * and one for RTCP. + */ +typedef struct pj_ice_sess_comp { + /** + * Pointer to ICE check with highest priority which connectivity check + * has been successful. The value will be NULL if a no successful check + * has not been found for this component. + */ + pj_ice_sess_check *valid_check; + + /** + * Pointer to ICE check with highest priority which connectivity check + * has been successful and it has been nominated. The value may be NULL + * if there is no such check yet. + */ + pj_ice_sess_check *nominated_check; + + /** + * The STUN session to be used to send and receive STUN messages for this + * component. + */ + pj_stun_session *stun_sess; + +} pj_ice_sess_comp; + +/** + * Data structure to be attached to internal message processing. + */ +typedef struct pj_ice_msg_data { + /** Transport ID for this message */ + unsigned transport_id; + + /** Flag to indicate whether data.req contains data */ + pj_bool_t has_req_data; + + /** The data */ + union data { + /** Request data */ + struct request_data { + pj_ice_sess *ice; /**< ICE session */ + pj_ice_sess_checklist *clist; /**< Checklist */ + unsigned ckid; /**< Check ID */ + pj_ice_sess_cand *lcand; /**< Local cand */ + pj_ice_sess_cand *rcand; /**< Remote cand */ + } req; /**< Request data */ + } data; /**< The data */ + +} pj_ice_msg_data; + +/** + * This structure describes an ICE candidate. + * ICE candidate is a transport address that is to be tested by ICE + * procedures in order to determine its suitability for usage for + * receipt of media. Candidates also have properties - their type + * (server reflexive, relayed or host), priority, foundation, and + * base. + */ +struct pj_ice_sess_cand { + /** + * The candidate ID. + */ + unsigned id; + + /** + * The candidate type, as described in #pj_ice_cand_type enumeration. + */ + pj_ice_cand_type type; + + /** + * Status of this candidate. The value will be PJ_SUCCESS if candidate + * address has been resolved successfully, PJ_EPENDING when the address + * resolution process is in progress, or other value when the address + * resolution has completed with failure. + */ + pj_status_t status; + + /** + * The component ID of this candidate. Note that component IDs starts + * with one for RTP and two for RTCP. In other words, it's not zero + * based. + */ + pj_uint8_t comp_id; + + /** + * Transport ID to be used to send packets for this candidate. + */ + pj_uint8_t transport_id; + + /** + * Local preference value, which typically is 65535. + */ + pj_uint16_t local_pref; + + /** + * The foundation string, which is an identifier which value will be + * equivalent for two candidates that are of the same type, share the + * same base, and come from the same STUN server. The foundation is + * used to optimize ICE performance in the Frozen algorithm. + */ + pj_str_t foundation; + + /** + * The candidate's priority, a 32-bit unsigned value which value will be + * calculated by the ICE session when a candidate is registered to the + * ICE session. + */ + pj_uint32_t prio; + + /** + * IP address of this candidate. For host candidates, this represents + * the local address of the socket. For reflexive candidates, the value + * will be the public address allocated in NAT router for the host + * candidate and as reported in MAPPED-ADDRESS or XOR-MAPPED-ADDRESS + * attribute of STUN Binding request. For relayed candidate, the value + * will be the address allocated in the TURN server by STUN Allocate + * request. + */ + pj_sockaddr addr; + + /** + * Base address of this candidate. "Base" refers to the address an agent + * sends from for a particular candidate. For host candidates, the base + * is the same as the host candidate itself. For reflexive candidates, + * the base is the local IP address of the socket. For relayed candidates, + * the base address is the transport address allocated in the TURN server + * for this candidate. + */ + pj_sockaddr base_addr; + + /** + * Related address, which is used for informational only and is not used + * in any way by the ICE session. + */ + pj_sockaddr rel_addr; +}; + +/** + * This enumeration describes the state of ICE check. + */ +typedef enum pj_ice_sess_check_state { + /** + * A check for this pair hasn't been performed, and it can't + * yet be performed until some other check succeeds, allowing this + * pair to unfreeze and move into the Waiting state. + */ + PJ_ICE_SESS_CHECK_STATE_FROZEN, + + /** + * A check has not been performed for this pair, and can be + * performed as soon as it is the highest priority Waiting pair on + * the check list. + */ + PJ_ICE_SESS_CHECK_STATE_WAITING, + + /** + * A check has not been performed for this pair, and can be + * performed as soon as it is the highest priority Waiting pair on + * the check list. + */ + PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS, + + /** + * A check has not been performed for this pair, and can be + * performed as soon as it is the highest priority Waiting pair on + * the check list. + */ + PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, + + /** + * A check for this pair was already done and failed, either + * never producing any response or producing an unrecoverable failure + * response. + */ + PJ_ICE_SESS_CHECK_STATE_FAILED + +} pj_ice_sess_check_state; + +/** + * This structure describes an ICE connectivity check. An ICE check + * contains a candidate pair, and will involve sending STUN Binding + * Request transaction for the purposes of verifying connectivity. + * A check is sent from the local candidate to the remote candidate + * of a candidate pair. + */ +struct pj_ice_sess_check { + /** + * Pointer to local candidate entry of this check. + */ + pj_ice_sess_cand *lcand; + + /** + * Pointer to remote candidate entry of this check. + */ + pj_ice_sess_cand *rcand; + + /** + * Foundation index, referring to foundation array defined in checklist. + */ + int foundation_idx; + + /** + * Check priority. + */ + pj_timestamp prio; + + /** + * Connectivity check state. + */ + pj_ice_sess_check_state state; + + /** + * STUN transmit data containing STUN Binding request that was sent + * as part of this check. The value will only be set when this check + * has a pending transaction, and is used to cancel the transaction + * when other check has succeeded. + */ + pj_stun_tx_data *tdata; + + /** + * Flag to indicate whether this check is nominated. A nominated check + * contains USE-CANDIDATE attribute in its STUN Binding request. + */ + pj_bool_t nominated; + + /** + * When the check failed, this will contain the failure status of the + * STUN transaction. + */ + pj_status_t err_code; +}; + +/** + * This enumeration describes ICE checklist state. + */ +typedef enum pj_ice_sess_checklist_state { + /** + * The checklist is not yet running. + */ + PJ_ICE_SESS_CHECKLIST_ST_IDLE, + + /** + * In this state, ICE checks are still in progress for this + * media stream. + */ + PJ_ICE_SESS_CHECKLIST_ST_RUNNING, + + /** + * In this state, ICE checks have completed for this media stream, + * either successfully or with failure. + */ + PJ_ICE_SESS_CHECKLIST_ST_COMPLETED + +} pj_ice_sess_checklist_state; + +/** + * This structure represents ICE check list, that is an ordered set of + * candidate pairs that an agent will use to generate checks. + */ +struct pj_ice_sess_checklist { + /** + * The checklist state. + */ + pj_ice_sess_checklist_state state; + + /** + * Number of candidate pairs (checks). + */ + unsigned count; + + /** + * Array of candidate pairs (checks). + */ + pj_ice_sess_check checks[PJ_ICE_MAX_CHECKS]; + + /** + * Number of foundations. + */ + unsigned foundation_cnt; + + /** + * Array of foundations, check foundation index refers to this array. + */ + pj_str_t foundation[PJ_ICE_MAX_CHECKS * 2]; + + /** + * A timer used to perform periodic check for this checklist. + */ + pj_timer_entry timer; +}; + +/** + * This structure contains callbacks that will be called by the ICE + * session. + */ +typedef struct pj_ice_sess_cb { + /** + * An optional callback that will be called by the ICE session when + * a valid pair has been found during ICE negotiation. + * + * @param ice The ICE session. + */ + void (*on_valid_pair)(pj_ice_sess *ice); + + /** + * An optional callback that will be called by the ICE session when + * ICE negotiation has completed, successfully or with failure. + * + * @param ice The ICE session. + * @param status Will contain PJ_SUCCESS if ICE negotiation is + * successful, or some error code. + */ + void (*on_ice_complete)(pj_ice_sess *ice, pj_status_t status); + + /** + * A mandatory callback which will be called by the ICE session when + * it needs to send outgoing STUN packet. + * + * @param ice The ICE session. + * @param comp_id ICE component ID. + * @param transport_id Transport ID. + * @param pkt The STUN packet. + * @param size The size of the packet. + * @param dst_addr Packet destination address. + * @param dst_addr_len Length of destination address. + */ + pj_status_t (*on_tx_pkt)(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, pj_size_t size, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); + + /** + * A mandatory callback which will be called by the ICE session when + * it receives packet which is not part of ICE negotiation. + * + * @param ice The ICE session. + * @param comp_id ICE component ID. + * @param transport_id Transport ID. + * @param pkt The whole packet. + * @param size Size of the packet. + * @param src_addr Source address where this packet was received + * from. + * @param src_addr_len The length of source address. + */ + void (*on_rx_data)(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); +} pj_ice_sess_cb; + +/** + * This enumeration describes the role of the ICE agent. + */ +typedef enum pj_ice_sess_role { + /** + * The role is unknown. + */ + PJ_ICE_SESS_ROLE_UNKNOWN, + + /** + * The ICE agent is in controlled role. + */ + PJ_ICE_SESS_ROLE_CONTROLLED, + + /** + * The ICE agent is in controlling role. + */ + PJ_ICE_SESS_ROLE_CONTROLLING + +} pj_ice_sess_role; + +/** + * This structure represents an incoming check (an incoming Binding + * request message), and is mainly used to keep early checks in the + * list in the ICE session. An early check is a request received + * from remote when we haven't received SDP answer yet, therefore we + * can't perform triggered check. For such cases, keep the incoming + * request in a list, and we'll do triggered checks (simultaneously) + * as soon as we receive answer. + */ +typedef struct pj_ice_rx_check { + PJ_DECL_LIST_MEMBER(struct pj_ice_rx_check); /**< Standard list */ + + unsigned comp_id; /**< Component ID. */ + unsigned transport_id; /**< Transport ID. */ + + pj_sockaddr src_addr; /**< Source address of request */ + unsigned src_addr_len; /**< Length of src address. */ + + pj_bool_t use_candidate; /**< USE-CANDIDATE is present? */ + pj_uint32_t priority; /**< PRIORITY value in the req. */ + pj_stun_uint64_attr *role_attr; /**< ICE-CONTROLLING/CONTROLLED */ + +} pj_ice_rx_check; + +/** + * This enumeration describes the modes of trickle ICE. + */ +typedef enum pj_ice_sess_trickle { + /** + * Trickle ICE is disabled. + */ + PJ_ICE_SESS_TRICKLE_DISABLED, + + /** + * Half trickle ICE. This mode has better interoperability when remote + * capability of ICE trickle is unknown at ICE initialization. + * + * As ICE initiator, it will convey all local ICE candidates to remote + * (just like regular ICE) and be ready to receive either response, + * trickle or regular ICE. As ICE answerer, it will do trickle ICE if + * it receives an offer with trickle ICE indication, otherwise it will do + * regular ICE. + */ + PJ_ICE_SESS_TRICKLE_HALF, + + /** + * Full trickle ICE. Only use this mode if it is known that that remote + * supports trickle ICE. The discovery whether remote supports trickle + * ICE should be done prior to ICE initialization and done by application + * (ICE does not provide the discovery mechanism). + */ + PJ_ICE_SESS_TRICKLE_FULL + +} pj_ice_sess_trickle; + +/** + * This structure describes various ICE session options. Application + * configure the ICE session with these options by calling + * #pj_ice_sess_set_options(). + */ +typedef struct pj_ice_sess_options { + /** + * Specify whether to use aggressive nomination. This setting can only + * be enabled when trickle ICE is disabled. + */ + pj_bool_t aggressive; + + /** + * For controlling agent if it uses regular nomination, specify the delay + * to perform nominated check (connectivity check with USE-CANDIDATE + * attribute) after all components have a valid pair. + * + * Default value is PJ_ICE_NOMINATED_CHECK_DELAY. + */ + unsigned nominated_check_delay; + + /** + * For a controlled agent, specify how long it wants to wait (in + * milliseconds) for the controlling agent to complete sending + * connectivity check with nominated flag set to true for all components + * after the controlled agent has found that all connectivity checks in + * its checklist have been completed and there is at least one successful + * (but not nominated) check for every component. + * + * Default value for this option is + * ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT. Specify -1 to disable + * this timer. + */ + int controlled_agent_want_nom_timeout; + + /** + * Trickle ICE mode. Note that, when enabled, aggressive nomination will + * be automatically disabled. + * + * Default value is PJ_ICE_SESS_TRICKLE_DISABLED. + */ + pj_ice_sess_trickle trickle; + +} pj_ice_sess_options; + +/** + * This structure describes the ICE session. For this version of PJNATH, + * an ICE session corresponds to a single media stream (unlike the ICE + * session described in the ICE standard where an ICE session covers the + * whole media and may consist of multiple media streams). The decision + * to support only a single media session was chosen for simplicity, + * while still allowing application to utilize multiple media streams by + * creating multiple ICE sessions, one for each media stream. + */ +struct pj_ice_sess { + char obj_name[PJ_MAX_OBJ_NAME]; /**< Object name. */ + + pj_pool_t *pool; /**< Pool instance. */ + void *user_data; /**< App. data. */ + pj_grp_lock_t *grp_lock; /**< Group lock */ + pj_ice_sess_role role; /**< ICE role. */ + pj_ice_sess_options opt; /**< Options */ + pj_timestamp tie_breaker; /**< Tie breaker value */ + pj_uint8_t *prefs; /**< Type preference. */ + pj_bool_t is_nominating; /**< Nominating stage */ + pj_bool_t is_complete; /**< Complete? */ + pj_bool_t is_destroying; /**< Destroy is called */ + pj_bool_t valid_pair_found; /**< First pair found */ + pj_bool_t is_trickling; /**< End-of-candidates ind + sent/received? */ + pj_status_t ice_status; /**< Error status. */ + pj_timer_entry timer; /**< ICE timer. */ + pj_timer_entry timer_end_of_cand; /**< End-of-cand timer. */ + pj_ice_sess_cb cb; /**< Callback. */ + + pj_stun_config stun_cfg; /**< STUN settings. */ + + /* STUN credentials */ + pj_str_t tx_ufrag; /**< Remote ufrag. */ + pj_str_t tx_uname; /**< Uname for TX. */ + pj_str_t tx_pass; /**< Remote password. */ + pj_str_t rx_ufrag; /**< Local ufrag. */ + pj_str_t rx_uname; /**< Uname for RX */ + pj_str_t rx_pass; /**< Local password. */ + + /* Components */ + unsigned comp_cnt; /**< # of components. */ + pj_ice_sess_comp comp[PJ_ICE_MAX_COMP]; /**< Component array */ + unsigned comp_ka; /**< Next comp for KA */ + + /* Local candidates */ + unsigned lcand_cnt; /**< # of local cand. */ + pj_ice_sess_cand lcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */ + unsigned lcand_paired; /**< # of local cand + paired (trickling) */ + + /* Remote candidates */ + unsigned rcand_cnt; /**< # of remote cand. */ + pj_ice_sess_cand rcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */ + unsigned rcand_paired; /**< # of remote cand + paired (trickling) */ + + /** Array of transport datas */ + pj_ice_msg_data tp_data[PJ_ICE_MAX_STUN + PJ_ICE_MAX_TURN]; + + /* List of eearly checks */ + pj_ice_rx_check early_check; /**< Early checks. */ + + /* Checklist */ + pj_ice_sess_checklist clist; /**< Active checklist */ + + /* Valid list */ + pj_ice_sess_checklist valid_list; /**< Valid list. */ + + /** Temporary buffer for misc stuffs to avoid using stack too much */ + union { + char txt[128]; + char errmsg[PJ_ERR_MSG_SIZE]; + } tmp; +}; + +/** + * This is a utility function to retrieve the string name for the + * particular candidate type. + * + * @param type Candidate type. + * + * @return The string representation of the candidate type. + */ +PJ_DECL(const char *) pj_ice_get_cand_type_name(pj_ice_cand_type type); + +/** + * This is a utility function to retrieve the string name for the + * particular role type. + * + * @param role Role type. + * + * @return The string representation of the role. + */ +PJ_DECL(const char *) pj_ice_sess_role_name(pj_ice_sess_role role); + +/** + * This is a utility function to calculate the foundation identification + * for a candidate. + * + * @param pool Pool to allocate the foundation string. + * @param foundation Pointer to receive the foundation string. + * @param type Candidate type. + * @param base_addr Base address of the candidate. + */ +PJ_DECL(void) +pj_ice_calc_foundation(pj_pool_t *pool, pj_str_t *foundation, pj_ice_cand_type type, const pj_sockaddr *base_addr); + +/** + * Initialize ICE session options with library default values. + * + * @param opt ICE session options. + */ +PJ_DECL(void) pj_ice_sess_options_default(pj_ice_sess_options *opt); + +/** + * Create ICE session with the specified role and number of components. + * Application would typically need to create an ICE session before + * sending an offer or upon receiving one. After the session is created, + * application can register candidates to the ICE session by calling + * #pj_ice_sess_add_cand() function. + * + * @param stun_cfg The STUN configuration settings, containing among + * other things the timer heap instance to be used + * by the ICE session. + * @param name Optional name to identify this ICE instance in + * the log file. + * @param role ICE role. + * @param comp_cnt Number of components. + * @param cb ICE callback. + * @param local_ufrag Optional string to be used as local username to + * authenticate incoming STUN binding request. If + * the value is NULL, a random string will be + * generated. + * @param local_passwd Optional string to be used as local password. + * @param grp_lock Optional group lock to be used by this session. + * If NULL, the session will create one itself. + * @param p_ice Pointer to receive the ICE session instance. + * + * @return PJ_SUCCESS if ICE session is created successfully. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_create(pj_stun_config *stun_cfg, const char *name, pj_ice_sess_role role, unsigned comp_cnt, + const pj_ice_sess_cb *cb, const pj_str_t *local_ufrag, const pj_str_t *local_passwd, + pj_grp_lock_t *grp_lock, pj_ice_sess **p_ice); + +/** + * Get the value of various options of the ICE session. + * + * @param ice The ICE session. + * @param opt The options to be initialized with the values + * from the ICE session. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice, pj_ice_sess_options *opt); + +/** + * Specify various options for this ICE session. Application MUST only + * call this function after the ICE session has been created but before + * any connectivity check is started. + * + * Application should call #pj_ice_sess_get_options() to initialize the + * options with their default values. + * + * @param ice The ICE session. + * @param opt Options to be applied to the ICE session. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, const pj_ice_sess_options *opt); + +/** + * Detach ICE session from group lock. This will delete ICE session group lock + * handler. + * + * This function is useful when application creates an ICE session with + * group lock and later it needs to recreate ICE session (e.g: for ICE + * restart) so the previous ICE session resources can be released manually + * (by calling the group lock handler) without waiting for the group lock + * destroy to avoid memory bloat. + * + * @param ice ICE session instance. + * @param handler Pointer to receive the group lock handler of + * this ICE session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_ice_sess_detach_grp_lock(pj_ice_sess *ice, pj_grp_lock_handler *handler); + +/** + * Destroy ICE session. This will cancel any connectivity checks currently + * running, if any, and any other events scheduled by this session, as well + * as all memory resources. + * + * @param ice ICE session instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_ice_sess_destroy(pj_ice_sess *ice); + +/** + * Change session role. This happens for example when ICE session was + * created with controlled role when receiving an offer, but it turns out + * that the offer contains "a=ice-lite" attribute when the SDP gets + * inspected. + * + * @param ice The ICE session. + * @param new_role The new role to be set. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_change_role(pj_ice_sess *ice, pj_ice_sess_role new_role); + +/** + * Assign a custom preference values for ICE candidate types. By assigning + * custom preference value, application can control the order of candidates + * to be checked first. The default preference settings is to use 126 for + * host candidates, 100 for server reflexive candidates, 110 for peer + * reflexive candidates, an 0 for relayed candidates. + * + * Note that this function must be called before any candidates are added + * to the ICE session. + * + * @param ice The ICE session. + * @param prefs Array of candidate preference value. The values are + * put in the array indexed by the candidate type as + * specified in pj_ice_cand_type. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_sess_set_prefs(pj_ice_sess *ice, const pj_uint8_t prefs[4]); + +/** + * Add a candidate to this ICE session. Application must add candidates for + * each components ID before it can start pairing the candidates and + * performing connectivity checks. + * + * @param ice ICE session instance. + * @param comp_id Component ID of this candidate. + * @param transport_id Transport ID to be used to send packets for this + * candidate. + * @param type Candidate type. + * @param local_pref Local preference for this candidate, which + * normally should be set to 65535. + * @param foundation Foundation identification. + * @param addr The candidate address. + * @param base_addr The candidate's base address. + * @param rel_addr Optional related address. + * @param addr_len Length of addresses. + * @param p_cand_id Optional pointer to receive the candidate ID. + * + * @return PJ_SUCCESS if candidate is successfully added. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_add_cand(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, pj_ice_cand_type type, + pj_uint16_t local_pref, const pj_str_t *foundation, const pj_sockaddr_t *addr, + const pj_sockaddr_t *base_addr, const pj_sockaddr_t *rel_addr, int addr_len, unsigned *p_cand_id); + +/** + * Find default candidate for the specified component ID, using this + * rule: + * - if the component has a successful candidate pair, then the + * local candidate of this pair will be returned. + * - otherwise a relay, reflexive, or host candidate will be selected + * on that specified order. + * + * @param ice The ICE session instance. + * @param comp_id The component ID. + * @param p_cand_id Pointer to receive the candidate ID. + * + * @return PJ_SUCCESS if a candidate has been selected. + */ +PJ_DECL(pj_status_t) pj_ice_sess_find_default_cand(pj_ice_sess *ice, unsigned comp_id, int *p_cand_id); + +/** + * Pair the local and remote candidates to create check list. Application + * typically would call this function after receiving SDP containing ICE + * candidates from the remote host (either upon receiving the initial + * offer, for UAS, or upon receiving the answer, for UAC). + * + * Note that ICE connectivity check will not start until application calls + * #pj_ice_sess_start_check(). + * + * @param ice ICE session instance. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rem_cand_cnt Number of remote candidates. + * @param rem_cand Remote candidate array. Remote candidates are + * gathered from the SDP received from the remote + * agent. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_create_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[]); + +/** + * Update check list after receiving new remote ICE candidates or after + * new local ICE candidates are found and conveyed to remote. This function + * can also be called to indicate that trickling has completed, i.e: + * local candidates gathering completed and remote has sent end-of-candidate + * indication. + * + * This function is only applicable when trickle ICE is not disabled. + * + * @param ice ICE session instance. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rem_cand_cnt Number of remote candidates. + * @param rem_cand Remote candidate array. Remote candidates are + * gathered from the SDP received from the remote + * agent. + * @param trickle_done Flag to indicate end of trickling, set to PJ_TRUE + * after all local candidates have been gathered AND + * after receiving end-of-candidate indication from + * remote. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_update_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[], pj_bool_t trickle_done); + +/** + * Start ICE periodic check. This function will return immediately, and + * application will be notified about the connectivity check status in + * #pj_ice_sess_cb callback. + * + * @param ice The ICE session instance. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice); + +/** + * Send data using this ICE session. If ICE checks have not produced a + * valid check for the specified component ID, this function will return + * with failure. Otherwise ICE session will send the packet to remote + * destination using the nominated local candidate for the specified + * component. + * + * This function will in turn call \a on_tx_pkt function in + * #pj_ice_sess_cb callback to actually send the packet to the wire. + * + * @param ice The ICE session. + * @param comp_id Component ID. + * @param data The data or packet to be sent. + * @param data_len Size of data or packet, in bytes. + * + * @return If the callback \a on_tx_pkt() is called, this + * will contain the return value of the callback. + * Otherwise, it will indicate failure with + * the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice, unsigned comp_id, const void *data, pj_size_t data_len); + +/** + * Report the arrival of packet to the ICE session. Since ICE session + * itself doesn't have any transports, it relies on application or + * higher layer component to give incoming packets to the ICE session. + * If the packet is not a STUN packet, this packet will be given back + * to application via \a on_rx_data() callback in #pj_ice_sess_cb. + * + * @param ice The ICE session. + * @param comp_id Component ID. + * @param transport_id Number to identify where this packet was received + * from. This parameter will be returned back to + * application in \a on_tx_pkt() callback. + * @param pkt Incoming packet. + * @param pkt_size Size of incoming packet. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the address. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *src_addr, int src_addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_ICE_SESSION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h new file mode 100755 index 000000000..486d6a590 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h @@ -0,0 +1,1037 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_ICE_STRANS_H__ +#define __PJNATH_ICE_STRANS_H__ + +/** + * @file ice_strans.h + * @brief ICE Stream Transport + */ +#include +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @addtogroup PJNATH_ICE_STREAM_TRANSPORT + * @{ + * + * This module describes ICE stream transport, as represented by #pj_ice_strans + * structure, and is part of PJNATH - the Open Source NAT traversal helper + * library. + * + * ICE stream transport, as represented by #pj_ice_strans structure, is an ICE + * capable class for transporting media streams within a media session. + * It consists of one or more transport sockets (typically two for RTP + * based communication - one for RTP and one for RTCP), and an + * \ref PJNATH_ICE_SESSION for performing connectivity checks among the. + * various candidates of the transport addresses. + * + * + * \section ice_strans_using_sec Using the ICE stream transport + * + * The steps below describe how to use ICE session: + * + * - initialize a #pj_ice_strans_cfg structure. This contains various + * settings for the ICE stream transport, and among other things contains + * the STUN and TURN settings.\n\n + * - create the instance with #pj_ice_strans_create(). Among other things, + * the function needs the following arguments: + * - the #pj_ice_strans_cfg structure for the main configurations + * - number of components to be supported + * - instance of #pj_ice_strans_cb structure to report callbacks to + * application.\n\n + * - while the #pj_ice_strans_create() call completes immediately, the + * initialization will be running in the background to gather the + * candidates (for example STUN and TURN candidates, if they are enabled + * in the #pj_ice_strans_cfg setting). Application will be notified when + * the initialization completes in the \a on_ice_complete callback of + * the #pj_ice_strans_cb structure (the \a op argument of this callback + * will be PJ_ICE_STRANS_OP_INIT).\n\n + * - when media stream is to be started (for example, a call is to be + * started), create an ICE session by calling #pj_ice_strans_init_ice().\n\n + * - the application now typically will need to communicate local ICE + * information to remote host. It can achieve this by using the following + * functions to query local ICE information: + * - #pj_ice_strans_get_ufrag_pwd() + * - #pj_ice_strans_enum_cands() + * - #pj_ice_strans_get_def_cand()\n + * The application may need to encode the above information as SDP.\n\n + * - when the application receives remote ICE information (for example, from + * the SDP received from remote), it can now start ICE negotiation, by + * calling #pj_ice_strans_start_ice(). This function requires some + * information about remote ICE agent such as remote ICE username fragment + * and password as well as array of remote candidates.\n\n + * - note that the PJNATH library does not work with SDP; application would + * need to encode and parse the SDP itself.\n\n + * - once ICE negotiation has been started, application will be notified + * about the completion in the \a on_ice_complete() callback of the + * #pj_ice_strans_cb.\n\n + * - at any time, application may send or receive data. However the ICE + * stream transport may not be able to send it depending on its current + * state. Before ICE negotiation is started, the data will be sent using + * default candidate of the component. After negotiation is completed, + * data will be sent using the candidate from the successful/nominated + * pair. The ICE stream transport may not be able to send data while + * negotiation is in progress.\n\n + * - application sends data by using #pj_ice_strans_sendto2(). Incoming + * data will be reported in \a on_rx_data() callback of the + * #pj_ice_strans_cb.\n\n + * - once the media session has finished (e.g. user hangs up the call), + * destroy the ICE session with #pj_ice_strans_stop_ice().\n\n + * - at this point, application may destroy the ICE stream transport itself, + * or let it run so that it can be reused to create other ICE session. + * The benefit of letting the ICE stream transport alive (without any + * session active) is to avoid delay with the initialization, howerver + * keeping the transport alive means the transport needs to keep the + * STUN binding open by using keep-alive and also TURN allocation alive, + * and this will consume power which is an important issue for mobile + * applications.\n\n + */ + +/** Deprecated API pj_ice_strans_sendto() due to its limitations. See + * below for more info and refer to + * https://github.com/pjsip/pjproject/issues/2229 for more details. + */ +#ifndef DEPRECATED_FOR_TICKET_2229 +#define DEPRECATED_FOR_TICKET_2229 0 +#endif + +/** Forward declaration for ICE stream transport. */ +typedef struct pj_ice_strans pj_ice_strans; + +/** Transport operation types to be reported on \a on_status() callback */ +typedef enum pj_ice_strans_op { + /** Initialization (candidate gathering) */ + PJ_ICE_STRANS_OP_INIT, + + /** Negotiation */ + PJ_ICE_STRANS_OP_NEGOTIATION, + + /** This operation is used to report failure in keep-alive operation. + * Currently it is only used to report TURN Refresh failure. + */ + PJ_ICE_STRANS_OP_KEEP_ALIVE, + + /** IP address change notification from STUN keep-alive operation. + */ + PJ_ICE_STRANS_OP_ADDR_CHANGE + +} pj_ice_strans_op; + +/** + * This structure contains callbacks that will be called by the + * ICE stream transport. + */ +typedef struct pj_ice_strans_cb { + /** + * This callback will be called when the ICE transport receives + * incoming packet from the sockets which is not related to ICE + * (for example, normal RTP/RTCP packet destined for application). + * + * @param ice_st The ICE stream transport. + * @param comp_id The component ID. + * @param pkt The packet. + * @param size Size of the packet. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the source address. + */ + void (*on_rx_data)(pj_ice_strans *ice_st, unsigned comp_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + + /** + * This callback is optional and will be called to notify the status of + * async send operations. + * + * @param ice_st The ICE stream transport. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + */ + void (*on_data_sent)(pj_ice_strans *sock, pj_ssize_t sent); + + /** + * An optional callback that will be called by the ICE transport when a + * valid pair has been found during ICE negotiation. + * + * @param ice_st The ICE stream transport. + */ + void (*on_valid_pair)(pj_ice_strans *ice_st); + + /** + * Callback to report status of various ICE operations. + * + * @param ice_st The ICE stream transport. + * @param op The operation which status is being reported. + * @param status Operation status. + */ + void (*on_ice_complete)(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); + + /** + * Callback to report a new ICE local candidate, e.g: after successful + * STUN Binding, after a successful TURN allocation. Only new candidates + * whose type is server reflexive or relayed will be notified via this + * callback. This callback also indicates end-of-candidate via parameter + * 'last'. + * + * Trickle ICE can use this callback to convey the new candidate + * to remote agent and monitor end-of-candidate indication. + * + * @param ice_st The ICE stream transport. + * @param cand The new local candidate, can be NULL when the last + * local candidate initialization failed/timeout. + * @param end_of_cand PJ_TRUE if this is the last of local candidate. + */ + void (*on_new_candidate)(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t end_of_cand); + +} pj_ice_strans_cb; + +/** + * STUN and local transport settings for ICE stream transport. + */ +typedef struct pj_ice_strans_stun_cfg { + /** + * Address family, IPv4 or IPv6. + * + * Default value is pj_AF_INET() (IPv4) + */ + int af; + + /** + * Optional configuration for STUN transport. The default + * value will be initialized with #pj_stun_sock_cfg_default(). + */ + pj_stun_sock_cfg cfg; + + /** + * Maximum number of host candidates to be added. If the + * value is zero, no host candidates will be added. + * + * Default: 64 + */ + unsigned max_host_cands; + + /** + * Include loopback addresses in the host candidates. + * + * Default: PJ_FALSE + */ + pj_bool_t loop_addr; + + /** + * Specify the STUN server domain or hostname or IP address. + * If DNS SRV resolution is required, application must fill + * in this setting with the domain name of the STUN server + * and set the resolver instance in the \a resolver field. + * Otherwise if the \a resolver setting is not set, this + * field will be resolved with hostname resolution and in + * this case the \a port field must be set. + * + * The \a port field should also be set even when DNS SRV + * resolution is used, in case the DNS SRV resolution fails. + * + * When this field is empty, STUN mapped address resolution + * will not be performed. In this case only ICE host candidates + * will be added to the ICE transport, unless if \a no_host_cands + * field is set. In this case, both host and srflx candidates + * are disabled. + * + * If there are more than one STUN candidates per ICE stream + * transport component, the standard recommends to use the same + * STUN server for all STUN candidates. + * + * The default value is empty. + */ + pj_str_t server; + + /** + * The port number of the STUN server, when \a server + * field specifies a hostname rather than domain name. This + * field should also be set even when the \a server + * specifies a domain name, to allow DNS SRV resolution + * to fallback to DNS A/AAAA resolution when the DNS SRV + * resolution fails. + * + * The default value is PJ_STUN_PORT. + */ + pj_uint16_t port; + + /** + * Ignore STUN resolution error and proceed with just local + * addresses. + * + * The default is PJ_FALSE + */ + pj_bool_t ignore_stun_error; + +} pj_ice_strans_stun_cfg; + +/** + * TURN transport settings for ICE stream transport. + */ +typedef struct pj_ice_strans_turn_cfg { + /** + * Address family, IPv4 or IPv6. + * + * Default value is pj_AF_INET() (IPv4) + */ + int af; + + /** + * Optional TURN socket settings. The default values will be + * initialized by #pj_turn_sock_cfg_default(). This contains + * settings such as QoS. + */ + pj_turn_sock_cfg cfg; + + /** + * Specify the TURN server domain or hostname or IP address. + * If DNS SRV resolution is required, application must fill + * in this setting with the domain name of the TURN server + * and set the resolver instance in the \a resolver field. + * Otherwise if the \a resolver setting is not set, this + * field will be resolved with hostname resolution and in + * this case the \a port field must be set. + * + * The \a port field should also be set even when DNS SRV + * resolution is used, in case the DNS SRV resolution fails. + * + * When this field is empty, relay candidate will not be + * created. + * + * The default value is empty. + */ + pj_str_t server; + + /** + * The port number of the TURN server, when \a server + * field specifies a hostname rather than domain name. This + * field should also be set even when the \a server + * specifies a domain name, to allow DNS SRV resolution + * to fallback to DNS A/AAAA resolution when the DNS SRV + * resolution fails. + * + * Default is zero. + */ + pj_uint16_t port; + + /** + * Type of connection to the TURN server. + * + * Default is PJ_TURN_TP_UDP. + */ + pj_turn_tp_type conn_type; + + /** + * Credential to be used for the TURN session. This setting + * is mandatory. + * + * Default is to have no credential. + */ + pj_stun_auth_cred auth_cred; + + /** + * Optional TURN Allocate parameter. The default value will be + * initialized by #pj_turn_alloc_param_default(). + */ + pj_turn_alloc_param alloc_param; + +} pj_ice_strans_turn_cfg; + +/** + * This structure describes ICE stream transport configuration. Application + * should initialize the structure by calling #pj_ice_strans_cfg_default() + * before changing the settings. + */ +typedef struct pj_ice_strans_cfg { + /** + * The address family which will be used as the default address + * in the SDP offer. Setting this to pj_AF_UNSPEC() means that + * the address family will not be considered during the process + * of default candidate selection. + * + * The default value is pj_AF_INET() (IPv4). + */ + int af; + + /** + * STUN configuration which contains the timer heap and + * ioqueue instance to be used, and STUN retransmission + * settings. This setting is mandatory. + * + * The default value is all zero. Application must initialize + * this setting with #pj_stun_config_init(). + */ + pj_stun_config stun_cfg; + + /** + * DNS resolver to be used to resolve servers. If DNS SRV + * resolution is required, the resolver must be set. + * + * The default value is NULL. + */ + pj_dns_resolver *resolver; + + /** + * This contains various STUN session options. Once the ICE stream + * transport is created, application may also change the options + * with #pj_ice_strans_set_options(). + */ + pj_ice_sess_options opt; + + /** + * Warning: this field is deprecated, please use \a stun_tp field instead. + * To maintain backward compatibility, if \a stun_tp_cnt is zero, the + * value of this field will be copied to \a stun_tp. + * + * STUN and local transport settings. This specifies the settings + * for local UDP socket address and STUN resolved address. + */ + pj_ice_strans_stun_cfg stun; + + /** + * Number of STUN transports. + * + * Default: 0 + */ + unsigned stun_tp_cnt; + + /** + * STUN and local transport settings. This specifies the settings + * for local UDP socket address and STUN resolved address. + */ + pj_ice_strans_stun_cfg stun_tp[PJ_ICE_MAX_STUN]; + + /** + * Warning: this field is deprecated, please use \a turn_tp field instead. + * To maintain backward compatibility, if \a turn_tp_cnt is zero, the + * value of this field will be copied to \a turn_tp. + * + * TURN transport settings. + */ + pj_ice_strans_turn_cfg turn; + + /** + * Number of TURN transports. + * + * Default: 0 + */ + unsigned turn_tp_cnt; + + /** + * TURN transport settings. + */ + pj_ice_strans_turn_cfg turn_tp[PJ_ICE_MAX_TURN]; + + /** + * Number of send buffers used for pj_ice_strans_sendto2(). If the send + * buffers are full, pj_ice_strans_sendto()/sendto2() will return + * PJ_EBUSY. + * + * Set this to 0 to disable buffering (then application will have to + * maintain the buffer passed to pj_ice_strans_sendto()/sendto2() + * until it has been sent). + * + * Default: 4 + */ + unsigned num_send_buf; + + /** + * Buffer size used for pj_ice_strans_sendto2(). + * + * Default: 0 (size determined by the size of the first packet sent). + */ + unsigned send_buf_size; + + /** + * Component specific settings, which will override the settings in + * the STUN and TURN settings above. For example, setting the QoS + * parameters here allows the application to have different QoS + * traffic type for RTP and RTCP component. + */ + struct { + /** + * QoS traffic type to be set on this transport. When application + * wants to apply QoS tagging to the transport, it's preferable to + * set this field rather than \a qos_param fields since this is + * more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a + * lower level operation than setting the \a qos_type field and + * may not be supported on all platforms. + * + * By default all settings in this structure are disabled. + */ + pj_qos_params qos_params; + + /** + * Specify target value for socket receive buffer size. It will be + * applied using setsockopt(). When it fails to set the specified + * size, it will try with lower value until the highest possible is + * successfully set. + * + * When this is set to zero, this component will apply socket receive + * buffer size settings specified in STUN and TURN socket config + * above, i.e: \a stun::cfg::so_rcvbuf_size and + * \a turn::cfg::so_rcvbuf_size. Otherwise, this setting will be + * applied to STUN and TURN sockets for this component, overriding + * the setting specified in STUN/TURN socket config. + * + * Default: 0 + */ + unsigned so_rcvbuf_size; + + /** + * Specify target value for socket send buffer size. It will be + * applied using setsockopt(). When it fails to set the specified + * size, it will try with lower value until the highest possible is + * successfully set. + * + * When this is set to zero, this component will apply socket send + * buffer size settings specified in STUN and TURN socket config + * above, i.e: \a stun::cfg::so_sndbuf_size and + * \a turn::cfg::so_sndbuf_size. Otherwise, this setting will be + * applied to STUN and TURN sockets for this component, overriding + * the setting specified in STUN/TURN socket config. + * + * Default: 0 + */ + unsigned so_sndbuf_size; + + } comp[PJ_ICE_MAX_COMP]; + +} pj_ice_strans_cfg; + +/** + * ICE stream transport's state. + */ +typedef enum pj_ice_strans_state { + /** + * ICE stream transport is not created. + */ + PJ_ICE_STRANS_STATE_NULL, + + /** + * ICE candidate gathering process is in progress. + */ + PJ_ICE_STRANS_STATE_INIT, + + /** + * ICE stream transport initialization/candidate gathering process is + * complete, ICE session may be created on this stream transport. + */ + PJ_ICE_STRANS_STATE_READY, + + /** + * New session has been created and the session is ready. + */ + PJ_ICE_STRANS_STATE_SESS_READY, + + /** + * ICE negotiation is in progress. + */ + PJ_ICE_STRANS_STATE_NEGO, + + /** + * ICE negotiation has completed successfully and media is ready + * to be used. + */ + PJ_ICE_STRANS_STATE_RUNNING, + + /** + * ICE negotiation has completed with failure. + */ + PJ_ICE_STRANS_STATE_FAILED + +} pj_ice_strans_state; + +/** + * Initialize ICE transport configuration with default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg); + +/** + * Initialize ICE STUN transport configuration with default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_ice_strans_stun_cfg_default(pj_ice_strans_stun_cfg *cfg); + +/** + * Initialize ICE TURN transport configuration with default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_ice_strans_turn_cfg_default(pj_ice_strans_turn_cfg *cfg); + +/** + * Copy configuration. + * + * @param pool Pool. + * @param dst Destination. + * @param src Source. + */ +PJ_DECL(void) pj_ice_strans_cfg_copy(pj_pool_t *pool, pj_ice_strans_cfg *dst, const pj_ice_strans_cfg *src); + +/** + * Create and initialize the ICE stream transport with the specified + * parameters. + * + * @param name Optional name for logging identification. + * @param cfg Configuration. + * @param comp_cnt Number of components. + * @param user_data Arbitrary user data to be associated with this + * ICE stream transport. + * @param cb Callback. + * @param p_ice_st Pointer to receive the ICE stream transport + * instance. + * + * @return PJ_SUCCESS if ICE stream transport is created + * successfully. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_create(const char *name, const pj_ice_strans_cfg *cfg, unsigned comp_cnt, void *user_data, + const pj_ice_strans_cb *cb, pj_ice_strans **p_ice_st); + +/** + * Get ICE session state. + * + * @param ice_st The ICE stream transport. + * + * @return ICE session state. + */ +PJ_DECL(pj_ice_strans_state) pj_ice_strans_get_state(pj_ice_strans *ice_st); + +/** + * Get string representation of ICE state. + * + * @param state ICE stream transport state. + * + * @return String. + */ +PJ_DECL(const char *) pj_ice_strans_state_name(pj_ice_strans_state state); + +/** + * Destroy the ICE stream transport. This will destroy the ICE session + * inside the ICE stream transport, close all sockets and release all + * other resources. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st); + +/** + * Get the user data associated with the ICE stream transport. + * + * @param ice_st The ICE stream transport. + * + * @return The user data. + */ +PJ_DECL(void *) pj_ice_strans_get_user_data(pj_ice_strans *ice_st); + +/** + * Get the value of various options of the ICE stream transport. + * + * @param ice_st The ICE stream transport. + * @param opt The options to be initialized with the values + * from the ICE stream transport. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st, pj_ice_sess_options *opt); + +/** + * Specify various options for this ICE stream transport. Application + * should call #pj_ice_strans_get_options() to initialize the options + * with their default values. + * + * @param ice_st The ICE stream transport. + * @param opt Options to be applied to this ICE stream transport. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st, const pj_ice_sess_options *opt); + +/** + * Update number of components of the ICE stream transport. This can only + * reduce the number of components from the initial value specified in + * pj_ice_strans_create() and before ICE session is initialized. + * + * @param ice_st The ICE stream transport. + * @param comp_cnt Number of components. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_update_comp_cnt(pj_ice_strans *ice_st, unsigned comp_cnt); + +/** + * Get the group lock for this ICE stream transport. + * + * @param ice_st The ICE stream transport. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_ice_strans_get_grp_lock(pj_ice_strans *ice_st); + +/** + * Initialize the ICE session in the ICE stream transport. + * When application is about to send an offer containing ICE capability, + * or when it receives an offer containing ICE capability, it must + * call this function to initialize the internal ICE session. This would + * register all transport address aliases for each component to the ICE + * session as candidates. Then application can enumerate all local + * candidates by calling #pj_ice_strans_enum_cands(), and encode these + * candidates in the SDP to be sent to remote agent. + * + * @param ice_st The ICE stream transport. + * @param role ICE role. + * @param local_ufrag Optional local username fragment. + * @param local_passwd Optional local password. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_init_ice(pj_ice_strans *ice_st, pj_ice_sess_role role, const pj_str_t *local_ufrag, + const pj_str_t *local_passwd); + +/** + * Check if the ICE stream transport has the ICE session created. The + * ICE session is created with #pj_ice_strans_init_ice(). + * + * @param ice_st The ICE stream transport. + * + * @return PJ_TRUE if #pj_ice_strans_init_ice() has been + * called. + */ +PJ_DECL(pj_bool_t) pj_ice_strans_has_sess(pj_ice_strans *ice_st); + +/** + * Check if ICE negotiation is still running. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_TRUE if ICE session has been created and ICE + * negotiation negotiation is in progress. + */ +PJ_DECL(pj_bool_t) pj_ice_strans_sess_is_running(pj_ice_strans *ice_st); + +/** + * Check if ICE negotiation has completed. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_TRUE if ICE session has been created and the + * negotiation is complete. + */ +PJ_DECL(pj_bool_t) pj_ice_strans_sess_is_complete(pj_ice_strans *ice_st); + +/** + * Get the current/running component count. If ICE negotiation has not + * been started, the number of components will be equal to the number + * when the ICE stream transport was created. Once negotiation been + * started, the number of components will be the lowest number of + * component between local and remote agents. + * + * @param ice_st The ICE stream transport. + * + * @return The running number of components. + */ +PJ_DECL(unsigned) pj_ice_strans_get_running_comp_cnt(pj_ice_strans *ice_st); + +/** + * Get the ICE username fragment and password of the ICE session. The + * local username fragment and password can only be retrieved once ICE + * session has been created with #pj_ice_strans_init_ice(). The remote + * username fragment and password can only be retrieved once ICE session + * has been started with #pj_ice_strans_start_ice(). + * + * Note that the string returned by this function is only valid throughout + * the duration of the ICE session, and the application must not modify + * these strings. Once the ICE session has been stopped with + * #pj_ice_strans_stop_ice(), the pointer in the string will no longer be + * valid. + * + * @param ice_st The ICE stream transport. + * @param loc_ufrag Optional pointer to receive ICE username fragment + * of local endpoint from the ICE session. + * @param loc_pwd Optional pointer to receive ICE password of local + * endpoint from the ICE session. + * @param rem_ufrag Optional pointer to receive ICE username fragment + * of remote endpoint from the ICE session. + * @param rem_pwd Optional pointer to receive ICE password of remote + * endpoint from the ICE session. + * + * @return PJ_SUCCESS if the strings have been retrieved + * successfully. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_get_ufrag_pwd(pj_ice_strans *ice_st, pj_str_t *loc_ufrag, pj_str_t *loc_pwd, pj_str_t *rem_ufrag, + pj_str_t *rem_pwd); + +/** + * Get the number of local candidates for the specified component ID. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * + * @return The number of candidates. + */ +PJ_DECL(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st, unsigned comp_id); + +/** + * Enumerate the local candidates for the specified component. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param count On input, it specifies the maximum number of + * elements. On output, it will be filled with + * the number of candidates copied to the + * array. + * @param cand Array of candidates. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_enum_cands(pj_ice_strans *ice_st, unsigned comp_id, unsigned *count, pj_ice_sess_cand cand[]); + +/** + * Get the default candidate for the specified component. When this + * function is called before ICE negotiation completes, the default + * candidate is selected according to local preference criteria. When + * this function is called after ICE negotiation completes, the + * default candidate is the candidate that forms the valid pair. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param cand Pointer to receive the default candidate + * information. + */ +PJ_DECL(pj_status_t) pj_ice_strans_get_def_cand(pj_ice_strans *ice_st, unsigned comp_id, pj_ice_sess_cand *cand); + +/** + * Get the current ICE role. ICE session must have been initialized + * before this function can be called. + * + * @param ice_st The ICE stream transport. + * + * @return Current ICE role. + */ +PJ_DECL(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st); + +/** + * Change session role. This happens for example when ICE session was + * created with controlled role when receiving an offer, but it turns out + * that the offer contains "a=ice-lite" attribute when the SDP gets + * inspected. ICE session must have been initialized before this function + * can be called. + * + * @param ice_st The ICE stream transport. + * @param new_role The new role to be set. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_change_role(pj_ice_strans *ice_st, pj_ice_sess_role new_role); + +/** + * Start ICE connectivity checks. This function can only be called + * after the ICE session has been created in the ICE stream transport + * with #pj_ice_strans_init_ice(). + * + * This function must be called once application has received remote + * candidate list (typically from the remote SDP). This function pairs + * local candidates with remote candidates, and starts ICE connectivity + * checks. The ICE session/transport will then notify the application + * via the callback when ICE connectivity checks completes, either + * successfully or with failure. + * + * @param ice_st The ICE stream transport. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rcand_cnt Number of remote candidates in the array. + * @param rcand Remote candidates array. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_start_ice(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rcand_cnt, const pj_ice_sess_cand rcand[]); + +/** + * Update check list after receiving new remote ICE candidates or after + * new local ICE candidates are found and conveyed to remote. This function + * can also be called after receiving end of candidate indication from + * either remote or local agent. + * + * This function is only applicable when trickle ICE is not disabled and + * after ICE session has been created using pj_ice_strans_init_ice(). + * + * @param ice_st The ICE stream transport. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rcand_cnt Number of new remote candidates in the array. + * @param rcand New remote candidates array. + * @param rcand_end Set to PJ_TRUE if remote has signalled + * end-of-candidate. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_update_check_list(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rcand_cnt, const pj_ice_sess_cand rcand[], pj_bool_t rcand_end); + +/** + * Retrieve the candidate pair that has been nominated and successfully + * checked for the specified component. If ICE negotiation is still in + * progress or it has failed, this function will return NULL. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * + * @return The valid pair as ICE checklist structure if the + * pair exist. + */ +PJ_DECL(const pj_ice_sess_check *) +pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st, unsigned comp_id); + +/** + * Stop and destroy the ICE session inside this media transport. Application + * needs to call this function once the media session is over (the call has + * been disconnected). + * + * Application MAY reuse this ICE stream transport for subsequent calls. + * In this case, it must call #pj_ice_strans_stop_ice() when the call is + * disconnected, and reinitialize the ICE stream transport for subsequent + * call with #pj_ice_strans_init_ice()/#pj_ice_strans_start_ice(). In this + * case, the ICE stream transport will maintain the internal sockets and + * continue to send STUN keep-alive packets and TURN Refresh request to + * keep the NAT binding/TURN allocation open and to detect change in STUN + * mapped address. + * + * If application does not want to reuse the ICE stream transport for + * subsequent calls, it must call #pj_ice_strans_destroy() to destroy the + * ICE stream transport altogether. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st); + +#if !DEPRECATED_FOR_TICKET_2229 +/** + * Send outgoing packet using this transport. + * Application can send data (normally RTP or RTCP packets) at any time + * by calling this function. This function takes a destination + * address as one of the arguments, and this destination address should + * be taken from the default transport address of the component (that is + * the address in SDP c= and m= lines, or in a=rtcp attribute). + * If ICE negotiation is in progress, this function will send the data + * to the destination address. Otherwise if ICE negotiation has completed + * successfully, this function will send the data to the nominated remote + * address, as negotiated by ICE. + * + * Limitations: + * 1. This function cannot inform the app whether the data has been sent, + * or currently still pending. + * 2. In case that the data is still pending, the application has no way + * of knowing the status of the send operation (whether it's a success + * or failure). + * Due to these limitations, the API is deprecated and will be removed + * in the future. + * + * Note that application shouldn't mix using pj_ice_strans_sendto() and + * pj_ice_strans_sendto2() to avoid inconsistent calling of + * on_data_sent() callback. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param data The data or packet to be sent. + * @param data_len Size of data or packet, in bytes. + * @param dst_addr The destination address. + * @param dst_addr_len Length of destination address. + * + * @return PJ_SUCCESS if data has been sent, or will be sent + * later. No callback will be called. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_sendto(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len); +#endif + +/** + * Send outgoing packet using this transport. + * Application can send data (normally RTP or RTCP packets) at any time + * by calling this function. This function takes a destination + * address as one of the arguments, and this destination address should + * be taken from the default transport address of the component (that is + * the address in SDP c= and m= lines, or in a=rtcp attribute). + * If ICE negotiation is in progress, this function will try to send the data + * via any valid candidate pair (which has passed ICE connectivity test). + * If ICE negotiation has completed successfully, this function will send + * the data to the nominated remote address, as negotiated by ICE. + * If the ICE negotiation fails or valid candidate pair is not yet available, + * this function will send the data using default candidate to the specified + * destination address. + * + * Note that application shouldn't mix using pj_ice_strans_sendto() and + * pj_ice_strans_sendto2() to avoid inconsistent calling of + * on_data_sent() callback. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param data The data or packet to be sent. + * @param data_len Size of data or packet, in bytes. + * @param dst_addr The destination address. + * @param dst_addr_len Length of destination address. + * + * @return PJ_SUCCESS if data has been sent, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_sendto2(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_ICE_STRANS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h new file mode 100755 index 000000000..ab95bb5eb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_NAT_DETECT_H__ +#define __PJNATH_NAT_DETECT_H__ + +/** + * @file ice_session.h + * @brief ICE session management + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJNATH_NAT_DETECT NAT Classification/Detection Tool + * @brief NAT Classification/Detection Tool + * @ingroup PJNATH + * @{ + * + * This module provides one function to perform NAT classification and + * detection. NAT type detection is performed by calling + * #pj_stun_detect_nat_type() function. + */ + +/** + * This enumeration describes the NAT types, as specified by RFC 3489 + * Section 5, NAT Variations. + */ +typedef enum pj_stun_nat_type { + /** + * NAT type is unknown because the detection has not been performed. + */ + PJ_STUN_NAT_TYPE_UNKNOWN, + + /** + * NAT type is unknown because there is failure in the detection + * process, possibly because server does not support RFC 3489. + */ + PJ_STUN_NAT_TYPE_ERR_UNKNOWN, + + /** + * This specifies that the client has open access to Internet (or + * at least, its behind a firewall that behaves like a full-cone NAT, + * but without the translation) + */ + PJ_STUN_NAT_TYPE_OPEN, + + /** + * This specifies that communication with server has failed, probably + * because UDP packets are blocked. + */ + PJ_STUN_NAT_TYPE_BLOCKED, + + /** + * Firewall that allows UDP out, and responses have to come back to + * the source of the request (like a symmetric NAT, but no + * translation. + */ + PJ_STUN_NAT_TYPE_SYMMETRIC_UDP, + + /** + * A full cone NAT is one where all requests from the same internal + * IP address and port are mapped to the same external IP address and + * port. Furthermore, any external host can send a packet to the + * internal host, by sending a packet to the mapped external address. + */ + PJ_STUN_NAT_TYPE_FULL_CONE, + + /** + * A symmetric NAT is one where all requests from the same internal + * IP address and port, to a specific destination IP address and port, + * are mapped to the same external IP address and port. If the same + * host sends a packet with the same source address and port, but to + * a different destination, a different mapping is used. Furthermore, + * only the external host that receives a packet can send a UDP packet + * back to the internal host. + */ + PJ_STUN_NAT_TYPE_SYMMETRIC, + + /** + * A restricted cone NAT is one where all requests from the same + * internal IP address and port are mapped to the same external IP + * address and port. Unlike a full cone NAT, an external host (with + * IP address X) can send a packet to the internal host only if the + * internal host had previously sent a packet to IP address X. + */ + PJ_STUN_NAT_TYPE_RESTRICTED, + + /** + * A port restricted cone NAT is like a restricted cone NAT, but the + * restriction includes port numbers. Specifically, an external host + * can send a packet, with source IP address X and source port P, + * to the internal host only if the internal host had previously sent + * a packet to IP address X and port P. + */ + PJ_STUN_NAT_TYPE_PORT_RESTRICTED + +} pj_stun_nat_type; + +/** + * This structure contains the result of NAT classification function. + */ +typedef struct pj_stun_nat_detect_result { + /** + * Status of the detection process. If this value is not PJ_SUCCESS, + * the detection has failed and \a nat_type field will contain + * PJ_STUN_NAT_TYPE_UNKNOWN. + */ + pj_status_t status; + + /** + * The text describing the status, if the status is not PJ_SUCCESS. + */ + const char *status_text; + + /** + * This contains the NAT type as detected by the detection procedure. + * This value is only valid when the \a status is PJ_SUCCESS. + */ + pj_stun_nat_type nat_type; + + /** + * Text describing that NAT type. + */ + const char *nat_type_name; + +} pj_stun_nat_detect_result; + +/** + * Type of callback to be called when the NAT detection function has + * completed. + */ +typedef void pj_stun_nat_detect_cb(void *user_data, const pj_stun_nat_detect_result *res); + +/** + * Get the NAT name from the specified NAT type. + * + * @param type NAT type. + * + * @return NAT name. + */ +PJ_DECL(const char *) pj_stun_get_nat_name(pj_stun_nat_type type); + +/** + * Perform NAT classification function according to the procedures + * specified in RFC 3489. Once this function returns successfully, + * the procedure will run in the "background" and will complete + * asynchronously. Application can register a callback to be notified + * when such detection has completed. + * + * See also #pj_stun_detect_nat_type2() which supports IPv6. + * + * @param server STUN server address. + * @param stun_cfg A structure containing various STUN configurations, + * such as the ioqueue and timer heap instance used + * to receive network I/O and timer events. + * @param user_data Application data, which will be returned back + * in the callback. + * @param cb Callback to be registered to receive notification + * about detection result. + * + * @return If this function returns PJ_SUCCESS, the procedure + * will complete asynchronously and callback will be + * called when it completes. For other return + * values, it means that an error has occured and + * the procedure did not start. + */ +PJ_DECL(pj_status_t) +pj_stun_detect_nat_type(const pj_sockaddr_in *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb); + +/** + * Variant of #pj_stun_detect_nat_type() that supports IPv6. + * + * @param server STUN server address. + * @param stun_cfg A structure containing various STUN configurations, + * such as the ioqueue and timer heap instance used + * to receive network I/O and timer events. + * @param user_data Application data, which will be returned back + * in the callback. + * @param cb Callback to be registered to receive notification + * about detection result. + * + * @return If this function returns PJ_SUCCESS, the procedure + * will complete asynchronously and callback will be + * called when it completes. For other return + * values, it means that an error has occured and + * the procedure did not start. + */ +PJ_DECL(pj_status_t) +pj_stun_detect_nat_type2(const pj_sockaddr *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_NAT_DETECT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h new file mode 100755 index 000000000..c454b34ec --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_AUTH_H__ +#define __PJNATH_STUN_AUTH_H__ + +/** + * @file stun_auth.h + * @brief STUN authentication. + */ + +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_AUTH STUN Authentication + * @brief STUN authentication helper + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/** + * Type of authentication. + */ +typedef enum pj_stun_auth_type { + /** + * No authentication. + */ + PJ_STUN_AUTH_NONE = 0, + + /** + * Authentication using short term credential. + */ + PJ_STUN_AUTH_SHORT_TERM = 1, + + /** + * Authentication using long term credential. + */ + PJ_STUN_AUTH_LONG_TERM = 2 + +} pj_stun_auth_type; + +/** + * Type of authentication data in the credential. + */ +typedef enum pj_stun_auth_cred_type { + /** + * The credential data contains a static credential to be matched + * against the credential in the message. A static credential can be + * used as both client side or server side authentication. + */ + PJ_STUN_AUTH_CRED_STATIC, + + /** + * The credential data contains callbacks to be called to verify the + * credential in the message. A dynamic credential is suitable when + * performing server side authentication where server does not know + * in advance the identity of the user requesting authentication. + */ + PJ_STUN_AUTH_CRED_DYNAMIC + +} pj_stun_auth_cred_type; + +/** + * Type of encoding applied to the password stored in the credential. + */ +typedef enum pj_stun_passwd_type { + /** + * Plain text password. + */ + PJ_STUN_PASSWD_PLAIN = 0, + + /** + * Hashed password, valid for long term credential only. The hash value + * of the password is calculated as MD5(USERNAME ":" REALM ":" PASSWD) + * with all quotes removed from the username and realm values. + */ + PJ_STUN_PASSWD_HASHED = 1 + +} pj_stun_passwd_type; + +/** + * This structure contains the descriptions needed to perform server side + * authentication. Depending on the \a type set in the structure, application + * may specify a static username/password combination, or to have callbacks + * called by the function to authenticate the credential dynamically. + */ +typedef struct pj_stun_auth_cred { + /** + * The type of authentication information in this structure. + */ + pj_stun_auth_cred_type type; + + /** + * This union contains the authentication data. + */ + union { + /** + * This structure contains static data for performing authentication. + * A non-empty realm indicates whether short term or long term + * credential is used. + */ + struct { + /** + * If not-empty, it indicates that this is a long term credential. + */ + pj_str_t realm; + + /** + * The username of the credential. + */ + pj_str_t username; + + /** + * Data type to indicate the type of password in the \a data field. + */ + pj_stun_passwd_type data_type; + + /** + * The data, which depends depends on the value of \a data_type + * field. When \a data_type is zero, this field will contain the + * plaintext password. + */ + pj_str_t data; + + /** + * Optional NONCE. + */ + pj_str_t nonce; + + } static_cred; + + /** + * This structure contains callback to be called by the framework + * to authenticate the incoming message. + */ + struct { + /** + * User data which will be passed back to callback functions. + */ + void *user_data; + + /** + * This callback is called by pj_stun_verify_credential() when + * server needs to challenge the request with 401 response. + * + * @param user_data The user data as specified in the credential. + * @param pool Pool to allocate memory. + * @param realm On return, the function should fill in with + * realm if application wants to use long term + * credential. Otherwise application should set + * empty string for the realm. + * @param nonce On return, if application wants to use long + * term credential, it MUST fill in the nonce + * with some value. Otherwise if short term + * credential is wanted, it MAY set this value. + * If short term credential is wanted and the + * application doesn't want to include NONCE, + * then it must set this to empty string. + * + * @return The callback should return PJ_SUCCESS, or + * otherwise response message will not be + * created. + */ + pj_status_t (*get_auth)(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce); + + /** + * Get the credential to be put in outgoing request. + * + * @param msg The outgoing message where the credential is + * to be applied. + * @param user_data The user data as specified in the credential. + * @param pool Pool where the callback can allocate memory + * to fill in the credential. + * @param realm On return, the callback may specify the realm + * if long term credential is desired, otherwise + * this string must be set to empty. + * @param username On return, the callback must fill in with the + * username. + * @param nonce On return, the callback may optionally fill in + * this argument with NONCE value if desired, + * otherwise this argument must be set to empty. + * @param data_type On return, the callback must set this argument + * with the type of password in the data argument. + * @param data On return, the callback must set this with + * the password, encoded according to data_type + * argument. + * + * @return The callback must return PJ_SUCCESS, otherwise + * the message transmission will be cancelled. + */ + pj_status_t (*get_cred)(const pj_stun_msg *msg, void *user_data, pj_pool_t *pool, pj_str_t *realm, + pj_str_t *username, pj_str_t *nonce, pj_stun_passwd_type *data_type, + pj_str_t *data); + + /** + * Get the password for the specified username. This function + * is also used to check whether the username is valid. + * + * @param msg The STUN message where the password will be + * applied to. + * @param user_data The user data as specified in the credential. + * @param realm The realm as specified in the message. + * @param username The username as specified in the message. + * @param pool Pool to allocate memory when necessary. + * @param data_type On return, application should fill up this + * argument with the type of data (which should + * be zero if data is a plaintext password). + * @param data On return, application should fill up this + * argument with the password according to + * data_type. + * + * @return The callback should return PJ_SUCCESS if + * username has been successfully verified + * and password was obtained. If non-PJ_SUCCESS + * is returned, it is assumed that the + * username is not valid. + */ + pj_status_t (*get_password)(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, pj_pool_t *pool, pj_stun_passwd_type *data_type, + pj_str_t *data); + + /** + * This callback will be called to verify that the NONCE given + * in the message can be accepted. If this callback returns + * PJ_FALSE, 438 (Stale Nonce) response will be created. + * + * This callback is optional. + * + * @param msg The STUN message where the nonce was received. + * @param user_data The user data as specified in the credential. + * @param realm The realm as specified in the message. + * @param username The username as specified in the message. + * @param nonce The nonce to be verified. + * + * @return The callback MUST return non-zero if the + * NONCE can be accepted. + */ + pj_bool_t (*verify_nonce)(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, const pj_str_t *nonce); + + } dyn_cred; + + } data; + +} pj_stun_auth_cred; + +/** + * This structure contains the credential information that is found and + * used to authenticate incoming requests. Application may use this + * information when generating authentication for the outgoing response. + */ +typedef struct pj_stun_req_cred_info { + /** + * The REALM value found in the incoming request. If short term + * credential is used, the value will be empty. + */ + pj_str_t realm; + + /** + * The USERNAME value found in the incoming request. + */ + pj_str_t username; + + /** + * Optional NONCE. + */ + pj_str_t nonce; + + /** + * Authentication key that was used to authenticate the incoming + * request. This key is created with #pj_stun_create_key(), and + * it can be used to encode the credential of the outgoing + * response. + */ + pj_str_t auth_key; + +} pj_stun_req_cred_info; + +/** + * Duplicate authentication credential. + * + * @param pool Pool to be used to allocate memory. + * @param dst Destination credential. + * @param src Source credential. + */ +PJ_DECL(void) pj_stun_auth_cred_dup(pj_pool_t *pool, pj_stun_auth_cred *dst, const pj_stun_auth_cred *src); + +/** + * Duplicate request credential. + * + * @param pool Pool to be used to allocate memory. + * @param dst Destination credential. + * @param src Source credential. + */ +PJ_DECL(void) pj_stun_req_cred_info_dup(pj_pool_t *pool, pj_stun_req_cred_info *dst, const pj_stun_req_cred_info *src); + +/** + * Create authentication key to be used for encoding the message with + * MESSAGE-INTEGRITY. If short term credential is used (i.e. the realm + * argument is NULL or empty), the key will be copied from the password. + * If long term credential is used, the key will be calculated from the + * MD5 hash of the realm, username, and password. + * + * @param pool Pool to allocate memory for the key. + * @param key String to receive the key. + * @param realm The realm of the credential, if long term credential + * is to be used. If short term credential is wanted, + * application can put NULL or empty string here. + * @param username The username. + * @param data_type Password encoding. + * @param data The password. + */ +PJ_DECL(void) +pj_stun_create_key(pj_pool_t *pool, pj_str_t *key, const pj_str_t *realm, const pj_str_t *username, + pj_stun_passwd_type data_type, const pj_str_t *data); + +/** + * Verify credential in the STUN request. Note that before calling this + * function, application must have checked that the message contains + * PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute by calling pj_stun_msg_find_attr() + * function, because this function will reject the message with 401 error + * if it doesn't contain PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute. + * + * @param pkt The original packet which has been parsed into + * the message. This packet MUST NOT have been modified + * after the parsing. + * @param pkt_len The length of the packet. + * @param msg The parsed message to be verified. + * @param cred Pointer to credential to be used to authenticate + * the message. + * @param pool If response is to be created, then memory will + * be allocated from this pool. + * @param info Optional pointer to receive authentication information + * found in the request and the credential that is used + * to authenticate the request. + * @param p_response Optional pointer to receive the response message + * then the credential in the request fails to + * authenticate. + * + * @return PJ_SUCCESS if credential is verified successfully. + * If the verification fails and \a p_response is not + * NULL, an appropriate response will be returned in + * \a p_response. + */ +PJ_DECL(pj_status_t) +pj_stun_authenticate_request(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, pj_stun_auth_cred *cred, + pj_pool_t *pool, pj_stun_req_cred_info *info, pj_stun_msg **p_response); + +/** + * Determine if STUN message can be authenticated. Some STUN error + * responses cannot be authenticated since they cannot contain STUN + * MESSAGE-INTEGRITY attribute. STUN Indication messages also cannot + * be authenticated. + * + * @param msg The STUN message. + * + * @return Non-zero if the STUN message can be authenticated. + */ +PJ_DECL(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg); + +/** + * Verify credential in the STUN response. Note that before calling this + * function, application must have checked that the message contains + * PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute by calling pj_stun_msg_find_attr() + * function, because otherwise this function will report authentication + * failure. + * + * @param pkt The original packet which has been parsed into + * the message. This packet MUST NOT have been modified + * after the parsing. + * @param pkt_len The length of the packet. + * @param msg The parsed message to be verified. + * @param key Authentication key to calculate MESSAGE-INTEGRITY + * value. Application can create this key by using + * #pj_stun_create_key() function. + * + * @return PJ_SUCCESS if credential is verified successfully. + */ +PJ_DECL(pj_status_t) +pj_stun_authenticate_response(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, const pj_str_t *key); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_AUTH_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h new file mode 100755 index 000000000..f70cbbf34 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_CONFIG_H__ +#define __PJNATH_STUN_CONFIG_H__ + +/** + * @file stun_config.h + * @brief STUN endpoint. + */ + +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_CONFIG STUN Config + * @brief STUN config + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/** + * STUN configuration. + */ +typedef struct pj_stun_config { + /** + * Pool factory to be used. + */ + pj_pool_factory *pf; + + /** + * Ioqueue. + */ + pj_ioqueue_t *ioqueue; + + /** + * Timer heap instance. + */ + pj_timer_heap_t *timer_heap; + + /** + * Options. + */ + unsigned options; + + /** + * The default initial STUN round-trip time estimation in msecs. + * The value normally is PJ_STUN_RTO_VALUE. + */ + unsigned rto_msec; + + /** + * The interval to cache outgoing STUN response in the STUN session, + * in miliseconds. + * + * Default 10000 (10 seconds). + */ + unsigned res_cache_msec; + + /** + * Software name to be included in all STUN requests and responses. + * + * Default: PJNATH_STUN_SOFTWARE_NAME. + */ + pj_str_t software_name; + +} pj_stun_config; + +/** + * Initialize STUN config. + */ +PJ_INLINE(void) +pj_stun_config_init(pj_stun_config *cfg, pj_pool_factory *factory, unsigned options, pj_ioqueue_t *ioqueue, + pj_timer_heap_t *timer_heap) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->pf = factory; + cfg->options = options; + cfg->ioqueue = ioqueue; + cfg->timer_heap = timer_heap; + cfg->rto_msec = PJ_STUN_RTO_VALUE; + cfg->res_cache_msec = PJ_STUN_RES_CACHE_DURATION; + cfg->software_name = pj_str((char *)PJNATH_STUN_SOFTWARE_NAME); +} + +/** + * Check that STUN config is valid. + */ +PJ_INLINE(pj_status_t) pj_stun_config_check_valid(const pj_stun_config *cfg) +{ + PJ_ASSERT_RETURN(cfg->ioqueue && cfg->pf && cfg->timer_heap && cfg->rto_msec && cfg->res_cache_msec, PJ_EINVAL); + return PJ_SUCCESS; +} + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h new file mode 100755 index 000000000..3353c0ec9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h @@ -0,0 +1,1697 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_MSG_H__ +#define __PJNATH_STUN_MSG_H__ + +/** + * @file stun_msg.h + * @brief STUN message components. + */ + +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_MSG STUN Message Representation and Parsing + * @ingroup PJNATH_STUN_BASE + * @brief Low-level representation and parsing of STUN messages. + * @{ + */ + +/** + * STUN magic cookie. + */ +#define PJ_STUN_MAGIC 0x2112A442 + +/** + * STUN method constants. + */ +enum pj_stun_method_e { + /** + * STUN Binding method as defined by RFC 3489-bis. + */ + PJ_STUN_BINDING_METHOD = 1, + + /** + * STUN Shared Secret method as defined by RFC 3489-bis. + */ + PJ_STUN_SHARED_SECRET_METHOD = 2, + + /** + * STUN/TURN Allocate method as defined by draft-ietf-behave-turn + */ + PJ_STUN_ALLOCATE_METHOD = 3, + + /** + * STUN/TURN Refresh method as defined by draft-ietf-behave-turn + */ + PJ_STUN_REFRESH_METHOD = 4, + + /** + * STUN/TURN Send indication as defined by draft-ietf-behave-turn + */ + PJ_STUN_SEND_METHOD = 6, + + /** + * STUN/TURN Data indication as defined by draft-ietf-behave-turn + */ + PJ_STUN_DATA_METHOD = 7, + + /** + * STUN/TURN CreatePermission method as defined by draft-ietf-behave-turn + */ + PJ_STUN_CREATE_PERM_METHOD = 8, + + /** + * STUN/TURN ChannelBind as defined by draft-ietf-behave-turn + */ + PJ_STUN_CHANNEL_BIND_METHOD = 9, + + /** + * STUN/TURN Connect as defined by RFC 6062 + */ + PJ_STUN_CONNECT_METHOD = 10, + + /** + * STUN/TURN ConnectionBind as defined by RFC 6062 + */ + PJ_STUN_CONNECTION_BIND_METHOD = 11, + + /** + * STUN/TURN ConnectionAttempt as defined by RFC 6062 + */ + PJ_STUN_CONNECTION_ATTEMPT_METHOD = 12, + + /** + * All known methods. + */ + PJ_STUN_METHOD_MAX +}; + +/** + * Retrieve the STUN method from the message-type field of the STUN + * message. + */ +#define PJ_STUN_GET_METHOD(msg_type) ((msg_type)&0xFEEF) + +/** + * STUN message classes constants. + */ +enum pj_stun_msg_class_e { + /** + * This specifies that the message type is a STUN request message. + */ + PJ_STUN_REQUEST_CLASS = 0, + + /** + * This specifies that the message type is a STUN indication message. + */ + PJ_STUN_INDICATION_CLASS = 1, + + /** + * This specifies that the message type is a STUN successful response. + */ + PJ_STUN_SUCCESS_CLASS = 2, + + /** + * This specifies that the message type is a STUN error response. + */ + PJ_STUN_ERROR_CLASS = 3 +}; + +/** + * Determine if the message type is a request. + */ +#define PJ_STUN_IS_REQUEST(msg_type) (((msg_type)&0x0110) == 0x0000) + +/** + * Determine if the message type is a successful response. + */ +#define PJ_STUN_IS_SUCCESS_RESPONSE(msg_type) (((msg_type)&0x0110) == 0x0100) + +/** + * The response bit in the message type. + */ +#define PJ_STUN_SUCCESS_RESPONSE_BIT (0x0100) + +/** + * Determine if the message type is an error response. + */ +#define PJ_STUN_IS_ERROR_RESPONSE(msg_type) (((msg_type)&0x0110) == 0x0110) + +/** + * The error response bit in the message type. + */ +#define PJ_STUN_ERROR_RESPONSE_BIT (0x0110) + +/** + * Determine if the message type is a response. + */ +#define PJ_STUN_IS_RESPONSE(msg_type) (((msg_type)&0x0100) == 0x0100) + +/** + * Determine if the message type is an indication message. + */ +#define PJ_STUN_IS_INDICATION(msg_type) (((msg_type)&0x0110) == 0x0010) + +/** + * The error response bit in the message type. + */ +#define PJ_STUN_INDICATION_BIT (0x0010) + +/** + * This enumeration describes STUN message types. + */ +typedef enum pj_stun_msg_type { + /** + * STUN BINDING request. + */ + PJ_STUN_BINDING_REQUEST = 0x0001, + + /** + * Successful response to STUN BINDING-REQUEST. + */ + PJ_STUN_BINDING_RESPONSE = 0x0101, + + /** + * Error response to STUN BINDING-REQUEST. + */ + PJ_STUN_BINDING_ERROR_RESPONSE = 0x0111, + + /** + * Binding Indication (ICE) + */ + PJ_STUN_BINDING_INDICATION = 0x0011, + + /** + * STUN SHARED-SECRET reqeust. + */ + PJ_STUN_SHARED_SECRET_REQUEST = 0x0002, + + /** + * Successful response to STUN SHARED-SECRET reqeust. + */ + PJ_STUN_SHARED_SECRET_RESPONSE = 0x0102, + + /** + * Error response to STUN SHARED-SECRET reqeust. + */ + PJ_STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112, + + /** + * STUN/TURN Allocate Request + */ + PJ_STUN_ALLOCATE_REQUEST = 0x0003, + + /** + * Successful response to STUN/TURN Allocate Request + */ + PJ_STUN_ALLOCATE_RESPONSE = 0x0103, + + /** + * Failure response to STUN/TURN Allocate Request + */ + PJ_STUN_ALLOCATE_ERROR_RESPONSE = 0x0113, + + /** + * STUN/TURN REFRESH Request + */ + PJ_STUN_REFRESH_REQUEST = 0x0004, + + /** + * Successful response to STUN REFRESH request + */ + PJ_STUN_REFRESH_RESPONSE = 0x0104, + + /** + * Error response to STUN REFRESH request. + */ + PJ_STUN_REFRESH_ERROR_RESPONSE = 0x0114, + + /** + * TURN Send indication + */ + PJ_STUN_SEND_INDICATION = 0x0016, + + /** + * TURN Data indication + */ + PJ_STUN_DATA_INDICATION = 0x0017, + + /** + * TURN CreatePermission request + */ + PJ_STUN_CREATE_PERM_REQUEST = 0x0008, + + /** + * TURN CreatePermission successful response. + */ + PJ_STUN_CREATE_PERM_RESPONSE = 0x0108, + + /** + * TURN CreatePermission failure response + */ + PJ_STUN_CREATE_PERM_ERROR_RESPONSE = 0x0118, + + /** + * STUN/TURN ChannelBind Request + */ + PJ_STUN_CHANNEL_BIND_REQUEST = 0x0009, + + /** + * Successful response to STUN ChannelBind request + */ + PJ_STUN_CHANNEL_BIND_RESPONSE = 0x0109, + + /** + * Error response to STUN ChannelBind request. + */ + PJ_STUN_CHANNEL_BIND_ERROR_RESPONSE = 0x0119, + + /** + * STUN/TURN Connect Request + */ + PJ_STUN_CONNECT_REQUEST = 0x000a, + + /** + * STUN/TURN ConnectBind Request + */ + PJ_STUN_CONNECTION_BIND_REQUEST = 0x000b, + + /** + * TURN ConnectionAttempt indication + */ + PJ_STUN_CONNECTION_ATTEMPT_INDICATION = 0x001c, + +} pj_stun_msg_type; + +/** + * This enumeration describes STUN attribute types. + */ +typedef enum pj_stun_attr_type { + PJ_STUN_ATTR_MAPPED_ADDR = 0x0001, /**< MAPPED-ADDRESS. */ + PJ_STUN_ATTR_RESPONSE_ADDR = 0x0002, /**< RESPONSE-ADDRESS (deprcatd)*/ + PJ_STUN_ATTR_CHANGE_REQUEST = 0x0003, /**< CHANGE-REQUEST (deprecated)*/ + PJ_STUN_ATTR_SOURCE_ADDR = 0x0004, /**< SOURCE-ADDRESS (deprecated)*/ + PJ_STUN_ATTR_CHANGED_ADDR = 0x0005, /**< CHANGED-ADDRESS (deprecatd)*/ + PJ_STUN_ATTR_USERNAME = 0x0006, /**< USERNAME attribute. */ + PJ_STUN_ATTR_PASSWORD = 0x0007, /**< was PASSWORD attribute. */ + PJ_STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, /**< MESSAGE-INTEGRITY. */ + PJ_STUN_ATTR_ERROR_CODE = 0x0009, /**< ERROR-CODE. */ + PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000A, /**< UNKNOWN-ATTRIBUTES. */ + PJ_STUN_ATTR_REFLECTED_FROM = 0x000B, /**< REFLECTED-FROM (deprecatd)*/ + PJ_STUN_ATTR_CHANNEL_NUMBER = 0x000C, /**< TURN CHANNEL-NUMBER */ + PJ_STUN_ATTR_LIFETIME = 0x000D, /**< TURN LIFETIME attr. */ + PJ_STUN_ATTR_MAGIC_COOKIE = 0x000F, /**< MAGIC-COOKIE attr (deprec)*/ + PJ_STUN_ATTR_BANDWIDTH = 0x0010, /**< TURN BANDWIDTH (deprec) */ + PJ_STUN_ATTR_XOR_PEER_ADDR = 0x0012, /**< TURN XOR-PEER-ADDRESS */ + PJ_STUN_ATTR_DATA = 0x0013, /**< DATA attribute. */ + PJ_STUN_ATTR_REALM = 0x0014, /**< REALM attribute. */ + PJ_STUN_ATTR_NONCE = 0x0015, /**< NONCE attribute. */ + PJ_STUN_ATTR_XOR_RELAYED_ADDR = 0x0016, /**< TURN XOR-RELAYED-ADDRESS */ + PJ_STUN_ATTR_REQ_ADDR_TYPE = 0x0017, /**< REQUESTED-ADDRESS-TYPE */ + PJ_STUN_ATTR_REQ_ADDR_FAMILY = 0x0017, /**< REQUESTED-ADDRESS-FAMILY */ + PJ_STUN_ATTR_EVEN_PORT = 0x0018, /**< TURN EVEN-PORT */ + PJ_STUN_ATTR_REQ_TRANSPORT = 0x0019, /**< TURN REQUESTED-TRANSPORT */ + PJ_STUN_ATTR_DONT_FRAGMENT = 0x001A, /**< TURN DONT-FRAGMENT */ + PJ_STUN_ATTR_XOR_MAPPED_ADDR = 0x0020, /**< XOR-MAPPED-ADDRESS */ + PJ_STUN_ATTR_TIMER_VAL = 0x0021, /**< TIMER-VAL attribute. */ + PJ_STUN_ATTR_RESERVATION_TOKEN = 0x0022, /**< TURN RESERVATION-TOKEN */ + PJ_STUN_ATTR_XOR_REFLECTED_FROM = 0x0023, /**< XOR-REFLECTED-FROM */ + PJ_STUN_ATTR_PRIORITY = 0x0024, /**< PRIORITY */ + PJ_STUN_ATTR_USE_CANDIDATE = 0x0025, /**< USE-CANDIDATE */ + PJ_STUN_ATTR_CONNECTION_ID = 0x002a, /**< CONNECTION-ID */ + PJ_STUN_ATTR_ICMP = 0x0030, /**< ICMP (TURN) */ + + PJ_STUN_ATTR_END_MANDATORY_ATTR, + + PJ_STUN_ATTR_START_EXTENDED_ATTR = 0x8021, + + PJ_STUN_ATTR_SOFTWARE = 0x8022, /**< SOFTWARE attribute. */ + PJ_STUN_ATTR_ALTERNATE_SERVER = 0x8023, /**< ALTERNATE-SERVER. */ + PJ_STUN_ATTR_REFRESH_INTERVAL = 0x8024, /**< REFRESH-INTERVAL. */ + PJ_STUN_ATTR_FINGERPRINT = 0x8028, /**< FINGERPRINT attribute. */ + PJ_STUN_ATTR_ICE_CONTROLLED = 0x8029, /**< ICE-CCONTROLLED attribute.*/ + PJ_STUN_ATTR_ICE_CONTROLLING = 0x802a, /**< ICE-CCONTROLLING attribute*/ + + PJ_STUN_ATTR_END_EXTENDED_ATTR + +} pj_stun_attr_type; + +/** + * STUN error codes, which goes into STUN ERROR-CODE attribute. + */ +typedef enum pj_stun_status { + PJ_STUN_SC_TRY_ALTERNATE = 300, /**< Try Alternate */ + PJ_STUN_SC_BAD_REQUEST = 400, /**< Bad Request */ + PJ_STUN_SC_UNAUTHORIZED = 401, /**< Unauthorized */ + PJ_STUN_SC_FORBIDDEN = 403, /**< Forbidden (TURN) */ + PJ_STUN_SC_UNKNOWN_ATTRIBUTE = 420, /**< Unknown Attribute */ +#if 0 + /* These were obsolete in recent rfc3489bis */ + //PJ_STUN_SC_STALE_CREDENTIALS = 430, /**< Stale Credentials */ + //PJ_STUN_SC_INTEGRITY_CHECK_FAILURE= 431, /**< Integrity Chk Fail */ + //PJ_STUN_SC_MISSING_USERNAME = 432, /**< Missing Username */ + //PJ_STUN_SC_USE_TLS = 433, /**< Use TLS */ + //PJ_STUN_SC_MISSING_REALM = 434, /**< Missing Realm */ + //PJ_STUN_SC_MISSING_NONCE = 435, /**< Missing Nonce */ + //PJ_STUN_SC_UNKNOWN_USERNAME = 436, /**< Unknown Username */ +#endif + PJ_STUN_SC_ALLOCATION_MISMATCH = 437, /**< TURN Alloc Mismatch */ + PJ_STUN_SC_STALE_NONCE = 438, /**< Stale Nonce */ + PJ_STUN_SC_TRANSITIONING = 439, /**< Transitioning. */ + PJ_STUN_SC_WRONG_CREDENTIALS = 441, /**< TURN Wrong Credentials */ + PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO = 442, /**< Unsupported Transport or + Protocol (TURN) */ + PJ_STUN_SC_OPER_TCP_ONLY = 445, /**< Operation for TCP Only */ + PJ_STUN_SC_CONNECTION_FAILURE = 446, /**< Connection Failure */ + PJ_STUN_SC_CONNECTION_TIMEOUT = 447, /**< Connection Timeout */ + PJ_STUN_SC_ALLOCATION_QUOTA_REACHED = 486, /**< Allocation Quota Reached + (TURN) */ + PJ_STUN_SC_ROLE_CONFLICT = 487, /**< Role Conflict */ + PJ_STUN_SC_SERVER_ERROR = 500, /**< Server Error */ + PJ_STUN_SC_INSUFFICIENT_CAPACITY = 508, /**< Insufficient Capacity + (TURN) */ + PJ_STUN_SC_GLOBAL_FAILURE = 600 /**< Global Failure */ +} pj_stun_status; + +/** + * This structure describes STUN message header. A STUN message has the + * following format: + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0 0| STUN Message Type | Message Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Magic Cookie | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Transaction ID + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +#pragma pack(1) +typedef struct pj_stun_msg_hdr { + /** + * STUN message type, which the first two bits must be zeroes. + */ + pj_uint16_t type; + + /** + * The message length is the size, in bytes, of the message not + * including the 20 byte STUN header. + */ + pj_uint16_t length; + + /** + * The magic cookie is a fixed value, 0x2112A442 (PJ_STUN_MAGIC constant). + * In the previous version of this specification [15] this field was part + * of the transaction ID. + */ + pj_uint32_t magic; + + /** + * The transaction ID is a 96 bit identifier. STUN transactions are + * identified by their unique 96-bit transaction ID. For request/ + * response transactions, the transaction ID is chosen by the STUN + * client and MUST be unique for each new STUN transaction generated by + * that STUN client. The transaction ID MUST be uniformly and randomly + * distributed between 0 and 2**96 - 1. + */ + pj_uint8_t tsx_id[12]; + +} pj_stun_msg_hdr; +#pragma pack() + +/** + * This structre describes STUN attribute header. Each attribute is + * TLV encoded, with a 16 bit type, 16 bit length, and variable value. + * Each STUN attribute ends on a 32 bit boundary: + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +#pragma pack(1) +typedef struct pj_stun_attr_hdr { + /** + * STUN attribute type. + */ + pj_uint16_t type; + + /** + * The Length refers to the length of the actual useful content of the + * Value portion of the attribute, measured in bytes. The value + * in the Length field refers to the length of the Value part of the + * attribute prior to padding - i.e., the useful content. + */ + pj_uint16_t length; + +} pj_stun_attr_hdr; +#pragma pack() + +/** + * This structure describes STUN generic IP address attribute, used for + * example to represent STUN MAPPED-ADDRESS attribute. + * + * The generic IP address attribute indicates the transport address. + * It consists of an eight bit address family, and a sixteen bit port, + * followed by a fixed length value representing the IP address. If the + * address family is IPv4, the address is 32 bits, in network byte + * order. If the address family is IPv6, the address is 128 bits in + * network byte order. + * + * The format of the generic IP address attribute is: + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |x x x x x x x x| Family | Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Address (variable) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +typedef struct pj_stun_sockaddr_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * Flag to indicate whether this attribute should be sent in XOR-ed + * format, or has been received in XOR-ed format. + */ + pj_bool_t xor_ed; + + /** + * The socket address + */ + pj_sockaddr sockaddr; + +} pj_stun_sockaddr_attr; + +/** + * This structure represents a generic STUN attributes with no payload, + * and it is used for example by ICE USE-CANDIDATE attribute. + */ +typedef struct pj_stun_empty_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + +} pj_stun_empty_attr; + +/** + * This structure represents generic STUN string attributes, such as STUN + * USERNAME, PASSWORD, SOFTWARE, REALM, and NONCE attributes. + */ +typedef struct pj_stun_string_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The string value. + */ + pj_str_t value; + +} pj_stun_string_attr; + +/** + * This structure represents a generic STUN attributes with 32bit (unsigned) + * integer value, such as STUN FINGERPRINT and REFRESH-INTERVAL attributes. + */ +typedef struct pj_stun_uint_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The 32bit value, in host byte order. + */ + pj_uint32_t value; + +} pj_stun_uint_attr; + +/** + * This structure represents a generic STUN attributes with 64bit (unsigned) + * integer value, such as ICE-CONTROLLED and ICE-CONTROLLING attributes. + */ +typedef struct pj_stun_uint64_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The 64bit value, in host byte order, represented with pj_timestamp. + */ + pj_timestamp value; + +} pj_stun_uint64_attr; + +/** + * This structure represents generic STUN attributes to hold a raw binary + * data. + */ +typedef struct pj_stun_binary_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * Special signature to indicate that this is a valid attribute even + * though we don't have meta-data to describe this attribute. + */ + pj_uint32_t magic; + + /** + * Length of the data. + */ + unsigned length; + + /** + * The raw data. + */ + pj_uint8_t *data; + +} pj_stun_binary_attr; + +/** + * This structure describes STUN MESSAGE-INTEGRITY attribute. + * The MESSAGE-INTEGRITY attribute contains an HMAC-SHA1 [10] of the + * STUN message. The MESSAGE-INTEGRITY attribute can be present in any + * STUN message type. Since it uses the SHA1 hash, the HMAC will be 20 + * bytes. + */ +typedef struct pj_stun_msgint_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The 20 bytes hmac value. + */ + pj_uint8_t hmac[20]; + +} pj_stun_msgint_attr; + +/** + * This structure describes STUN FINGERPRINT attribute. The FINGERPRINT + * attribute can be present in all STUN messages. It is computed as + * the CRC-32 of the STUN message up to (but excluding) the FINGERPRINT + * attribute itself, xor-d with the 32 bit value 0x5354554e + */ +typedef struct pj_stun_uint_attr pj_stun_fingerprint_attr; + +/** + * This structure represents STUN ERROR-CODE attribute. The ERROR-CODE + * attribute is present in the Binding Error Response and Shared Secret + * Error Response. It is a numeric value in the range of 100 to 699 + * plus a textual reason phrase encoded in UTF-8 + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 0 |Class| Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reason Phrase (variable) .. + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +typedef struct pj_stun_errcode_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * STUN error code. + */ + int err_code; + + /** + * The reason phrase. + */ + pj_str_t reason; + +} pj_stun_errcode_attr; + +/** + * This describes STUN REALM attribute. + * The REALM attribute is present in requests and responses. It + * contains text which meets the grammar for "realm" as described in RFC + * 3261 [11], and will thus contain a quoted string (including the + * quotes). + */ +typedef struct pj_stun_string_attr pj_stun_realm_attr; + +/** + * This describes STUN NONCE attribute. + * The NONCE attribute is present in requests and in error responses. + * It contains a sequence of qdtext or quoted-pair, which are defined in + * RFC 3261 [11]. See RFC 2617 [7] for guidance on selection of nonce + * values in a server. + */ +typedef struct pj_stun_string_attr pj_stun_nonce_attr; + +/** + * This describes STUN UNKNOWN-ATTRIBUTES attribute. + * The UNKNOWN-ATTRIBUTES attribute is present only in an error response + * when the response code in the ERROR-CODE attribute is 420. + * The attribute contains a list of 16 bit values, each of which + * represents an attribute type that was not understood by the server. + * If the number of unknown attributes is an odd number, one of the + * attributes MUST be repeated in the list, so that the total length of + * the list is a multiple of 4 bytes. + */ +typedef struct pj_stun_unknown_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * Number of unknown attributes in the array. + */ + unsigned attr_count; + + /** + * Array of unknown attribute IDs. + */ + pj_uint16_t attrs[PJ_STUN_MAX_ATTR]; + +} pj_stun_unknown_attr; + +/** + * This structure describes STUN MAPPED-ADDRESS attribute. + * The MAPPED-ADDRESS attribute indicates the mapped transport address. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_mapped_addr_attr; + +/** + * This describes STUN XOR-MAPPED-ADDRESS attribute (which has the same + * format as STUN MAPPED-ADDRESS attribute). + * The XOR-MAPPED-ADDRESS attribute is present in responses. It + * provides the same information that would present in the MAPPED- + * ADDRESS attribute but because the NAT's public IP address is + * obfuscated through the XOR function, STUN messages are able to pass + * through NATs which would otherwise interfere with STUN. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_mapped_addr_attr; + +/** + * This describes STUN SOFTWARE attribute. + * The SOFTWARE attribute contains a textual description of the software + * being used by the agent sending the message. It is used by clients + * and servers. Its value SHOULD include manufacturer and version + * number. */ +typedef struct pj_stun_string_attr pj_stun_software_attr; + +/** + * This describes STUN ALTERNATE-SERVER attribute. + * The alternate server represents an alternate transport address for a + * different STUN server to try. It is encoded in the same way as + * MAPPED-ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_alt_server_attr; + +/** + * This describes STUN REFRESH-INTERVAL attribute. + * The REFRESH-INTERVAL indicates the number of milliseconds that the + * server suggests the client should use between refreshes of the NAT + * bindings between the client and server. + */ +typedef struct pj_stun_uint_attr pj_stun_refresh_interval_attr; + +/** + * This structure describes STUN RESPONSE-ADDRESS attribute. + * The RESPONSE-ADDRESS attribute indicates where the response to a + * Binding Request should be sent. Its syntax is identical to MAPPED- + * ADDRESS. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_response_addr_attr; + +/** + * This structure describes STUN CHANGED-ADDRESS attribute. + * The CHANGED-ADDRESS attribute indicates the IP address and port where + * responses would have been sent from if the "change IP" and "change + * port" flags had been set in the CHANGE-REQUEST attribute of the + * Binding Request. The attribute is always present in a Binding + * Response, independent of the value of the flags. Its syntax is + * identical to MAPPED-ADDRESS. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_changed_addr_attr; + +/** + * This structure describes STUN CHANGE-REQUEST attribute. + * The CHANGE-REQUEST attribute is used by the client to request that + * the server use a different address and/or port when sending the + * response. + * + * Bit 29 of the value is the "change IP" flag. If true, it requests + * the server to send the Binding Response with a different IP address + * than the one the Binding Request was received on. + * + * Bit 30 of the value is the "change port" flag. If true, it requests + * the server to send the Binding Response with a different port than + * the one the Binding Request was received on. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_uint_attr pj_stun_change_request_attr; + +/** + * This structure describes STUN SOURCE-ADDRESS attribute. + * The SOURCE-ADDRESS attribute is present in Binding Responses. It + * indicates the source IP address and port that the server is sending + * the response from. Its syntax is identical to that of MAPPED- + * ADDRESS. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_src_addr_attr; + +/** + * This describes the STUN REFLECTED-FROM attribute. + * The REFLECTED-FROM attribute is present only in Binding Responses, + * when the Binding Request contained a RESPONSE-ADDRESS attribute. The + * attribute contains the identity (in terms of IP address) of the + * source where the request came from. Its purpose is to provide + * traceability, so that a STUN server cannot be used as a reflector for + * denial-of-service attacks. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_reflected_from_attr; + +/** + * This describes STUN USERNAME attribute. + * The USERNAME attribute is used for message integrity. It identifies + * the shared secret used in the message integrity check. Consequently, + * the USERNAME MUST be included in any request that contains the + * MESSAGE-INTEGRITY attribute. + */ +typedef struct pj_stun_string_attr pj_stun_username_attr; + +/** + * This describes STUN PASSWORD attribute. + * If the message type is Shared Secret Response it MUST include the + * PASSWORD attribute. + */ +typedef struct pj_stun_string_attr pj_stun_password_attr; + +/** + * This describes TURN CHANNEL-NUMBER attribute. In this library, + * this attribute is represented with 32bit integer. Application may + * use #PJ_STUN_GET_CH_NB() and #PJ_STUN_SET_CH_NB() to extract/set + * channel number value from the 32bit integral value. + * + * The CHANNEL-NUMBER attribute contains the number of the channel. + * It is a 16-bit unsigned integer, followed by a two-octet RFFU field + * which MUST be set to 0 on transmission and ignored on reception. + + \verbatim + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Channel Number | RFFU | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + \endverbatim + */ +typedef struct pj_stun_uint_attr pj_stun_channel_number_attr; + +/** + * Get 16bit channel number from 32bit integral value. + * Note that uint32 attributes are always stored in host byte order + * after they have been parsed from the PDU, so no need to do ntohs() + * here. + */ +#define PJ_STUN_GET_CH_NB(u32) ((pj_uint16_t)(u32 >> 16)) + +/** + * Convert 16bit channel number into 32bit integral value. + * Note that uint32 attributes will be converted to network byte order + * when the attribute is written to packet, so no need to do htons() + * here. + */ +#define PJ_STUN_SET_CH_NB(chnum) (((pj_uint32_t)chnum) << 16) + +/** + * This describes STUN LIFETIME attribute. + * The lifetime attribute represents the duration for which the server + * will maintain an allocation in the absence of data traffic either + * from or to the client. It is a 32 bit value representing the number + * of seconds remaining until expiration. + */ +typedef struct pj_stun_uint_attr pj_stun_lifetime_attr; + +/** + * This describes STUN BANDWIDTH attribute. + * The bandwidth attribute represents the peak bandwidth, measured in + * kbits per second, that the client expects to use on the binding. The + * value represents the sum in the receive and send directions. + */ +typedef struct pj_stun_uint_attr pj_stun_bandwidth_attr; + +/** + * This describes the STUN XOR-PEER-ADDRESS attribute. + * The XOR-PEER-ADDRESS specifies the address and port of the peer as seen + * from the TURN server. It is encoded in the same way as XOR-MAPPED- + * ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_peer_addr_attr; + +/** + * This describes the STUN DATA attribute. + * The DATA attribute is present in Send Indications and Data + * Indications. It contains raw payload data that is to be sent (in the + * case of a Send Request) or was received (in the case of a Data + * Indication).. + */ +typedef struct pj_stun_binary_attr pj_stun_data_attr; + +/** + * This describes the STUN XOR-RELAYED-ADDRESS attribute. The + * XOR-RELAYED-ADDRESS is present in Allocate responses. It specifies the + * address and port that the server allocated to the client. It is + * encoded in the same way as XOR-MAPPED-ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_relayed_addr_attr; + +/** + * According to RFC 6156, this describes the REQUESTED-ADDRESS-FAMILY + * attribute (formerly known as REQUESTED-ADDRESS-TYPE in the draft). + * The REQUESTED-ADDRESS-FAMILY attribute is used by clients to request + * the allocation of a specific address type from a server. The + * following is the format of the REQUESTED-ADDRESS-FAMILY attribute. + + \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Family | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +typedef struct pj_stun_uint_attr pj_stun_req_addr_type_attr; + +/** + * This describes the TURN REQUESTED-TRANSPORT attribute, encoded in + * STUN generic integer attribute. + * + * This attribute allows the client to request that the port in the + * relayed-transport-address be even, and (optionally) that the server + * reserve the next-higher port number. The attribute is 8 bits long. + * Its format is: + +\verbatim + 0 + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |R| RFFU | + +-+-+-+-+-+-+-+-+ + +\endverbatim + + * The attribute contains a single 1-bit flag: + * + * R: If 1, the server is requested to reserve the next higher port + * number (on the same IP address) for a subsequent allocation. If + * 0, no such reservation is requested. + * + * The other 7 bits of the attribute must be set to zero on transmission + * and ignored on reception. + */ +typedef struct pj_stun_uint_attr pj_stun_even_port_attr; + +/** + * This describes the TURN REQUESTED-TRANSPORT attribute, encoded in + * STUN generic integer attribute. + * + * This attribute is used by the client to request a specific transport + * protocol for the allocated transport address. It has the following + * format: + + \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Protocol | RFFU | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + \endverbatim + + * The Protocol field specifies the desired protocol. The codepoints + * used in this field are taken from those allowed in the Protocol field + * in the IPv4 header and the NextHeader field in the IPv6 header + * [Protocol-Numbers]. This specification only allows the use of + * codepoint 17 (User Datagram Protocol). + * + * The RFFU field is set to zero on transmission and ignored on + * receiption. It is reserved for future uses. + */ +typedef struct pj_stun_uint_attr pj_stun_req_transport_attr; + +/** + * Get protocol value from 32bit TURN REQUESTED-TRANSPORT attribute. + */ +#define PJ_STUN_GET_RT_PROTO(u32) (u32 >> 24) + +/** + * Convert protocol value to be placed in 32bit TURN REQUESTED-TRANSPORT + * attribute. + */ +#define PJ_STUN_SET_RT_PROTO(proto) (((pj_uint32_t)(proto)) << 24) + +/** + * This describes the TURN DONT-FRAGMENT attribute. + * + * This attribute is used by the client to request that the server set + * the DF (Don't Fragment) bit in the IP header when relaying the + * application data onward to the peer. This attribute has no value + * part and thus the attribute length field is 0. + */ +typedef struct pj_stun_empty_attr pj_stun_dont_fragment_attr; + +/** + * This describes the TURN RESERVATION-TOKEN attribute. + * The RESERVATION-TOKEN attribute contains a token that uniquely + * identifies a relayed transport address being held in reserve by the + * server. The server includes this attribute in a success response to + * tell the client about the token, and the client includes this + * attribute in a subsequent Allocate request to request the server use + * that relayed transport address for the allocation. + * + * The attribute value is a 64-bit-long field containing the token + * value. + */ +typedef struct pj_stun_uint64_attr pj_stun_res_token_attr; + +/** + * This describes the XOR-REFLECTED-FROM attribute, as described by + * draft-macdonald-behave-nat-behavior-discovery-00. + * The XOR-REFLECTED-FROM attribute is used in place of the REFLECTED- + * FROM attribute. It provides the same information, but because the + * NAT's public address is obfuscated through the XOR function, It can + * pass through a NAT that would otherwise attempt to translate it to + * the private network address. XOR-REFLECTED-FROM has identical syntax + * to XOR-MAPPED-ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_reflected_from_attr; + +/** + * This describes the PRIORITY attribute from draft-ietf-mmusic-ice-13. + * The PRIORITY attribute indicates the priority that is to be + * associated with a peer reflexive candidate, should one be discovered + * by this check. It is a 32 bit unsigned integer, and has an attribute + * type of 0x0024. + */ +typedef struct pj_stun_uint_attr pj_stun_priority_attr; + +/** + * This describes the USE-CANDIDATE attribute from draft-ietf-mmusic-ice-13. + * The USE-CANDIDATE attribute indicates that the candidate pair + * resulting from this check should be used for transmission of media. + * The attribute has no content (the Length field of the attribute is + * zero); it serves as a flag. + */ +typedef struct pj_stun_empty_attr pj_stun_use_candidate_attr; + +/** + * This describes the STUN TIMER-VAL attribute. + * The TIMER-VAL attribute is used only in conjunction with the Set + * Active Destination response. It conveys from the server, to the + * client, the value of the timer used in the server state machine. + */ +typedef struct pj_stun_uint_attr pj_stun_timer_val_attr; + +/** + * This describes ICE-CONTROLLING attribute. + */ +typedef struct pj_stun_uint64_attr pj_stun_ice_controlling_attr; + +/** + * This describes ICE-CONTROLLED attribute. + */ +typedef struct pj_stun_uint64_attr pj_stun_ice_controlled_attr; + +/** + * This describes TURN ICMP attribute + */ +typedef struct pj_stun_uint_attr pj_stun_icmp_attr; + +/** + * This structure describes a parsed STUN message. All integral fields + * in this structure (including IP addresses) will be in the host + * byte order. + */ +typedef struct pj_stun_msg { + /** + * STUN message header. + */ + pj_stun_msg_hdr hdr; + + /** + * Number of attributes in the STUN message. + */ + unsigned attr_count; + + /** + * Array of STUN attributes. + */ + pj_stun_attr_hdr *attr[PJ_STUN_MAX_ATTR]; + +} pj_stun_msg; + +/** STUN decoding options */ +enum pj_stun_decode_options { + /** + * Tell the decoder that the message was received from datagram + * oriented transport (such as UDP). + */ + PJ_STUN_IS_DATAGRAM = 1, + + /** + * Tell pj_stun_msg_decode() to check the validity of the STUN + * message by calling pj_stun_msg_check() before starting to + * decode the packet. + */ + PJ_STUN_CHECK_PACKET = 2, + + /** + * This option current is only valid for #pj_stun_session_on_rx_pkt(). + * When specified, it tells the session NOT to authenticate the + * message. + */ + PJ_STUN_NO_AUTHENTICATE = 4, + + /** + * Disable FINGERPRINT verification. This option can be used when calling + * #pj_stun_msg_check() and #pj_stun_msg_decode() to disable the + * verification of FINGERPRINT, for example when the STUN usage says when + * FINGERPRINT mechanism shall not be used. + */ + PJ_STUN_NO_FINGERPRINT_CHECK = 8 +}; + +/** + * Get STUN message method name. + * + * @param msg_type The STUN message type (in host byte order) + * + * @return The STUN message method name string. + */ +PJ_DECL(const char *) pj_stun_get_method_name(unsigned msg_type); + +/** + * Get STUN message class name. + * + * @param msg_type The STUN message type (in host byte order) + * + * @return The STUN message class name string. + */ +PJ_DECL(const char *) pj_stun_get_class_name(unsigned msg_type); + +/** + * Get STUN attribute name. + * + * @return attr_type The STUN attribute type (in host byte order). + * + * @return The STUN attribute type name string. + */ +PJ_DECL(const char *) pj_stun_get_attr_name(unsigned attr_type); + +/** + * Get STUN standard reason phrase for the specified error code. + * + * @param err_code The STUN error code. + * + * @return The STUN error reason phrase. + */ +PJ_DECL(pj_str_t) pj_stun_get_err_reason(int err_code); + +/** + * Internal: set the padding character for string attribute. + * The default padding character is PJ_STUN_STRING_ATTR_PAD_CHR. + * + * @return The previous padding character. + */ +PJ_DECL(int) pj_stun_set_padding_char(int chr); + +/** + * Initialize a generic STUN message. + * + * @param msg The message structure to be initialized. + * @param msg_type The 14bit message type (see pj_stun_msg_type + * constants). + * @param magic Magic value to be put to the mesage; for requests, + * the value normally should be PJ_STUN_MAGIC. + * @param tsx_id Optional transaction ID, or NULL to let the + * function generates a random transaction ID. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_init(pj_stun_msg *msg, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12]); + +/** + * Create a generic STUN message. + * + * @param pool Pool to create the STUN message. + * @param msg_type The 14bit message type. + * @param magic Magic value to be put to the mesage; for requests, + * the value should be PJ_STUN_MAGIC. + * @param tsx_id Optional transaction ID, or NULL to let the + * function generates a random transaction ID. + * @param p_msg Pointer to receive the message. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_create(pj_pool_t *pool, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_msg **p_msg); + +/** + * Clone a STUN message with all of its attributes. + * + * @param pool Pool to allocate memory for the new message. + * @param msg The message to be cloned. + * + * @return The duplicate message. + */ +PJ_DECL(pj_stun_msg *) pj_stun_msg_clone(pj_pool_t *pool, const pj_stun_msg *msg); + +/** + * Create STUN response message. + * + * @param pool Pool to create the mesage. + * @param req_msg The request message. + * @param err_code STUN error code. If this value is not zero, + * then error response will be created, otherwise + * successful response will be created. + * @param err_msg Optional error message to explain err_code. + * If this value is NULL and err_code is not zero, + * the error string will be taken from the default + * STUN error message. + * @param p_response Pointer to receive the response. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_create_response(pj_pool_t *pool, const pj_stun_msg *req_msg, unsigned err_code, const pj_str_t *err_msg, + pj_stun_msg **p_response); + +/** + * Add STUN attribute to STUN message. + * + * @param msg The STUN message. + * @param attr The STUN attribute to be added to the message. + * + * @return PJ_SUCCESS on success, or PJ_ETOOMANY if there are + * already too many attributes in the message. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg, pj_stun_attr_hdr *attr); + +/** + * Print the STUN message structure to a packet buffer, ready to be + * sent to remote destination. This function will take care about + * calculating the MESSAGE-INTEGRITY digest as well as FINGERPRINT + * value, if these attributes are present in the message. + * + * If application wants to apply credential to the message, it MUST + * include a blank MESSAGE-INTEGRITY attribute in the message as the + * last attribute or the attribute before FINGERPRINT. This function will + * calculate the HMAC digest from the message using the supplied key in + * the parameter. The key should be set to the password if short term + * credential is used, or calculated from the MD5 hash of the realm, + * username, and password using #pj_stun_create_key() if long term + * credential is used. + * + * If FINGERPRINT attribute is present, this function will calculate + * the FINGERPRINT CRC attribute for the message. The FINGERPRINT MUST + * be added as the last attribute of the message. + * + * @param msg The STUN message to be printed. Upon return, + * some fields in the header (such as message + * length) will be updated. + * @param pkt_buf The buffer to be filled with the packet. + * @param buf_size Size of the buffer. + * @param options Options, which currently must be zero. + * @param key Authentication key to calculate MESSAGE-INTEGRITY + * value. Application can create this key by using + * #pj_stun_create_key() function. + * @param p_msg_len Upon return, it will be filed with the size of + * the packet in bytes, or negative value on error. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_encode(pj_stun_msg *msg, pj_uint8_t *pkt_buf, pj_size_t buf_size, unsigned options, const pj_str_t *key, + pj_size_t *p_msg_len); + +/** + * Check that the PDU is potentially a valid STUN message. This function + * is useful when application needs to multiplex STUN packets with other + * application traffic. When this function returns PJ_SUCCESS, there is a + * big chance that the packet is a STUN packet. + * + * Note that we cannot be sure that the PDU is a really valid STUN message + * until we actually parse the PDU. + * + * @param pdu The packet buffer. + * @param pdu_len The length of the packet buffer. + * @param options Additional options to be applied in the checking, + * which can be taken from pj_stun_decode_options. One + * of the useful option is PJ_STUN_IS_DATAGRAM which + * means that the pdu represents a whole STUN packet. + * + * @return PJ_SUCCESS if the PDU is a potentially valid STUN + * message. + */ +PJ_DECL(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options); + +/** + * Decode incoming packet into STUN message. + * + * @param pool Pool to allocate the message. + * @param pdu The incoming packet to be parsed. + * @param pdu_len The length of the incoming packet. + * @param options Parsing flags, according to pj_stun_decode_options. + * @param p_msg Pointer to receive the parsed message. + * @param p_parsed_len Optional pointer to receive how many bytes have + * been parsed for the STUN message. This is useful + * when the packet is received over stream oriented + * transport. + * @param p_response Optional pointer to receive an instance of response + * message, if one can be created. If the packet being + * decoded is a request message, and it contains error, + * and a response can be created, then the STUN + * response message will be returned on this argument. + * + * @return PJ_SUCCESS if a STUN message has been successfully + * decoded. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_decode(pj_pool_t *pool, const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options, pj_stun_msg **p_msg, + pj_size_t *p_parsed_len, pj_stun_msg **p_response); + +/** + * Dump STUN message to a printable string output. + * + * @param msg The STUN message + * @param buffer Buffer where the printable string output will + * be printed on. + * @param length Specify the maximum length of the buffer. + * @param printed_len Optional pointer, which on output will be filled + * up with the actual length of the output string. + * + * @return The message string output. + */ +#if PJ_LOG_MAX_LEVEL > 0 +PJ_DECL(char *) pj_stun_msg_dump(const pj_stun_msg *msg, char *buffer, unsigned length, unsigned *printed_len); +#else +#define pj_stun_msg_dump(msg, buf, length, printed_len) "" +#endif + +/** + * Find STUN attribute in the STUN message, starting from the specified + * index. + * + * @param msg The STUN message. + * @param attr_type The attribute type to be found, from pj_stun_attr_type. + * @param start_index The start index of the attribute in the message. + * Specify zero to start searching from the first + * attribute. + * + * @return The attribute instance, or NULL if it cannot be + * found. + */ +PJ_DECL(pj_stun_attr_hdr *) pj_stun_msg_find_attr(const pj_stun_msg *msg, int attr_type, unsigned start_index); + +/** + * Clone a STUN attribute. + * + * @param pool Pool to allocate memory. + * @param attr Attribute to clone. + * + * @return Duplicate attribute. + */ +PJ_DECL(pj_stun_attr_hdr *) pj_stun_attr_clone(pj_pool_t *pool, const pj_stun_attr_hdr *attr); + +/** + * Initialize generic STUN IP address attribute. The \a addr_len and + * \a addr parameters specify whether the address is IPv4 or IPv4 + * address. + * + * @param attr The socket address attribute to initialize. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param xor_ed If non-zero, the port and address will be XOR-ed + * with magic, to make the XOR-MAPPED-ADDRESS attribute. + * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure. + * @param addr_len Length of \a addr parameter. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_sockaddr_attr_init(pj_stun_sockaddr_attr *attr, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len); + +/** + * Create a generic STUN IP address attribute. The \a addr_len and + * \a addr parameters specify whether the address is IPv4 or IPv4 + * address. + * + * @param pool The pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param xor_ed If non-zero, the port and address will be XOR-ed + * with magic, to make the XOR-MAPPED-ADDRESS attribute. + * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure. + * @param addr_len Length of \a addr parameter. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_sockaddr_attr_create(pj_pool_t *pool, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len, pj_stun_sockaddr_attr **p_attr); + +/** + * Create and add generic STUN IP address attribute to a STUN message. + * The \a addr_len and \a addr parameters specify whether the address is + * IPv4 or IPv4 address. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param xor_ed If non-zero, the port and address will be XOR-ed + * with magic, to make the XOR-MAPPED-ADDRESS attribute. + * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure. + * @param addr_len Length of \a addr parameter. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_bool_t xor_ed, + const pj_sockaddr_t *addr, unsigned addr_len); + +/** + * Initialize a STUN generic string attribute. + * + * @param attr The string attribute to be initialized. + * @param pool Pool to duplicate the value into the attribute, + * if value is not NULL or empty. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The string value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_string_attr_init(pj_stun_string_attr *attr, pj_pool_t *pool, int attr_type, const pj_str_t *value); + +/** + * Create a STUN generic string attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The string value to be assigned to the attribute. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_string_attr_create(pj_pool_t *pool, int attr_type, const pj_str_t *value, pj_stun_string_attr **p_attr); + +/** + * Create and add STUN generic string attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The string value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_string_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_str_t *value); + +/** + * Create a STUN generic 32bit value attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The 32bit value to be assigned to the attribute. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_uint_attr_create(pj_pool_t *pool, int attr_type, pj_uint32_t value, pj_stun_uint_attr **p_attr); + +/** + * Create and add STUN generic 32bit value attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The 32bit value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_uint32_t value); + +/** + * Create a STUN generic 64bit value attribute. + * + * @param pool Pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value Optional value to be assigned. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_uint64_attr_create(pj_pool_t *pool, int attr_type, const pj_timestamp *value, pj_stun_uint64_attr **p_attr); + +/** + * Create and add STUN generic 64bit value attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The 64bit value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_uint64_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_timestamp *value); + +/** + * Create a STUN MESSAGE-INTEGRITY attribute. + * + * @param pool The pool to allocate memory from. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool, pj_stun_msgint_attr **p_attr); + +/** + * Create and add STUN MESSAGE-INTEGRITY attribute. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool, pj_stun_msg *msg); + +/** + * Create a STUN ERROR-CODE attribute. + * + * @param pool The pool to allocate memory from. + * @param err_code STUN error code. + * @param err_reason Optional STUN error reason. If NULL is given, the + * standard error reason will be given. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_errcode_attr_create(pj_pool_t *pool, int err_code, const pj_str_t *err_reason, pj_stun_errcode_attr **p_attr); + +/** + * Create and add STUN ERROR-CODE attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN mesage. + * @param err_code STUN error code. + * @param err_reason Optional STUN error reason. If NULL is given, the + * standard error reason will be given. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_errcode_attr(pj_pool_t *pool, pj_stun_msg *msg, int err_code, const pj_str_t *err_reason); + +/** + * Create instance of STUN UNKNOWN-ATTRIBUTES attribute and copy the + * unknown attribute array to the attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_cnt Number of attributes in the array (can be zero). + * @param attr Optional array of attributes. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_unknown_attr_create(pj_pool_t *pool, unsigned attr_cnt, const pj_uint16_t attr[], + pj_stun_unknown_attr **p_attr); + +/** + * Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_cnt Number of attributes in the array (can be zero). + * @param attr Optional array of attribute types. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_unknown_attr(pj_pool_t *pool, pj_stun_msg *msg, unsigned attr_cnt, const pj_uint16_t attr[]); + +/** + * Initialize STUN binary attribute. + * + * @param attr The attribute to be initialized. + * @param pool Pool to copy data, if the data and length are set. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param data Data to be coped to the attribute, or NULL + * if no data to be copied now. + * @param length Length of data, or zero if no data is to be + * copied now. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_binary_attr_init(pj_stun_binary_attr *attr, pj_pool_t *pool, int attr_type, const pj_uint8_t *data, + unsigned length); + +/** + * Create STUN binary attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param data Data to be coped to the attribute, or NULL + * if no data to be copied now. + * @param length Length of data, or zero if no data is to be + * copied now. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_binary_attr_create(pj_pool_t *pool, int attr_type, const pj_uint8_t *data, unsigned length, + pj_stun_binary_attr **p_attr); + +/** + * Create STUN binary attribute and add the attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param data Data to be coped to the attribute, or NULL + * if no data to be copied now. + * @param length Length of data, or zero if no data is to be + * copied now. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_binary_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_uint8_t *data, unsigned length); + +/** + * Create STUN empty attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool, int attr_type, pj_stun_empty_attr **p_attr); + +/** + * Create STUN empty attribute and add the attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_empty_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_MSG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h new file mode 100755 index 000000000..38910f51d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_SESSION_H__ +#define __PJNATH_STUN_SESSION_H__ + +/** + * @file stun_session.h + * @brief STUN session management for client/server. + */ + +#include +#include +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @addtogroup PJNATH_STUN_SESSION + * @{ + * + * This is is a transport-independent object to manage a client or server + * STUN session. It has the following features: + * + * - transport independent:\n + * the object does not have it's own socket, but rather it provides + * functions and callbacks to send and receive packets. This way the + * object can be used by different transport types (e.g. UDP, TCP, + * TLS, etc.) as well as better integration to application which + * already has its own means to send and receive packets. + * + * - authentication management:\n + * the object manages STUN authentication throughout the lifetime of + * the session. For client sessions, once it's given a credential to + * authenticate itself with the server, the object will automatically + * add authentication info (the MESSAGE-INTEGRITY) to the request as + * well as authenticate the response. It will also handle long-term + * authentication challenges, including handling of nonce expiration, + * and retry the request automatically. For server sessions, it can + * be configured to authenticate incoming requests automatically. + * + * - static or dynamic credential:\n + * application may specify static or dynamic credential to be used by + * the STUN session. Static credential means a static combination of + * username and password (and these cannot change during the session + * duration), while dynamic credential provides callback to ask the + * application about which username/password to use everytime + * authentication is about to be performed. + * + * - client transaction management:\n + * outgoing requests may be sent with a STUN transaction for reliability, + * and the object will manage the transaction internally (including + * performing retransmissions). Application will be notified about the + * result of the request when the response arrives (or the transaction + * times out). When the request is challenged with authentication, the + * object will retry the request with new authentication info, and + * application will be notified about the final result of this request. + * + * - server transaction management:\n + * application may ask response to incoming requests to be cached by + * the object, and in this case the object will check for cached + * response everytime request is received. The cached response will be + * deleted once a timer expires. + * + * \section using_stun_sess_sec Using the STUN session + * + * The following steps describes how to use the STUN session: + * + * - create the object configuration:\n + * The #pj_stun_config contains the configuration to create the STUN + * session, such as the timer heap to register internal timers and + * various STUN timeout values. You can initialize this structure by + * calling #pj_stun_config_init() + * + * - create the STUN session:\n + * by calling #pj_stun_session_create(). Among other things, this + * function requires the instance of #pj_stun_config and also + * #pj_stun_session_cb structure which stores callbacks to send + * outgoing packets as well as to notify application about incoming + * STUN requests, responses, and indicates and other events. + * + * - configure credential:\n + * if authentication is required for the session, configure the + * credential with #pj_stun_session_set_credential() + * + * - configuring other settings:\n + * several APIs are provided to configure the behavior of the STUN + * session (for example, to set the SOFTWARE attribute value, controls + * the logging behavior, fine tune the mutex locking, etc.). Please see + * the API reference for more info. + * + * - creating outgoing STUN requests or indications:\n + * create the STUN message by using #pj_stun_session_create_req() or + * #pj_stun_session_create_ind(). This will create a transmit data + * buffer containing a blank STUN request or indication. You will then + * typically need to add STUN attributes that are relevant to the + * request or indication, but note that some default attributes will + * be added by the session later when the message is sent (such as + * SOFTWARE attribute and attributes related to authentication). + * The message is now ready to be sent. + * + * - sending outgoing message:\n + * use #pj_stun_session_send_msg() to send outgoing STUN messages (this + * includes STUN requests, indications, and responses). The function has + * options whether to retransmit the request (for non reliable transports) + * or to cache the response if we're sending response. This function in + * turn will call the \a on_send_msg() callback of #pj_stun_session_cb + * to request the application to send the packet. + * + * - handling incoming packet:\n + * call #pj_stun_session_on_rx_pkt() everytime the application receives + * a STUN packet. This function will decode the packet and process the + * packet according to the message, and normally this will cause one + * of the callback in the #pj_stun_session_cb to be called to notify + * the application about the event. + * + * - handling incoming requests:\n + * incoming requests are notified to application in the \a on_rx_request + * callback of the #pj_stun_session_cb. If authentication is enabled in + * the session, the application will only receive this callback after + * the incoming request has been authenticated (if the authentication + * fails, the session would respond automatically with 401 error and + * the callback will not be called). Application now must create and + * send response for this request. + * + * - creating and sending response:\n + * create the STUN response with #pj_stun_session_create_res(). This will + * create a transmit data buffer containing a blank STUN response. You + * will then typically need to add STUN attributes that are relevant to + * the response, but note that some default attributes will + * be added by the session later when the message is sent (such as + * SOFTWARE attribute and attributes related to authentication). + * The message is now ready to be sent. Use #pj_stun_session_send_msg() + * (as explained above) to send the response. + * + * - convenient way to send response:\n + * the #pj_stun_session_respond() is provided as a convenient way to + * create and send simple STUN responses, such as error responses. + * + * - destroying the session:\n + * once the session is done, use #pj_stun_session_destroy() to destroy + * the session. + */ + +/** Forward declaration for pj_stun_tx_data */ +typedef struct pj_stun_tx_data pj_stun_tx_data; + +/** Forward declaration for pj_stun_rx_data */ +typedef struct pj_stun_rx_data pj_stun_rx_data; + +/** Forward declaration for pj_stun_session */ +typedef struct pj_stun_session pj_stun_session; + +/** + * This is the callback to be registered to pj_stun_session, to send + * outgoing message and to receive various notifications from the STUN + * session. + */ +typedef struct pj_stun_session_cb { + /** + * Callback to be called by the STUN session to send outgoing message. + * + * @param sess The STUN session. + * @param token The token associated with this outgoing message + * and was set by the application. This token was + * set by application in pj_stun_session_send_msg() + * for outgoing messages that are initiated by the + * application, or in pj_stun_session_on_rx_pkt() + * if this message is a response that was internally + * generated by the STUN session (for example, an + * 401/Unauthorized response). Application may use + * this facility for any purposes. + * @param pkt Packet to be sent. + * @param pkt_size Size of the packet to be sent. + * @param dst_addr The destination address. + * @param addr_len Length of destination address. + * + * @return The callback should return the status of the + * packet sending. + */ + pj_status_t (*on_send_msg)(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + + /** + * Callback to be called on incoming STUN request message. This function + * is called when application calls pj_stun_session_on_rx_pkt() and when + * the STUN session has detected that the incoming STUN message is a + * STUN request message. In the + * callback processing, application MUST create a response by calling + * pj_stun_session_create_response() function and send the response + * with pj_stun_session_send_msg() function, before returning from + * the callback. + * + * @param sess The STUN session. + * @param pkt Pointer to the original STUN packet. + * @param pkt_len Length of the STUN packet. + * @param rdata Data containing incoming request message. + * @param token The token that was set by the application when + * calling pj_stun_session_on_rx_pkt() function. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the source address. + * + * @return The return value of this callback will be + * returned back to pj_stun_session_on_rx_pkt() + * function. + */ + pj_status_t (*on_rx_request)(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_rx_data *rdata, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + + /** + * Callback to be called when response is received or the transaction + * has timed out. This callback is called either when application calls + * pj_stun_session_on_rx_pkt() with the packet containing a STUN + * response for the client transaction, or when the internal timer of + * the STUN client transaction has timed-out before a STUN response is + * received. + * + * @param sess The STUN session. + * @param status Status of the request. If the value if not + * PJ_SUCCESS, the transaction has timed-out + * or other error has occurred, and the response + * argument may be NULL. + * Note that when the status is not success, the + * response may contain non-NULL value if the + * response contains STUN ERROR-CODE attribute. + * @param token The token that was set by the application when + * calling pj_stun_session_send_msg() function. + * Please not that this token IS NOT the token + * that was given in pj_stun_session_on_rx_pkt(). + * @param tdata The original STUN request. + * @param response The response message, on successful transaction, + * or otherwise MAY BE NULL if status is not success. + * Note that when the status is not success, this + * argument may contain non-NULL value if the + * response contains STUN ERROR-CODE attribute. + * @param src_addr The source address where the response was + * received, or NULL if the response is NULL. + * @param src_addr_len The length of the source address. + */ + void (*on_request_complete)(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); + + /** + * Callback to be called on incoming STUN request message. This function + * is called when application calls pj_stun_session_on_rx_pkt() and when + * the STUN session has detected that the incoming STUN message is a + * STUN indication message. + * + * @param sess The STUN session. + * @param pkt Pointer to the original STUN packet. + * @param pkt_len Length of the STUN packet. + * @param msg The parsed STUN indication. + * @param token The token that was set by the application when + * calling pj_stun_session_on_rx_pkt() function. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the source address. + * + * @return The return value of this callback will be + * returned back to pj_stun_session_on_rx_pkt() + * function. + */ + pj_status_t (*on_rx_indication)(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + +} pj_stun_session_cb; + +/** + * This structure describes incoming request message. + */ +struct pj_stun_rx_data { + /** + * The parsed request message. + */ + pj_stun_msg *msg; + + /** + * Credential information that is found and used to authenticate + * incoming request. Application may use this information when + * generating authentication for the outgoing response. + */ + pj_stun_req_cred_info info; +}; + +/** + * This structure describe the outgoing STUN transmit data to carry the + * message to be sent. + */ +struct pj_stun_tx_data { + /** PJLIB list interface */ + PJ_DECL_LIST_MEMBER(struct pj_stun_tx_data); + + pj_pool_t *pool; /**< Pool. */ + pj_stun_session *sess; /**< The STUN session. */ + pj_stun_msg *msg; /**< The STUN message. */ + pj_bool_t is_destroying; /**< Is destroying? */ + + void *token; /**< The token. */ + + pj_stun_client_tsx *client_tsx; /**< Client STUN transaction. */ + pj_bool_t retransmit; /**< Retransmit request? */ + pj_uint32_t msg_magic; /**< Message magic. */ + pj_uint8_t msg_key[12]; /**< Message/transaction key. */ + + pj_grp_lock_t *grp_lock; /**< Group lock (for resp cache). */ + + pj_stun_req_cred_info auth_info; /**< Credential info */ + + void *pkt; /**< The STUN packet. */ + unsigned max_len; /**< Length of packet buffer. */ + pj_size_t pkt_size; /**< The actual length of STUN pkt. */ + + unsigned addr_len; /**< Length of destination address. */ + const pj_sockaddr_t *dst_addr; /**< Destination address. */ + + pj_timer_entry res_timer; /**< Response cache timer. */ +}; + +/** + * These are the flags to control the message logging in the STUN session. + */ +typedef enum pj_stun_sess_msg_log_flag { + PJ_STUN_SESS_LOG_TX_REQ = 1, /**< Log outgoing STUN requests. */ + PJ_STUN_SESS_LOG_TX_RES = 2, /**< Log outgoing STUN responses. */ + PJ_STUN_SESS_LOG_TX_IND = 4, /**< Log outgoing STUN indications. */ + + PJ_STUN_SESS_LOG_RX_REQ = 8, /**< Log incoming STUN requests. */ + PJ_STUN_SESS_LOG_RX_RES = 16, /**< Log incoming STUN responses */ + PJ_STUN_SESS_LOG_RX_IND = 32 /**< Log incoming STUN indications */ +} pj_stun_sess_msg_log_flag; + +/** + * Create a STUN session. + * + * @param cfg The STUN endpoint, to be used to register timers etc. + * @param name Optional name to be associated with this instance. The + * name will be used for example for logging purpose. + * @param cb Session callback. + * @param fingerprint Enable message fingerprint for outgoing messages. + * @param grp_lock Optional group lock to be used by this session. + * If NULL, the session will create one itself. + * @param p_sess Pointer to receive STUN session instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_create(pj_stun_config *cfg, const char *name, const pj_stun_session_cb *cb, pj_bool_t fingerprint, + pj_grp_lock_t *grp_lock, pj_stun_session **p_sess); + +/** + * Destroy the STUN session and all objects created in the context of + * this session. + * + * @param sess The STUN session instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJ_EPENDING if the operation + * cannot be performed immediately because callbacks are + * being called; in this case the session will be destroyed + * as soon as the last callback returns. + */ +PJ_DECL(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess); + +/** + * Associated an arbitrary data with this STUN session. The user data may + * be retrieved later with pj_stun_session_get_user_data() function. + * + * @param sess The STUN session instance. + * @param user_data The user data. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_set_user_data(pj_stun_session *sess, void *user_data); + +/** + * Retrieve the user data previously associated to this STUN session with + * pj_stun_session_set_user_data(). + * + * @param sess The STUN session instance. + * + * @return The user data associated with this STUN session. + */ +PJ_DECL(void *) pj_stun_session_get_user_data(pj_stun_session *sess); + +/** + * Get the group lock for this STUN session. + * + * @param sess The STUN session instance. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_stun_session_get_grp_lock(pj_stun_session *sess); + +/** + * Set SOFTWARE name to be included in all requests and responses. + * + * @param sess The STUN session instance. + * @param sw Software name string. If this argument is NULL or + * empty, the session will not include SOFTWARE attribute + * in STUN requests and responses. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_set_software_name(pj_stun_session *sess, const pj_str_t *sw); + +/** + * Set credential to be used by this session. Once credential is set, all + * outgoing messages will include MESSAGE-INTEGRITY, and all incoming + * message will be authenticated against this credential. + * + * To disable authentication after it has been set, call this function + * again with NULL as the argument. + * + * @param sess The STUN session instance. + * @param auth_type Type of authentication. + * @param cred The credential to be used by this session. If NULL + * is specified, authentication will be disabled. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_set_credential(pj_stun_session *sess, pj_stun_auth_type auth_type, const pj_stun_auth_cred *cred); +/** + * Configure message logging. By default all flags are enabled. + * + * @param sess The STUN session instance. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DECL(void) pj_stun_session_set_log(pj_stun_session *sess, unsigned flags); +/** + * Configure whether the STUN session should utilize FINGERPRINT in + * outgoing messages. + * + * @param sess The STUN session instance. + * @param use Boolean for the setting. + * + * @return The previous configured value of FINGERPRINT + * utilization of the sessoin. + */ +PJ_DECL(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, pj_bool_t use); + +/** + * Create a STUN request message. After the message has been successfully + * created, application can send the message by calling + * pj_stun_session_send_msg(). + * + * @param sess The STUN session instance. + * @param msg_type The STUN request message type, from pj_stun_method_e or + * from pj_stun_msg_type. + * @param magic STUN magic, use PJ_STUN_MAGIC. + * @param tsx_id Optional transaction ID. + * @param p_tdata Pointer to receive STUN transmit data instance containing + * the request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_create_req(pj_stun_session *sess, int msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_tx_data **p_tdata); + +/** + * Create a STUN Indication message. After the message has been successfully + * created, application can send the message by calling + * pj_stun_session_send_msg(). + * + * @param sess The STUN session instance. + * @param msg_type The STUN request message type, from pj_stun_method_e or + * from pj_stun_msg_type. This function will add the + * indication bit as necessary. + * @param p_tdata Pointer to receive STUN transmit data instance containing + * the message. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess, int msg_type, pj_stun_tx_data **p_tdata); + +/** + * Create a STUN response message. After the message has been + * successfully created, application can send the message by calling + * pj_stun_session_send_msg(). Alternatively application may use + * pj_stun_session_respond() to create and send response in one function + * call. + * + * @param sess The STUN session instance. + * @param rdata The STUN request where the response is to be created. + * @param err_code Error code to be set in the response, if error response + * is to be created, according to pj_stun_status enumeration. + * This argument MUST be zero if successful response is + * to be created. + * @param err_msg Optional pointer for the error message string, when + * creating error response. If the value is NULL and the + * \a err_code is non-zero, then default error message will + * be used. + * @param p_tdata Pointer to receive the response message created. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_create_res(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned err_code, + const pj_str_t *err_msg, pj_stun_tx_data **p_tdata); + +/** + * Send STUN message to the specified destination. This function will encode + * the pj_stun_msg instance to a packet buffer, and add credential or + * fingerprint if necessary. If the message is a request, the session will + * also create and manage a STUN client transaction to be used to manage the + * retransmission of the request. After the message has been encoded and + * transaction is setup, the \a on_send_msg() callback of pj_stun_session_cb + * (which is registered when the STUN session is created) will be called + * to actually send the message to the wire. + * + * @param sess The STUN session instance. + * @param token Optional token which will be given back to application in + * \a on_send_msg() callback and \a on_request_complete() + * callback, if the message is a STUN request message. + * Internally this function will put the token in the + * \a token field of pj_stun_tx_data, hence it will + * overwrite any value that the application puts there. + * @param cache_res If the message is a response message for an incoming + * request, specify PJ_TRUE to instruct the STUN session + * to cache this response for subsequent incoming request + * retransmission. Otherwise this parameter will be ignored + * for non-response message. + * @param retransmit If the message is a request message, specify whether the + * request should be retransmitted. Normally application will + * specify TRUE if the underlying transport is UDP and FALSE + * if the underlying transport is TCP or TLS. + * @param dst_addr The destination socket address. + * @param addr_len Length of destination address. + * @param tdata The STUN transmit data containing the STUN message to + * be sent. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in + * \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_send_msg(pj_stun_session *sess, void *token, pj_bool_t cache_res, pj_bool_t retransmit, + const pj_sockaddr_t *dst_addr, unsigned addr_len, pj_stun_tx_data *tdata); + +/** + * This is a utility function to create and send response for an incoming + * STUN request. Internally this function calls pj_stun_session_create_res() + * and pj_stun_session_send_msg(). It is provided here as a matter of + * convenience. + * + * @param sess The STUN session instance. + * @param rdata The STUN request message to be responded. + * @param code Error code to be set in the response, if error response + * is to be created, according to pj_stun_status enumeration. + * This argument MUST be zero if successful response is + * to be created. + * @param err_msg Optional pointer for the error message string, when + * creating error response. If the value is NULL and the + * \a err_code is non-zero, then default error message will + * be used. + * @param token Optional token which will be given back to application in + * \a on_send_msg() callback and \a on_request_complete() + * callback, if the message is a STUN request message. + * Internally this function will put the token in the + * \a token field of pj_stun_tx_data, hence it will + * overwrite any value that the application puts there. + * @param cache Specify whether session should cache this response for + * future request retransmission. If TRUE, subsequent request + * retransmission will be handled by the session and it + * will not call request callback. + * @param dst_addr Destination address of the response (or equal to the + * source address of the original request). + * @param addr_len Address length. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in + * \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_respond(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned code, const char *err_msg, + void *token, pj_bool_t cache, const pj_sockaddr_t *dst_addr, unsigned addr_len); + +/** + * Cancel outgoing STUN transaction. This operation is only valid for outgoing + * STUN request, to cease retransmission of the request and destroy the + * STUN client transaction that is used to send the request. + * + * @param sess The STUN session instance. + * @param tdata The request message previously sent. + * @param notify Specify whether \a on_request_complete() callback should + * be called. + * @param status If \a on_request_complete() callback is to be called, + * specify the error status to be given when calling the + * callback. This error status MUST NOT be PJ_SUCCESS. + * + * @return PJ_SUCCESS if transaction is successfully cancelled. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in + * \a on_request_complete() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_cancel_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t notify, pj_status_t status); + +/** + * Explicitly request retransmission of the request. Normally application + * doesn't need to do this, but this functionality is needed by ICE to + * speed up connectivity check completion. + * + * @param sess The STUN session instance. + * @param tdata The request message previously sent. + * @param mod_count Boolean flag to indicate whether transmission count + * needs to be incremented. + * + * @return PJ_SUCCESS on success, or the appropriate error. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in \a on_send_msg() + * callback. + */ +PJ_DECL(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t mod_count); + +/** + * Application must call this function to notify the STUN session about + * the arrival of STUN packet. The STUN packet MUST have been checked + * first with #pj_stun_msg_check() to verify that this is indeed a valid + * STUN packet. + * + * The STUN session will decode the packet into pj_stun_msg, and process + * the message accordingly. If the message is a response, it will search + * through the outstanding STUN client transactions for a matching + * transaction ID and hand over the response to the transaction. + * + * On successful message processing, application will be notified about + * the message via one of the pj_stun_session_cb callback. + * + * @param sess The STUN session instance. + * @param packet The packet containing STUN message. + * @param pkt_size Size of the packet. + * @param options Options, from #pj_stun_decode_options. + * @param parsed_len Optional pointer to receive the size of the parsed + * STUN message (useful if packet is received via a + * stream oriented protocol). + * @param token Optional token which will be given back to application + * in the \a on_rx_request(), \a on_rx_indication() and + * \a on_send_msg() callbacks. The token can be used to + * associate processing or incoming request or indication + * with some context. + * @param src_addr The source address of the packet, which will also + * be given back to application callbacks, along with + * source address length. + * @param src_addr_len Length of the source address. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in one of the + * callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_on_rx_pkt(pj_stun_session *sess, const void *packet, pj_size_t pkt_size, unsigned options, void *token, + pj_size_t *parsed_len, const pj_sockaddr_t *src_addr, unsigned src_addr_len); + +/** + * Destroy the transmit data. Call this function only when tdata has been + * created but application doesn't want to send the message (perhaps + * because of other error). + * + * @param sess The STUN session. + * @param tdata The transmit data. + * + */ +PJ_DECL(void) pj_stun_msg_destroy_tdata(pj_stun_session *sess, pj_stun_tx_data *tdata); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_SESSION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h new file mode 100755 index 000000000..a1ae34dbe --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_SOCK_H__ +#define __PJNATH_STUN_SOCK_H__ + +/** + * @file stun_sock.h + * @brief STUN aware socket transport + */ +#include +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @addtogroup PJNATH_STUN_SOCK + * @{ + * + * The STUN transport provides asynchronous UDP like socket transport + * with the additional STUN capability. It has the following features: + * + * - API to send and receive UDP packets + * + * - multiplex STUN and non-STUN incoming packets and distinguish between + * STUN responses that belong to internal requests with application data + * (the application data may be STUN packets as well) + * + * - DNS SRV resolution to the STUN server (if wanted), along with fallback + * to DNS A resolution if SRV record is not found. + * + * - STUN keep-alive maintenance, and handle changes to the mapped address + * (when the NAT binding changes) + * + */ + +/** + * Opaque type to represent a STUN transport. + */ +typedef struct pj_stun_sock pj_stun_sock; + +/** + * Types of operation being reported in \a on_status() callback of + * pj_stun_sock_cb. Application may retrieve the string representation + * of these constants with pj_stun_sock_op_name(). + */ +typedef enum pj_stun_sock_op { + /** + * Asynchronous DNS resolution. + */ + PJ_STUN_SOCK_DNS_OP = 1, + + /** + * Initial STUN Binding request. + */ + PJ_STUN_SOCK_BINDING_OP, + + /** + * Subsequent STUN Binding request for keeping the binding + * alive. + */ + PJ_STUN_SOCK_KEEP_ALIVE_OP, + + /** + * IP address change notification from the keep-alive operation. + */ + PJ_STUN_SOCK_MAPPED_ADDR_CHANGE + +} pj_stun_sock_op; + +/** + * This structure contains callbacks that will be called by the STUN + * transport to notify application about various events. + */ +typedef struct pj_stun_sock_cb { + /** + * Notification when incoming packet has been received. + * + * @param stun_sock The STUN transport. + * @param data The packet. + * @param data_len Length of the packet. + * @param src_addr The source address of the packet. + * @param addr_len The length of the source address. + * + * @return Application should normally return PJ_TRUE to let + * the STUN transport continue its operation. However + * it must return PJ_FALSE if it has destroyed the + * STUN transport in this callback. + */ + pj_bool_t (*on_rx_data)(pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, + unsigned addr_len); + + /** + * Notifification when asynchronous send operation has completed. + * + * @param stun_sock The STUN transport. + * @param send_key The send operation key that was given in + * #pj_stun_sock_sendto(). + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application should normally return PJ_TRUE to let + * the STUN transport continue its operation. However + * it must return PJ_FALSE if it has destroyed the + * STUN transport in this callback. + */ + pj_bool_t (*on_data_sent)(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + + /** + * Notification when the status of the STUN transport has changed. This + * callback may be called for the following conditions: + * - the first time the publicly mapped address has been resolved from + * the STUN server, this callback will be called with \a op argument + * set to PJ_STUN_SOCK_BINDING_OP \a status argument set to + * PJ_SUCCESS. + * - anytime when the transport has detected that the publicly mapped + * address has changed, this callback will be called with \a op + * argument set to PJ_STUN_SOCK_KEEP_ALIVE_OP and \a status + * argument set to PJ_SUCCESS. On this case and the case above, + * application will get the resolved public address in the + * #pj_stun_sock_info structure. + * - for any terminal error (such as STUN time-out, DNS resolution + * failure, or keep-alive failure), this callback will be called + * with the \a status argument set to non-PJ_SUCCESS. + * + * @param stun_sock The STUN transport. + * @param op The operation that triggers the callback. + * @param status The status. + * + * @return Must return PJ_FALSE if it has destroyed the + * STUN transport in this callback. Application should + * normally destroy the socket and return PJ_FALSE + * upon encountering terminal error, otherwise it + * should return PJ_TRUE to let the STUN socket operation + * continues. + */ + pj_bool_t (*on_status)(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status); + +} pj_stun_sock_cb; + +/** + * This structure contains information about the STUN transport. Application + * may query this information by calling #pj_stun_sock_get_info(). + */ +typedef struct pj_stun_sock_info { + /** + * The bound address of the socket. + */ + pj_sockaddr bound_addr; + + /** + * IP address of the STUN server. + */ + pj_sockaddr srv_addr; + + /** + * The publicly mapped address. It may contain zero address when the + * mapped address has not been resolved. Application may query whether + * this field contains valid address with pj_sockaddr_has_addr(). + */ + pj_sockaddr mapped_addr; + + /** + * Number of interface address aliases. The interface address aliases + * are list of all interface addresses in this host. + */ + unsigned alias_cnt; + + /** + * Array of interface address aliases. + */ + pj_sockaddr aliases[PJ_ICE_ST_MAX_CAND]; + +} pj_stun_sock_info; + +/** + * This describe the settings to be given to the STUN transport during its + * creation. Application should initialize this structure by calling + * #pj_stun_sock_cfg_default(). + */ +typedef struct pj_stun_sock_cfg { + /** + * The group lock to be used by the STUN socket. If NULL, the STUN socket + * will create one internally. + * + * Default: NULL + */ + pj_grp_lock_t *grp_lock; + + /** + * Packet buffer size. + * + * Default value is PJ_STUN_SOCK_PKT_LEN. + */ + unsigned max_pkt_size; + + /** + * Specify the number of simultaneous asynchronous read operations to + * be invoked to the ioqueue. Having more than one read operations will + * increase performance on multiprocessor systems since the application + * will be able to process more than one incoming packets simultaneously. + * Default value is 1. + */ + unsigned async_cnt; + + /** + * Specify the interface where the socket should be bound to. If the + * address is zero, socket will be bound to INADDR_ANY. If the address + * is non-zero, socket will be bound to this address only, and the + * transport will have only one address alias (the \a alias_cnt field + * in #pj_stun_sock_info structure. If the port is set to zero, the + * socket will bind at any port (chosen by the OS). + */ + pj_sockaddr bound_addr; + + /** + * Specify the port range for STUN socket binding, relative to the start + * port number specified in \a bound_addr. Note that this setting is only + * applicable when the start port number is non zero. + * + * Default value is zero. + */ + pj_uint16_t port_range; + + /** + * Specify the STUN keep-alive duration, in seconds. The STUN transport + * does keep-alive by sending STUN Binding request to the STUN server. + * If this value is zero, the PJ_STUN_KEEP_ALIVE_SEC value will be used. + * If the value is negative, it will disable STUN keep-alive. + */ + int ka_interval; + + /** + * QoS traffic type to be set on this transport. When application wants + * to apply QoS tagging to the transport, it's preferable to set this + * field rather than \a qos_param fields since this is more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a lower + * level operation than setting the \a qos_type field and may not be + * supported on all platforms. + * + * By default all settings in this structure are disabled. + */ + pj_qos_params qos_params; + + /** + * Specify if STUN socket should ignore any errors when setting the QoS + * traffic type/parameters. + * + * Default: PJ_TRUE + */ + pj_bool_t qos_ignore_error; + + /** + * Specify target value for socket receive buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible is + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_rcvbuf_size; + + /** + * Specify target value for socket send buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible is + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_sndbuf_size; + +} pj_stun_sock_cfg; + +/** + * Retrieve the name representing the specified operation. + */ +PJ_DECL(const char *) pj_stun_sock_op_name(pj_stun_sock_op op); + +/** + * Initialize the STUN transport setting with its default values. + * + * @param cfg The STUN transport config. + */ +PJ_DECL(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg); + +/** + * Create the STUN transport using the specified configuration. Once + * the STUN transport has been create, application should call + * #pj_stun_sock_start() to start the transport. + * + * @param stun_cfg The STUN configuration which contains among other + * things the ioqueue and timer heap instance for + * the operation of this transport. + * @param af Address family of socket. Currently pj_AF_INET() + * and pj_AF_INET6() are supported. + * @param name Optional name to be given to this transport to + * assist debugging. + * @param cb Callback to receive events/data from the transport. + * @param cfg Optional transport settings. + * @param user_data Arbitrary application data to be associated with + * this transport. + * @param p_sock Pointer to receive the created transport instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_stun_sock_create(pj_stun_config *stun_cfg, const char *name, int af, const pj_stun_sock_cb *cb, + const pj_stun_sock_cfg *cfg, void *user_data, pj_stun_sock **p_sock); + +/** + * Start the STUN transport. This will start the DNS SRV resolution for + * the STUN server (if desired), and once the server is resolved, STUN + * Binding request will be sent to resolve the publicly mapped address. + * Once the initial STUN Binding response is received, the keep-alive + * timer will be started. + * + * @param stun_sock The STUN transport instance. + * @param domain The domain, hostname, or IP address of the STUN + * server. When this parameter contains domain name, + * the \a resolver parameter must be set to activate + * DNS SRV resolution. + * @param default_port The default STUN port number to use when DNS SRV + * resolution is not used. If DNS SRV resolution is + * used, the server port number will be set from the + * DNS SRV records. The recommended value for this + * parameter is PJ_STUN_PORT. + * @param resolver If this parameter is not NULL, then the \a domain + * parameter will be first resolved with DNS SRV and + * then fallback to using DNS A/AAAA resolution when + * DNS SRV resolution fails. If this parameter is + * NULL, the \a domain parameter will be resolved as + * hostname. + * + * @return PJ_SUCCESS if the operation has been successfully + * queued, or the appropriate error code on failure. + * When this function returns PJ_SUCCESS, the final + * result of the allocation process will be notified + * to application in \a on_status() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_sock_start(pj_stun_sock *stun_sock, const pj_str_t *domain, pj_uint16_t default_port, + pj_dns_resolver *resolver); + +/** + * Destroy the STUN transport. + * + * @param sock The STUN transport socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *sock); + +/** + * Associate a user data with this STUN transport. The user data may then + * be retrieved later with #pj_stun_sock_get_user_data(). + * + * @param stun_sock The STUN transport instance. + * @param user_data Arbitrary data. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_stun_sock_set_user_data(pj_stun_sock *stun_sock, void *user_data); + +/** + * Retrieve the previously assigned user data associated with this STUN + * transport. + * + * @param stun_sock The STUN transport instance. + * + * @return The user/application data. + */ +PJ_DECL(void *) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock); + +/** + * Get the group lock for this STUN transport. + * + * @param stun_sock The STUN transport instance. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_stun_sock_get_grp_lock(pj_stun_sock *stun_sock); + +/** + * Get the STUN transport info. The transport info contains, among other + * things, the allocated relay address. + * + * @param stun_sock The STUN transport instance. + * @param info Pointer to be filled with STUN transport info. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_stun_sock_get_info(pj_stun_sock *stun_sock, pj_stun_sock_info *info); + +/** + * Send a data to the specified address. This function may complete + * asynchronously and in this case \a on_data_sent() will be called. + * + * @param stun_sock The STUN transport instance. + * @param send_key Optional send key for sending the packet down to + * the ioqueue. This value will be given back to + * \a on_data_sent() callback + * @param pkt The data/packet to be sent to peer. + * @param pkt_len Length of the data. + * @param flag pj_ioqueue_sendto() flag. + * @param dst_addr The remote address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_stun_sock_sendto(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, const void *pkt, unsigned pkt_len, + unsigned flag, const pj_sockaddr_t *dst_addr, unsigned addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h new file mode 100755 index 000000000..78899b176 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_TRANSACTION_H__ +#define __PJNATH_STUN_TRANSACTION_H__ + +/** + * @file stun_transaction.h + * @brief STUN transaction + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_TRANSACTION STUN Client Transaction + * @brief STUN client transaction + * @ingroup PJNATH_STUN_BASE + * @{ + * + The @ref PJNATH_STUN_TRANSACTION is used to manage outgoing STUN request, + for example to retransmit the request and to notify application about the + completion of the request. + + The @ref PJNATH_STUN_TRANSACTION does not use any networking operations, + but instead application must supply the transaction with a callback to + be used by the transaction to send outgoing requests. This way the STUN + transaction is made more generic and can work with different types of + networking codes in application. + + + */ + +/** + * Opaque declaration of STUN client transaction. + */ +typedef struct pj_stun_client_tsx pj_stun_client_tsx; + +/** + * STUN client transaction callback. + */ +typedef struct pj_stun_tsx_cb { + /** + * This callback is called when the STUN transaction completed. + * + * @param tsx The STUN transaction. + * @param status Status of the transaction. Status PJ_SUCCESS + * means that the request has received a successful + * response. + * @param response The STUN response, which value may be NULL if + * \a status is not PJ_SUCCESS. + * @param src_addr The source address of the response, if response + * is not NULL. + * @param src_addr_len The length of the source address. + */ + void (*on_complete)(pj_stun_client_tsx *tsx, pj_status_t status, const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + + /** + * This callback is called by the STUN transaction when it wants to send + * outgoing message. + * + * @param tsx The STUN transaction instance. + * @param stun_pkt The STUN packet to be sent. + * @param pkt_size Size of the STUN packet. + * + * @return If return value of the callback is not PJ_SUCCESS, + * the transaction will fail. Application MUST return + * PJNATH_ESTUNDESTROYED if it has destroyed the + * transaction in this callback. + */ + pj_status_t (*on_send_msg)(pj_stun_client_tsx *tsx, const void *stun_pkt, pj_size_t pkt_size); + + /** + * This callback is called after the timer that was scheduled by + * #pj_stun_client_tsx_schedule_destroy() has elapsed. Application + * should call #pj_stun_client_tsx_destroy() upon receiving this + * callback. + * + * This callback is optional if application will not call + * #pj_stun_client_tsx_schedule_destroy(). + * + * @param tsx The STUN transaction instance. + */ + void (*on_destroy)(pj_stun_client_tsx *tsx); + +} pj_stun_tsx_cb; + +/** + * Create an instance of STUN client transaction. The STUN client + * transaction is used to transmit outgoing STUN request and to + * ensure the reliability of the request by periodically retransmitting + * the request, if necessary. + * + * @param cfg The STUN endpoint, which will be used to retrieve + * various settings for the transaction. + * @param pool Pool to be used to allocate memory from. + * @param grp_lock Group lock to synchronize. + * @param cb Callback structure, to be used by the transaction + * to send message and to notify the application about + * the completion of the transaction. + * @param p_tsx Pointer to receive the transaction instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_create(pj_stun_config *cfg, pj_pool_t *pool, pj_grp_lock_t *grp_lock, const pj_stun_tsx_cb *cb, + pj_stun_client_tsx **p_tsx); + +/** + * Schedule timer to destroy the transaction after the transaction is + * complete. Application normally calls this function in the on_complete() + * callback. When this timer elapsed, the on_destroy() callback will be + * called. + * + * This is convenient to let the STUN transaction absorbs any response + * for the previous request retransmissions. If application doesn't want + * this, it can destroy the transaction immediately by calling + * #pj_stun_client_tsx_destroy(). + * + * @param tsx The STUN transaction. + * @param delay The delay interval before on_destroy() callback + * is called. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_schedule_destroy(pj_stun_client_tsx *tsx, const pj_time_val *delay); + +/** + * Destroy the STUN transaction immediately after the transaction is complete. + * Application normally calls this function in the on_complete() callback. + * + * @param tsx The STUN transaction. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx); + +/** + * Stop the client transaction. + * + * @param tsx The STUN transaction. + * + * @return PJ_SUCCESS on success or PJ_EINVAL if the parameter + * is NULL. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_stop(pj_stun_client_tsx *tsx); + +/** + * Check if transaction has completed. + * + * @param tsx The STUN transaction. + * + * @return Non-zero if transaction has completed. + */ +PJ_DECL(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx); + +/** + * Associate an arbitrary data with the STUN transaction. This data + * can be then retrieved later from the transaction, by using + * pj_stun_client_tsx_get_data() function. + * + * @param tsx The STUN client transaction. + * @param data Application data to be associated with the + * STUN transaction. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx, void *data); + +/** + * Get the user data that was previously associated with the STUN + * transaction. + * + * @param tsx The STUN client transaction. + * + * @return The user data. + */ +PJ_DECL(void *) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx); + +/** + * Start the STUN client transaction by sending STUN request using + * this transaction. If reliable transport such as TCP or TLS is used, + * the retransmit flag should be set to PJ_FALSE because reliablity + * will be assured by the transport layer. + * + * @param tsx The STUN client transaction. + * @param retransmit Should this message be retransmitted by the + * STUN transaction. + * @param pkt The STUN packet to send. + * @param pkt_len Length of STUN packet. + * + * @return PJ_SUCCESS on success, or PJNATH_ESTUNDESTROYED + * when the user has destroyed the transaction in + * \a on_send_msg() callback, or any other error code + * as returned by \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx, pj_bool_t retransmit, void *pkt, unsigned pkt_len); + +/** + * Request to retransmit the request. Normally application should not need + * to call this function since retransmission would be handled internally, + * but this functionality is needed by ICE. + * + * @param tsx The STUN client transaction instance. + * @param mod_count Boolean flag to indicate whether transmission count + * needs to be incremented. + * + * @return PJ_SUCCESS on success, or PJNATH_ESTUNDESTROYED + * when the user has destroyed the transaction in + * \a on_send_msg() callback, or any other error code + * as returned by \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx, pj_bool_t mod_count); + +/** + * Notify the STUN transaction about the arrival of STUN response. + * If the STUN response contains a final error (300 and greater), the + * transaction will be terminated and callback will be called. If the + * STUN response contains response code 100-299, retransmission + * will cease, but application must still call this function again + * with a final response later to allow the transaction to complete. + * + * @param tsx The STUN client transaction instance. + * @param msg The incoming STUN message. + * @param src_addr The source address of the packet. + * @param src_addr_len The length of the source address. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, const pj_stun_msg *msg, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_TRANSACTION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h new file mode 100755 index 000000000..e719b7b37 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h @@ -0,0 +1,862 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TURN_SESSION_H__ +#define __PJNATH_TURN_SESSION_H__ + +/** + * @file turn_session.h + * @brief Transport independent TURN client session. + */ +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** +@addtogroup PJNATH_TURN_SESSION +@{ + +The \ref PJNATH_TURN_SESSION is a transport-independent object to +manage a client TURN session. It contains the core logic for manage +the TURN client session as listed in \ref turn_op_sec, but +in transport-independent manner (i.e. it doesn't have a socket), so +that developer can integrate TURN client functionality into existing +framework that already has its own means to send and receive data, +or to support new transport types to TURN, such as TLS. + + +\section turn_sess_using_sec Using the TURN session + +These steps describes how to use the TURN session: + + - Creating the session:\n + use #pj_turn_session_create() to create the session. + + - Configuring credential:\n + all TURN operations requires the use of authentication (it uses STUN + long term autentication method). Use #pj_turn_session_set_credential() + to configure the TURN credential to be used by the session. + + - Configuring server:\n + application must call #pj_turn_session_set_server() before it can send + Allocate request (with pj_turn_session_alloc()). This function will + resolve the TURN server using DNS SRV resolution if the \a resolver + is set. The server resolution process will complete asynchronously, + and application will be notified in \a on_state() callback of the + #pj_turn_session_cb structurewith the session state set to + PJ_TURN_STATE_RESOLVED. + + - Creating allocation:\n + create one "relay port" (or called relayed-transport-address + in TURN terminology) in the TURN server by using #pj_turn_session_alloc(). + This will send Allocate request to the server. This function will complete + immediately, and application will be notified about the allocation + result in the \a on_state() callback of the #pj_turn_session_cb structure. + + - Getting the allocation result:\n + if allocation is successful, the session state will progress to + \a PJ_TURN_STATE_READY, otherwise the state will be + \a PJ_TURN_STATE_DEALLOCATED or higher. Session state progression is + reported in the \a on_state() callback of the #pj_turn_session_cb + structure. On successful allocation, application may retrieve the + allocation info by calling #pj_turn_session_get_info(). + + - Sending data through the relay.\n + Once allocation has been created, client may send data to any remote + endpoints (called peers in TURN terminology) via the "relay port". It does + so by calling #pj_turn_session_sendto(), giving the peer address + in the function argument. But note that at this point peers are not allowed + to send data towards the client (via the "relay port") before permission is + installed for that peer. + + - Creating permissions.\n + Permission needs to be created in the TURN server so that a peer can send + data to the client via the relay port (a peer in this case is identified by + its IP address). Without this, when the TURN server receives data from the + peer in the "relay port", it will drop this data. Create the permission by + calling #pj_turn_session_set_perm(), specifying the peer IP address in the + argument (the port part of the address is ignored). More than one IP + addresses may be specified. + + - Receiving data from peers.\n + Once permission has been installed for the peer, any data received by the + TURN server (from that peer) in the "relay port" will be relayed back to + client by the server, and application will be notified via \a on_rx_data + callback of the #pj_turn_session_cb. + + - Using ChannelData.\n + TURN provides optimized framing to the data by using ChannelData + packetization. The client activates this format for the specified peer by + calling #pj_turn_session_bind_channel(). Data sent or received to/for + this peer will then use ChannelData format instead of Send or Data + Indications. + + - Refreshing the allocation, permissions, and channel bindings.\n + Allocations, permissions, and channel bindings will be refreshed by the + session automatically when they about to expire. + + - Destroying the allocation.\n + Once the "relay port" is no longer needed, client destroys the allocation + by calling #pj_turn_session_shutdown(). This function will return + immediately, and application will be notified about the deallocation + result in the \a on_state() callback of the #pj_turn_session_cb structure. + Once the state has reached PJ_TURN_STATE_DESTROYING, application must + assume that the session will be destroyed shortly after. + + */ + +/** + * Opaque declaration for TURN client session. + */ +typedef struct pj_turn_session pj_turn_session; + +/** + * TURN transport types, which will be used both to specify the connection + * type for reaching TURN server and the type of allocation transport to be + * requested to server (the REQUESTED-TRANSPORT attribute). + */ +typedef enum pj_turn_tp_type { + /** + * UDP transport, which value corresponds to IANA protocol number. + */ + PJ_TURN_TP_UDP = 17, + + /** + * TCP transport, which value corresponds to IANA protocol number. + */ + PJ_TURN_TP_TCP = 6, + + /** + * TLS transport. The TLS transport will only be used as the connection + * type to reach the server and never as the allocation transport type. + * The value corresponds to IANA protocol number. + */ + PJ_TURN_TP_TLS = 56 + +} pj_turn_tp_type; + +/** TURN session state */ +typedef enum pj_turn_state_t { + /** + * TURN session has just been created. + */ + PJ_TURN_STATE_NULL, + + /** + * TURN server has been configured and now is being resolved via + * DNS SRV resolution. + */ + PJ_TURN_STATE_RESOLVING, + + /** + * TURN server has been resolved. If there is pending allocation to + * be done, it will be invoked immediately. + */ + PJ_TURN_STATE_RESOLVED, + + /** + * TURN session has issued ALLOCATE request and is waiting for response + * from the TURN server. + */ + PJ_TURN_STATE_ALLOCATING, + + /** + * TURN session has successfully allocated relay resoruce and now is + * ready to be used. + */ + PJ_TURN_STATE_READY, + + /** + * TURN session has issued deallocate request and is waiting for a + * response from the TURN server. + */ + PJ_TURN_STATE_DEALLOCATING, + + /** + * Deallocate response has been received. Normally the session will + * proceed to DESTROYING state immediately. + */ + PJ_TURN_STATE_DEALLOCATED, + + /** + * TURN session is being destroyed. + */ + PJ_TURN_STATE_DESTROYING + +} pj_turn_state_t; + +#pragma pack(1) + +/** + * This structure ChannelData header. All the fields are in network byte + * order when it's on the wire. + */ +typedef struct pj_turn_channel_data { + pj_uint16_t ch_number; /**< Channel number. */ + pj_uint16_t length; /**< Payload length. */ +} pj_turn_channel_data; + +#pragma pack() + +/** + * Callback to receive events from TURN session. + */ +typedef struct pj_turn_session_cb { + /** + * This callback will be called by the TURN session whenever it + * needs to send data or outgoing messages. Since the TURN session + * doesn't have a socket on its own, this callback must be implemented. + * + * If the callback \a on_stun_send_pkt() is implemented, outgoing + * messages will use that callback instead. + * + * @param sess The TURN session. + * @param pkt The packet/data to be sent. + * @param pkt_len Length of the packet/data. + * @param dst_addr Destination address of the packet. + * @param addr_len Length of the destination address. + * + * @return The callback should return the status of the + * send operation. + */ + pj_status_t (*on_send_pkt)(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + + /** + * This callback will be called by the TURN session whenever it + * needs to send outgoing STUN requests/messages for TURN signalling + * purposes (data sending will not invoke this callback). If this + * callback is not implemented, the callback \a on_send_pkt() + * will be called instead. + * + * @param sess The TURN session. + * @param pkt The packet/data to be sent. + * @param pkt_len Length of the packet/data. + * @param dst_addr Destination address of the packet. + * @param addr_len Length of the destination address. + * + * @return The callback should return the status of the + * send operation. + */ + pj_status_t (*on_stun_send_pkt)(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + + /** + * Notification when peer address has been bound successfully to + * a channel number. + * + * This callback is optional since the nature of this callback is + * for information only. + * + * @param sess The TURN session. + * @param peer_addr The peer address. + * @param addr_len Length of the peer address. + * @param ch_num The channel number associated with this peer address. + */ + void (*on_channel_bound)(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len, unsigned ch_num); + + /** + * Notification when incoming data has been received, either through + * Data indication or ChannelData message from the TURN server. + * + * @param sess The TURN session. + * @param pkt The data/payload of the Data Indication or ChannelData + * packet. + * @param pkt_len Length of the data/payload. + * @param peer_addr Peer address where this payload was received by + * the TURN server. + * @param addr_len Length of the peer address. + */ + void (*on_rx_data)(pj_turn_session *sess, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notification when TURN session state has changed. Application should + * implement this callback at least to know that the TURN session is + * going to be destroyed. + * + * @param sess The TURN session. + * @param old_state The previous state of the session. + * @param new_state The current state of the session. + */ + void (*on_state)(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state); + + /** + * Notification when TURN client received a ConnectionAttempt Indication + * from the TURN server, which indicates that peer initiates a TCP + * connection to allocated slot in the TURN server. Application must + * implement this callback if it uses RFC 6062 (TURN TCP allocations). + * + * After receiving this callback, application should establish a new TCP + * connection to the TURN server and send ConnectionBind request (using + * pj_turn_session_connection_bind()). After the connection binding + * succeeds, this new connection will become a data only connection. + * + * @param sess The TURN session. + * @param conn_id The connection ID assigned by TURN server. + * @param peer_addr Peer address that tried to connect to the TURN server. + * @param addr_len Length of the peer address. + */ + void (*on_connection_attempt)(pj_turn_session *sess, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notification for ConnectionBind request sent using + * pj_turn_session_connection_bind(). + * + * @param sess The TURN session. + * @param status The status code. + * @param conn_id The connection ID. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + */ + void (*on_connection_bind_status)(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + + /** + * Notification for Connect request sent using + * pj_turn_session_connect(). + * + * @param sess The TURN session. + * @param status The status code. + * @param conn_id The connection ID. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + */ + void (*on_connect_complete)(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + +} pj_turn_session_cb; + +/** + * Allocation parameter, which can be given when application calls + * pj_turn_session_alloc() to allocate relay address in the TURN server. + * Application should call pj_turn_alloc_param_default() to initialize + * this structure with the default values. + */ +typedef struct pj_turn_alloc_param { + /** + * The requested BANDWIDTH. Default is zero to not request any + * specific bandwidth. Note that this attribute has been deprecated + * after TURN-08 draft, hence application should only use this + * attribute when talking to TURN-07 or older version. + */ + int bandwidth; + + /** + * The requested LIFETIME. Default is zero to not request any + * explicit allocation lifetime. + */ + int lifetime; + + /** + * If set to non-zero, the TURN session will periodically send blank + * Send Indication every PJ_TURN_KEEP_ALIVE_SEC to refresh local + * NAT bindings. Default is zero. + */ + int ka_interval; + + /** + * The requested ADDRESS-FAMILY. Default is zero to request relay with + * address family matched to the one specified in TURN session creation. + * Valid values are zero, pj_AF_INET(), and pj_AF_INET6(). + * + * Default value is zero. + */ + int af; + + /** + * Type of connection to from TURN server to peer. Supported values are + * PJ_TURN_TP_UDP (RFC 5766) and PJ_TURN_TP_TCP (RFC 6062) + * + * Default is PJ_TURN_TP_UDP. + */ + pj_turn_tp_type peer_conn_type; + +} pj_turn_alloc_param; + +/** + * This structure describes TURN session info. + */ +typedef struct pj_turn_session_info { + /** + * Session state. + */ + pj_turn_state_t state; + + /** + * Last error (if session was terminated because of error) + */ + pj_status_t last_status; + + /** + * Type of connection to the TURN server. + */ + pj_turn_tp_type conn_type; + + /** + * The selected TURN server address. + */ + pj_sockaddr server; + + /** + * Mapped address, as reported by the TURN server. + */ + pj_sockaddr mapped_addr; + + /** + * The relay address + */ + pj_sockaddr relay_addr; + + /** + * Current seconds before allocation expires. + */ + int lifetime; + +} pj_turn_session_info; + +/** + * Parameters for function pj_turn_session_on_rx_pkt2(). + */ +typedef struct pj_turn_session_on_rx_pkt_param { + /** + * The packet as received from the TURN server. This should contain + * either STUN encapsulated message or a ChannelData packet. + */ + void *pkt; + + /** + * The length of the packet. + */ + pj_size_t pkt_len; + + /** + * The number of parsed or processed data from the packet. + */ + pj_size_t parsed_len; + + /** + * Source address where the packet is received from. + */ + const pj_sockaddr_t *src_addr; + + /** + * Length of the source address. + */ + unsigned src_addr_len; + +} pj_turn_session_on_rx_pkt_param; + +/** + * Initialize pj_turn_alloc_param with the default values. + * + * @param prm The TURN allocation parameter to be initialized. + */ +PJ_DECL(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm); + +/** + * Duplicate pj_turn_alloc_param. + * + * @param pool Pool to allocate memory (currently not used) + * @param dst Destination parameter. + * @param src Source parameter. + */ +PJ_DECL(void) pj_turn_alloc_param_copy(pj_pool_t *pool, pj_turn_alloc_param *dst, const pj_turn_alloc_param *src); + +/** + * Get string representation for the given TURN state. + * + * @param state The TURN session state. + * + * @return The state name as NULL terminated string. + */ +PJ_DECL(const char *) pj_turn_state_name(pj_turn_state_t state); + +/** + * Create a TURN session instance with the specified address family and + * connection type. Once TURN session instance is created, application + * must call pj_turn_session_alloc() to allocate a relay address in the TURN + * server. + * + * @param cfg The STUN configuration which contains among other + * things the ioqueue and timer heap instance for + * the operation of this session. + * @param name Optional name to identify this session in the log. + * @param af Address family of the client connection. Currently + * pj_AF_INET() and pj_AF_INET6() are supported. + * @param conn_type Connection type to the TURN server. + * @param grp_lock Optional group lock object to be used by this session. + * If this value is NULL, the session will create + * a group lock internally. + * @param cb Callback to receive events from the TURN session. + * @param options Option flags, currently this value must be zero. + * @param user_data Arbitrary application data to be associated with + * this transport. + * @param p_sess Pointer to receive the created instance of the + * TURN session. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_turn_session_create(const pj_stun_config *cfg, const char *name, int af, pj_turn_tp_type conn_type, + pj_grp_lock_t *grp_lock, const pj_turn_session_cb *cb, unsigned options, void *user_data, + pj_turn_session **p_sess); + +/** + * Shutdown TURN client session. This will gracefully deallocate and + * destroy the client session. + * + * @param sess The TURN client session. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess); + +/** + * Forcefully destroy the TURN session. This will destroy the session + * immediately. If there is an active allocation, the server will not + * be notified about the client destruction. + * + * @param sess The TURN client session. + * @param last_err Optional error code to be set to the session, + * which would be returned back in the \a info + * parameter of #pj_turn_session_get_info(). If + * this argument value is PJ_SUCCESS, the error + * code will not be set. If the session already + * has an error code set, this function will not + * overwrite that error code either. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_destroy(pj_turn_session *sess, pj_status_t last_err); + +/** + * Get the information about this TURN session and the allocation, if + * any. + * + * @param sess The TURN client session. + * @param info The structure to be initialized with the TURN + * session info. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_get_info(pj_turn_session *sess, pj_turn_session_info *info); + +/** + * Associate a user data with this TURN session. The user data may then + * be retrieved later with pj_turn_session_get_user_data(). + * + * @param sess The TURN client session. + * @param user_data Arbitrary data. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_set_user_data(pj_turn_session *sess, void *user_data); + +/** + * Retrieve the previously assigned user data associated with this TURN + * session. + * + * @param sess The TURN client session. + * + * @return The user/application data. + */ +PJ_DECL(void *) pj_turn_session_get_user_data(pj_turn_session *sess); + +/** + * Get the group lock for this TURN session. + * + * @param sess The TURN client session. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_turn_session_get_grp_lock(pj_turn_session *sess); + +/** + * Configure message logging. By default all flags are enabled. + * + * @param sess The TURN client session. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DECL(void) pj_turn_session_set_log(pj_turn_session *sess, unsigned flags); + +/** + * Configure the SOFTWARE name to be sent in all STUN requests by the + * TURN session. + * + * @param sess The TURN client session. + * @param sw Software name string. If this argument is NULL or + * empty, the session will not include SOFTWARE attribute + * in STUN requests and responses. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_turn_session_set_software_name(pj_turn_session *sess, const pj_str_t *sw); + +/** + * Set the server or domain name of the server. Before the application + * can send Allocate request (with pj_turn_session_alloc()), it must first + * resolve the server address(es) using this function. This function will + * resolve the TURN server using DNS SRV resolution if the \a resolver + * is set. The server resolution process will complete asynchronously, + * and application will be notified in \a on_state() callback with the + * session state set to PJ_TURN_STATE_RESOLVED. + * + * Application may call with pj_turn_session_alloc() before the server + * resolution completes. In this case, the operation will be queued by + * the session, and it will be sent once the server resolution completes. + * + * @param sess The TURN client session. + * @param domain The domain, hostname, or IP address of the TURN + * server. When this parameter contains domain name, + * the \a resolver parameter must be set to activate + * DNS SRV resolution. + * @param default_port The default TURN port number to use when DNS SRV + * resolution is not used. If DNS SRV resolution is + * used, the server port number will be set from the + * DNS SRV records. + * @param resolver If this parameter is not NULL, then the \a domain + * parameter will be first resolved with DNS SRV and + * then fallback to using DNS A/AAAA resolution when + * DNS SRV resolution fails. If this parameter is + * NULL, the \a domain parameter will be resolved as + * hostname. + * + * @return PJ_SUCCESS if the operation has been successfully + * queued, or the appropriate error code on failure. + * When this function returns PJ_SUCCESS, the final + * result of the resolution process will be notified + * to application in \a on_state() callback. + */ +PJ_DECL(pj_status_t) +pj_turn_session_set_server(pj_turn_session *sess, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver); + +/** + * Set credential to be used to authenticate against TURN server. + * Application must call this function before sending Allocate request + * with pj_turn_session_alloc(). + * + * @param sess The TURN client session + * @param cred STUN credential to be used. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_set_credential(pj_turn_session *sess, const pj_stun_auth_cred *cred); + +/** + * Allocate a relay address/resource in the TURN server by sending TURN + * Allocate request. Application must first initiate the server resolution + * process with pj_turn_session_set_server() and set the credential to be + * used with pj_turn_session_set_credential() before calling this function. + * + * This function will complete asynchronously, and the application will be + * notified about the allocation result in \a on_state() callback. The + * TURN session state will move to PJ_TURN_STATE_READY if allocation is + * successful, and PJ_TURN_STATE_DEALLOCATING or greater state if allocation + * has failed. + * + * Once allocation has been successful, the TURN session will keep this + * allocation alive until the session is destroyed, by sending periodic + * allocation refresh to the TURN server. + * + * @param sess The TURN client session. + * @param param Optional TURN allocation parameter. + * + * @return PJ_SUCCESS if the operation has been successfully + * initiated or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess, const pj_turn_alloc_param *param); + +/** + * Create or renew permission in the TURN server for the specified peer IP + * addresses. Application must install permission for a particular (peer) + * IP address before it sends any data to that IP address, or otherwise + * the TURN server will drop the data. + * + * @param sess The TURN client session. + * @param addr_cnt Number of IP addresses. + * @param addr Array of peer IP addresses. Only the address family + * and IP address portion of the socket address matter. + * @param options Specify 1 to let the TURN client session automatically + * renew the permission later when they are about to + * expire. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) +pj_turn_session_set_perm(pj_turn_session *sess, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options); + +/** + * Send a data to the specified peer address via the TURN relay. This + * function will encapsulate the data as STUN Send Indication or TURN + * ChannelData packet and send the message to the TURN server. The TURN + * server then will send the data to the peer. + * + * The allocation (pj_turn_session_alloc()) must have been successfully + * created before application can relay any data. + * + * Since TURN session is transport independent, this function will + * ultimately call \a on_send_pkt() callback to request the application + * to actually send the packet containing the data to the TURN server. + * + * @param sess The TURN client session. + * @param pkt The data/packet to be sent to peer. + * @param pkt_len Length of the data. + * @param peer_addr The remote peer address (the ultimate destination + * of the data, and not the TURN server address). + * @param addr_len Length of the address. + * + * @return If the callback \a on_send_pkt() is called, this + * will contain the return value of the callback. + * Otherwise, it will indicate failure with + * the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_turn_session_sendto(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + +/** + * Optionally establish channel binding for the specified a peer address. + * This function will assign a unique channel number for the peer address + * and request channel binding to the TURN server for this address. When + * a channel has been bound to a peer, the TURN client and TURN server + * will exchange data using ChannelData encapsulation format, which has + * lower bandwidth overhead than Send Indication (the default format used + * when peer address is not bound to a channel). + * + * This function will complete asynchronously, and application will be + * notified about the result in \a on_channel_bound() callback. + * + * @param sess The TURN client session. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successfully + * initiated, or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_bind_channel(pj_turn_session *sess, const pj_sockaddr_t *peer, unsigned addr_len); + +/** + * Notify TURN client session upon receiving a packet from server. Since + * the TURN session is transport independent, it does not read packet from + * any sockets, and rather relies on application giving it packets that + * are received from the TURN server. The session then processes this packet + * and decides whether it is part of TURN protocol exchange or if it is a + * data to be reported back to user, which in this case it will call the + * \a on_rx_data() callback. + * + * @param sess The TURN client session. + * @param pkt The packet as received from the TURN server. This + * should contain either STUN encapsulated message or + * a ChannelData packet. + * @param pkt_len The length of the packet. + * @param parsed_len Optional argument to receive the number of parsed + * or processed data from the packet. + * + * @return The function may return non-PJ_SUCCESS if it receives + * non-STUN and non-ChannelData packet, or if the + * \a on_rx_data() returns non-PJ_SUCCESS; + */ +PJ_DECL(pj_status_t) +pj_turn_session_on_rx_pkt(pj_turn_session *sess, void *pkt, pj_size_t pkt_len, pj_size_t *parsed_len); + +/** + * Notify TURN client session upon receiving a packet from server. Since + * the TURN session is transport independent, it does not read packet from + * any sockets, and rather relies on application giving it packets that + * are received from the TURN server. The session then processes this packet + * and decides whether it is part of TURN protocol exchange or if it is a + * data to be reported back to user, which in this case it will call the + * \a on_rx_data() callback. + * + * This function is variant of pj_turn_session_on_rx_pkt() with additional + * parameters such as source address. Source address will allow STUN/TURN + * session to resend the request (e.g: with updated authentication) to the + * provided source address which may be different to the initial connection, + * for example in RFC 6062 scenario that there can be some data connection + * and a control connection. + * + * @param sess The TURN client session. + * @param prm The function parameters, e.g: packet, source address. + * + * @return The function may return non-PJ_SUCCESS if it receives + * non-STUN and non-ChannelData packet, or if the + * \a on_rx_data() returns non-PJ_SUCCESS; + */ +PJ_DECL(pj_status_t) pj_turn_session_on_rx_pkt2(pj_turn_session *sess, pj_turn_session_on_rx_pkt_param *prm); + +/** + * Initiate connection binding to the specified peer using ConnectionBind + * request. Application must call this function when it uses RFC 6062 + * (TURN TCP allocations) to establish a data connection with peer after + * opening/accepting connection to/from peer. The connection binding status + * will be notified via on_connection_bind_status callback. + * + * @param sess The TURN session. + * @param pool The memory pool. + * @param conn_id The connection ID assigned by TURN server. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) +pj_turn_session_connection_bind(pj_turn_session *sess, pj_pool_t *pool, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); +/** + * Initiate connection to the specified peer using Connect request. + * Application must call this function when it uses RFC 6062 (TURN TCP + * allocations) to initiate a data connection to a peer. When Connect response + * received, on_connect_complete will be called, application must implement + * this callback and initiate a new data connection to the specified peer. + * + * According to RFC 6062, a control connection must be a TCP connection, + * and application must send TCP Allocate request + * (with pj_turn_session_alloc(), set TURN allocation parameter peer_conn_type + * to PJ_TURN_TP_TCP) before calling this function. + * + * @param sess The TURN client session. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) pj_turn_session_connect(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_TURN_SESSION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h new file mode 100755 index 000000000..25a79ef61 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TURN_SOCK_H__ +#define __PJNATH_TURN_SOCK_H__ + +/** + * @file turn_sock.h + * @brief TURN relay using UDP client as transport protocol + */ +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** +@addtogroup PJNATH_TURN_SOCK +@{ + +This is a ready to use object for relaying application data via a TURN server, +by managing all the operations in \ref turn_op_sec. + +\section turnsock_using_sec Using TURN transport + +This object provides a thin wrapper to the \ref PJNATH_TURN_SESSION, hence the +API is very much the same (apart from the obvious difference in the names). +Please see \ref PJNATH_TURN_SESSION for the documentation on how to use the +session. + +\section turnsock_samples_sec Samples + +The \ref turn_client_sample is a sample application to use the +\ref PJNATH_TURN_SOCK. + +Also see \ref samples_page for other samples. + + */ + +/** + * Opaque declaration for TURN client. + */ +typedef struct pj_turn_sock pj_turn_sock; + +/** + * This structure contains callbacks that will be called by the TURN + * transport. + */ +typedef struct pj_turn_sock_cb { + /** + * Notification when incoming data has been received from the remote + * peer via the TURN server. The data reported in this callback will + * be the exact data as sent by the peer (e.g. the TURN encapsulation + * such as Data Indication or ChannelData will be removed before this + * function is called). + * + * @param turn_sock The TURN client transport. + * @param data The data as received from the peer. + * @param data_len Length of the data. + * @param peer_addr The peer address. + * @param addr_len The length of the peer address. + */ + void (*on_rx_data)(pj_turn_sock *turn_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notifification when asynchronous send operation has completed. + * + * @param turn_sock The TURN transport. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application should normally return PJ_TRUE to let + * the TURN transport continue its operation. However + * it must return PJ_FALSE if it has destroyed the + * TURN transport in this callback. + */ + pj_bool_t (*on_data_sent)(pj_turn_sock *sock, pj_ssize_t sent); + + /** + * Notification when TURN session state has changed. Application should + * implement this callback to monitor the progress of the TURN session. + * + * @param turn_sock The TURN client transport. + * @param old_state Previous state. + * @param new_state Current state. + */ + void (*on_state)(pj_turn_sock *turn_sock, pj_turn_state_t old_state, pj_turn_state_t new_state); + + /** + * Notification when TURN client received a ConnectionAttempt Indication + * from the TURN server, which indicates that peer initiates a TCP + * connection to allocated slot in the TURN server. Application should + * implement this callback if it uses RFC 6062 (TURN TCP allocations), + * otherwise TURN client will automatically accept it. + * + * If application accepts the peer connection attempt (i.e: by returning + * PJ_SUCCESS or not implementing this callback), the TURN socket will + * initiate a new connection to the TURN server and send ConnectionBind + * request, and eventually will notify application via + * on_connection_status callback, if implemented. + * + * @param turn_sock The TURN client transport. + * @param conn_id The connection ID assigned by TURN server. + * @param peer_addr Peer address that tried to connect to the + * TURN server. + * @param addr_len Length of the peer address. + * + * @return The callback must return PJ_SUCCESS to accept + * the connection attempt. + */ + pj_status_t (*on_connection_attempt)(pj_turn_sock *turn_sock, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notification for initiated TCP data connection to peer (RFC 6062), + * for example after peer connection attempt is accepted. + * + * @param turn_sock The TURN client transport. + * @param status The status code. + * @param conn_id The connection ID. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + */ + void (*on_connection_status)(pj_turn_sock *turn_sock, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + +} pj_turn_sock_cb; + +/** + * The default enabled SSL proto to be used. + * Default is all protocol above TLSv1 (TLSv1 & TLS v1.1 & TLS v1.2). + */ +#ifndef PJ_TURN_TLS_DEFAULT_PROTO +#define PJ_TURN_TLS_DEFAULT_PROTO (PJ_SSL_SOCK_PROTO_TLS1 | PJ_SSL_SOCK_PROTO_TLS1_1 | PJ_SSL_SOCK_PROTO_TLS1_2) +#endif + +/** + * TLS transport settings. + */ +typedef struct pj_turn_sock_tls_cfg { + /** + * Certificate of Authority (CA) list file. + */ + pj_str_t ca_list_file; + + /** + * Certificate of Authority (CA) list directory path. + */ + pj_str_t ca_list_path; + + /** + * Public endpoint certificate file, which will be used as client- + * side certificate for outgoing TLS connection. + */ + pj_str_t cert_file; + + /** + * Optional private key of the endpoint certificate to be used. + */ + pj_str_t privkey_file; + + /** + * Certificate of Authority (CA) buffer. If ca_list_file, ca_list_path, + * cert_file or privkey_file are set, this setting will be ignored. + */ + pj_ssl_cert_buffer ca_buf; + + /** + * Public endpoint certificate buffer, which will be used as client- + * side certificate for outgoing TLS connection, and server-side + * certificate for incoming TLS connection. If ca_list_file, ca_list_path, + * cert_file or privkey_file are set, this setting will be ignored. + */ + pj_ssl_cert_buffer cert_buf; + + /** + * Optional private key buffer of the endpoint certificate to be used. + * If ca_list_file, ca_list_path, cert_file or privkey_file are set, + * this setting will be ignored. + */ + pj_ssl_cert_buffer privkey_buf; + + /** + * Password to open private key. + */ + pj_str_t password; + + /** + * The ssl socket parameter. + * These fields are used by TURN TLS: + * - proto + * - ciphers_num + * - ciphers + * - curves_num + * - curves + * - sigalgs + * - entropy_type + * - entropy_path + * - timeout + * - sockopt_params + * - sockopt_ignore_error + */ + pj_ssl_sock_param ssock_param; + +} pj_turn_sock_tls_cfg; + +/** + * Initialize TLS setting with default values. + * + * @param tls_cfg The TLS setting to be initialized. + */ +PJ_DECL(void) pj_turn_sock_tls_cfg_default(pj_turn_sock_tls_cfg *tls_cfg); + +/** + * Duplicate TLS setting. + * + * @param pool The pool to duplicate strings etc. + * @param dst Destination structure. + * @param src Source structure. + */ +PJ_DECL(void) pj_turn_sock_tls_cfg_dup(pj_pool_t *pool, pj_turn_sock_tls_cfg *dst, const pj_turn_sock_tls_cfg *src); + +/** + * Wipe out certificates and keys in the TLS setting. + * + * @param tls_cfg The TLS setting. + */ +PJ_DECL(void) pj_turn_sock_tls_cfg_wipe_keys(pj_turn_sock_tls_cfg *tls_cfg); + +/** + * This structure describes options that can be specified when creating + * the TURN socket. Application should call #pj_turn_sock_cfg_default() + * to initialize this structure with its default values before using it. + */ +typedef struct pj_turn_sock_cfg { + /** + * The group lock to be used by the STUN socket. If NULL, the STUN socket + * will create one internally. + * + * Default: NULL + */ + pj_grp_lock_t *grp_lock; + + /** + * Packet buffer size. + * + * Default value is PJ_TURN_MAX_PKT_LEN. + */ + unsigned max_pkt_size; + + /** + * QoS traffic type to be set on this transport. When application wants + * to apply QoS tagging to the transport, it's preferable to set this + * field rather than \a qos_param fields since this is more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a lower + * level operation than setting the \a qos_type field and may not be + * supported on all platforms. + * + * By default all settings in this structure are not set. + */ + pj_qos_params qos_params; + + /** + * Specify if STUN socket should ignore any errors when setting the QoS + * traffic type/parameters. + * + * Default: PJ_TRUE + */ + pj_bool_t qos_ignore_error; + + /** + * Specify the interface where the socket should be bound to. If the + * address is zero, socket will be bound to INADDR_ANY. If the address + * is non-zero, socket will be bound to this address only. If the port is + * set to zero, the socket will bind at any port (chosen by the OS). + */ + pj_sockaddr bound_addr; + + /** + * Specify the port range for TURN socket binding, relative to the start + * port number specified in \a bound_addr. Note that this setting is only + * applicable when the start port number is non zero. + * + * Default value is zero. + */ + pj_uint16_t port_range; + + /** + * Specify target value for socket receive buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible has been + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_rcvbuf_size; + + /** + * Specify target value for socket send buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible has been + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_sndbuf_size; + + /** + * This specifies TLS settings for TLS transport. It is only be used + * when this TLS is used to connect to the TURN server. + */ + pj_turn_sock_tls_cfg tls_cfg; + +} pj_turn_sock_cfg; + +/** + * Initialize pj_turn_sock_cfg structure with default values. + */ +PJ_DECL(void) pj_turn_sock_cfg_default(pj_turn_sock_cfg *cfg); + +/** + * Create a TURN transport instance with the specified address family and + * connection type. Once TURN transport instance is created, application + * must call pj_turn_sock_alloc() to allocate a relay address in the TURN + * server. + * + * @param cfg The STUN configuration which contains among other + * things the ioqueue and timer heap instance for + * the operation of this transport. + * @param af Address family of the client connection. Currently + * pj_AF_INET() and pj_AF_INET6() are supported. + * @param conn_type Connection type to the TURN server. Both TCP and + * UDP are supported. + * @param cb Callback to receive events from the TURN transport. + * @param setting Optional settings to be specified to the transport. + * If this parameter is NULL, default values will be + * used. + * @param user_data Arbitrary application data to be associated with + * this transport. + * @param p_turn_sock Pointer to receive the created instance of the + * TURN transport. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_turn_sock_create(pj_stun_config *cfg, int af, pj_turn_tp_type conn_type, const pj_turn_sock_cb *cb, + const pj_turn_sock_cfg *setting, void *user_data, pj_turn_sock **p_turn_sock); + +/** + * Destroy the TURN transport instance. This will gracefully close the + * connection between the client and the TURN server. Although this + * function will return immediately, the TURN socket deletion may continue + * in the background and the application may still get state changes + * notifications from this transport. + * + * @param turn_sock The TURN transport instance. + */ +PJ_DECL(void) pj_turn_sock_destroy(pj_turn_sock *turn_sock); + +/** + * Associate a user data with this TURN transport. The user data may then + * be retrieved later with #pj_turn_sock_get_user_data(). + * + * @param turn_sock The TURN transport instance. + * @param user_data Arbitrary data. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_set_user_data(pj_turn_sock *turn_sock, void *user_data); + +/** + * Retrieve the previously assigned user data associated with this TURN + * transport. + * + * @param turn_sock The TURN transport instance. + * + * @return The user/application data. + */ +PJ_DECL(void *) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock); + +/** + * Get the group lock for this TURN transport. + * + * @param turn_sock The TURN transport instance. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_turn_sock_get_grp_lock(pj_turn_sock *turn_sock); + +/** + * Get the TURN transport info. The transport info contains, among other + * things, the allocated relay address. + * + * @param turn_sock The TURN transport instance. + * @param info Pointer to be filled with TURN transport info. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock, pj_turn_session_info *info); + +/** + * Acquire the internal mutex of the TURN transport. Application may need + * to call this function to synchronize access to other objects alongside + * the TURN transport, to avoid deadlock. + * + * @param turn_sock The TURN transport instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_lock(pj_turn_sock *turn_sock); + +/** + * Release the internal mutex previously held with pj_turn_sock_lock(). + * + * @param turn_sock The TURN transport instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock); + +/** + * Set STUN message logging for this TURN session. + * See #pj_stun_session_set_log(). + * + * @param turn_sock The TURN transport instance. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DECL(void) pj_turn_sock_set_log(pj_turn_sock *turn_sock, unsigned flags); + +/** + * Configure the SOFTWARE name to be sent in all STUN requests by the + * TURN session. + * + * @param turn_sock The TURN transport instance. + * @param sw Software name string. If this argument is NULL or + * empty, the session will not include SOFTWARE attribute + * in STUN requests and responses. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_turn_sock_set_software_name(pj_turn_sock *turn_sock, const pj_str_t *sw); + +/** + * Allocate a relay address/resource in the TURN server. This function + * will resolve the TURN server using DNS SRV (if desired) and send TURN + * \a Allocate request using the specified credential to allocate a relay + * address in the server. This function completes asynchronously, and + * application will be notified when the allocation process has been + * successful in the \a on_state() callback when the state is set to + * PJ_TURN_STATE_READY. If the allocation fails, the state will be set + * to PJ_TURN_STATE_DEALLOCATING or greater. + * + * @param turn_sock The TURN transport instance. + * @param domain The domain, hostname, or IP address of the TURN + * server. When this parameter contains domain name, + * the \a resolver parameter must be set to activate + * DNS SRV resolution. + * @param default_port The default TURN port number to use when DNS SRV + * resolution is not used. If DNS SRV resolution is + * used, the server port number will be set from the + * DNS SRV records. + * @param resolver If this parameter is not NULL, then the \a domain + * parameter will be first resolved with DNS SRV and + * then fallback to using DNS A/AAAA resolution when + * DNS SRV resolution fails. If this parameter is + * NULL, the \a domain parameter will be resolved as + * hostname. + * @param cred The STUN credential to be used for the TURN server. + * @param param Optional TURN allocation parameter. + * + * @return PJ_SUCCESS if the operation has been successfully + * queued, or the appropriate error code on failure. + * When this function returns PJ_SUCCESS, the final + * result of the allocation process will be notified + * to application in \a on_state() callback. + * + */ +PJ_DECL(pj_status_t) +pj_turn_sock_alloc(pj_turn_sock *turn_sock, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver, + const pj_stun_auth_cred *cred, const pj_turn_alloc_param *param); + +/** + * Create or renew permission in the TURN server for the specified peer IP + * addresses. Application must install permission for a particular (peer) + * IP address before it sends any data to that IP address, or otherwise + * the TURN server will drop the data. + * + * @param turn_sock The TURN transport instance. + * @param addr_cnt Number of IP addresses. + * @param addr Array of peer IP addresses. Only the address family + * and IP address portion of the socket address matter. + * @param options Specify 1 to let the TURN client session automatically + * renew the permission later when they are about to + * expire. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) +pj_turn_sock_set_perm(pj_turn_sock *turn_sock, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options); + +/** + * Send a data to the specified peer address via the TURN relay. This + * function will encapsulate the data as STUN Send Indication or TURN + * ChannelData packet and send the message to the TURN server. The TURN + * server then will send the data to the peer. + * + * The allocation (pj_turn_sock_alloc()) must have been successfully + * created before application can relay any data. + * + * @param turn_sock The TURN transport instance. + * @param pkt The data/packet to be sent to peer. + * @param pkt_len Length of the data. + * @param peer_addr The remote peer address (the ultimate destination + * of the data, and not the TURN server address). + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_turn_sock_sendto(pj_turn_sock *turn_sock, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + +/** + * Optionally establish channel binding for the specified a peer address. + * This function will assign a unique channel number for the peer address + * and request channel binding to the TURN server for this address. When + * a channel has been bound to a peer, the TURN transport and TURN server + * will exchange data using ChannelData encapsulation format, which has + * lower bandwidth overhead than Send Indication (the default format used + * when peer address is not bound to a channel). + * + * @param turn_sock The TURN transport instance. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_bind_channel(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len); +/** + * Initiate connection to the specified peer using Connect request. + * Application must call this function when it uses RFC 6062 (TURN TCP + * allocations) to initiate a data connection to a peer. The connection status + * will be notified via on_connection_status callback. + * + * According to RFC 6062, the TURN transport instance must be created with + * connection type are set to PJ_TURN_TP_TCP, application must send TCP + * Allocate request (with pj_turn_session_alloc(), set TURN allocation + * parameter peer_conn_type to PJ_TURN_TP_TCP) before calling this function. + * + * + * @param turn_sock The TURN transport instance. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_connect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len); +/** + * Close previous TCP data connection for the specified peer. + * According to RFC 6062, when the client wishes to terminate its relayed + * connection to the peer, it closes the data connection to the server. + * + * @param turn_sock The TURN transport instance. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_disconnect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_TURN_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h new file mode 100755 index 000000000..ba710cd00 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TYPES_H__ +#define __PJNATH_TYPES_H__ + +/** + * @file types.h + * @brief PJNATH types. + */ + +#include +#include + +/** + * @defgroup PJNATH NAT Traversal Helper Library + * @{ + */ + +PJ_BEGIN_DECL + +/** + * This constant describes a number to be used to identify an invalid TURN + * channel number. + */ +#define PJ_TURN_INVALID_CHANNEL 0xFFFF + +/** + * Initialize pjnath library. + * + * @return Initialization status. + */ +PJ_DECL(pj_status_t) pjnath_init(void); + +/** + * Display error to the log. + * + * @param sender The sender name. + * @param title Title message. + * @param status The error status. + */ +#if PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL +PJ_DECL(void) pjnath_perror(const char *sender, const char *title, pj_status_t status); +#else +#define pjnath_perror(sender, title, status) +#endif + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJNATH_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h new file mode 100755 index 000000000..dd660157c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_UPNP_H__ +#define __PJ_UPNP_H__ + +/** + * @file upnp.h + * @brief UPnP client. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJNATH_UPNP Simple UPnP Client + * @brief A simple UPnP client implementation. + * @{ + * + * This is a simple implementation of UPnP client. Its main function + * is to request a port mapping from an Internet Gateway Device (IGD), + * which will redirect communication received on a specified external + * port to a local socket. + */ + +/** + * This structre describes the parameter to initialize UPnP. + */ +typedef struct pj_upnp_init_param { + /** + * The pool factory where memory will be allocated from. + */ + pj_pool_factory *factory; + + /** + * The interface name to use for all UPnP operations. + * + * If NULL, the library will use the first suitable interface found. + */ + const char *if_name; + + /** + * The port number to use for all UPnP operations. + * + * If 0, the library will pick an arbitrary free port. + */ + unsigned port; + + /** + * The time duration to search for IGD devices (in seconds). + * + * If 0, the library will use PJ_UPNP_DEFAULT_SEARCH_TIME. + */ + int search_time; + + /** + * The callback to notify application when the initialization + * has completed. + * + * @param status The initialization status. + */ + void (*upnp_cb)(pj_status_t status); + +} pj_upnp_init_param; + +/** + * Initialize UPnP library and initiate the search for valid Internet + * Gateway Devices (IGD) in the network. + * + * @param param The UPnP initialization parameter. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) pj_upnp_init(const pj_upnp_init_param *param); + +/** + * Deinitialize UPnP library. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) pj_upnp_deinit(void); + +/** + * This is the main function to request a port mapping. If successful, + * the Internet Gateway Device will redirect communication received on + * the specified external ports to the local sockets. + * + * @param sock_cnt Number of sockets in the socket array. + * @param sock Array of local UDP sockets that will be mapped. + * @param ext_port (Optional) Array of external port numbers. If NULL, + * the external port numbers requested will be identical + * to the sockets' local port numbers. + * @param mapped_addr Array to receive the mapped public addresses and + * ports of the local UDP sockets, when the function + * returns PJ_SUCCESS. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) +pj_upnp_add_port_mapping(unsigned sock_cnt, const pj_sock_t sock[], unsigned ext_port[], pj_sockaddr mapped_addr[]); + +/** + * Send request to delete a port mapping. + * + * @param mapped_addr The public address and external port mapping to + * be deleted. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) pj_upnp_del_port_mapping(const pj_sockaddr *mapped_addr); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJ_UPNP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c new file mode 100755 index 000000000..5b373a180 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +/* PJNATH's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 +static const struct { + int code; + const char *msg; +} err_str[] = { + /* STUN related error codes */ + PJ_BUILD_ERR(PJNATH_EINSTUNMSG, "Invalid STUN message"), + PJ_BUILD_ERR(PJNATH_EINSTUNMSGLEN, "Invalid STUN message length"), + PJ_BUILD_ERR(PJNATH_EINSTUNMSGTYPE, "Invalid or unexpected STUN message type"), + PJ_BUILD_ERR(PJNATH_ESTUNTIMEDOUT, "STUN transaction has timed out"), + + PJ_BUILD_ERR(PJNATH_ESTUNTOOMANYATTR, "Too many STUN attributes"), + PJ_BUILD_ERR(PJNATH_ESTUNINATTRLEN, "Invalid STUN attribute length"), + PJ_BUILD_ERR(PJNATH_ESTUNDUPATTR, "Found duplicate STUN attribute"), + + PJ_BUILD_ERR(PJNATH_ESTUNFINGERPRINT, "STUN FINGERPRINT verification failed"), + PJ_BUILD_ERR(PJNATH_ESTUNMSGINTPOS, "Invalid STUN attribute after MESSAGE-INTEGRITY"), + PJ_BUILD_ERR(PJNATH_ESTUNFINGERPOS, "Invalid STUN attribute after FINGERPRINT"), + + PJ_BUILD_ERR(PJNATH_ESTUNNOMAPPEDADDR, "STUN (XOR-)MAPPED-ADDRESS attribute not found"), + PJ_BUILD_ERR(PJNATH_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + PJ_BUILD_ERR(PJNATH_EINVAF, "Invalid STUN address family value"), + PJ_BUILD_ERR(PJNATH_ESTUNINSERVER, "Invalid STUN server or server not configured"), + + PJ_BUILD_ERR(PJNATH_ESTUNDESTROYED, "STUN object has been destoyed"), + + /* ICE related errors */ + PJ_BUILD_ERR(PJNATH_ENOICE, "ICE session not available"), + PJ_BUILD_ERR(PJNATH_EICEINPROGRESS, "ICE check is in progress"), + PJ_BUILD_ERR(PJNATH_EICEFAILED, "All ICE checklists failed"), + PJ_BUILD_ERR(PJNATH_EICEMISMATCH, "Default target doesn't match any ICE candidates"), + PJ_BUILD_ERR(PJNATH_EICEINCOMPID, "Invalid ICE component ID"), + PJ_BUILD_ERR(PJNATH_EICEINCANDID, "Invalid ICE candidate ID"), + PJ_BUILD_ERR(PJNATH_EICEINSRCADDR, "Source address mismatch"), + PJ_BUILD_ERR(PJNATH_EICEMISSINGSDP, "Missing ICE SDP attribute"), + PJ_BUILD_ERR(PJNATH_EICEINCANDSDP, "Invalid SDP \"candidate\" attribute"), + PJ_BUILD_ERR(PJNATH_EICENOHOSTCAND, "No host candidate associated with srflx"), + PJ_BUILD_ERR(PJNATH_EICENOMTIMEOUT, "Controlled agent timed out waiting for nomination"), + + /* TURN related errors */ + PJ_BUILD_ERR(PJNATH_ETURNINTP, "Invalid/unsupported transport"), + +}; +#endif /* PJ_HAS_ERROR_STRING */ + +/* + * pjnath_strerror() + */ +static pj_str_t pjnath_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + if (statcode >= PJNATH_ERRNO_START && statcode < PJNATH_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n / 2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid + 1; + n -= (half + 1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char *)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown pjnath error %d", statcode); + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + + return errstr; +} + +static pj_str_t pjnath_strerror2(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + int stun_code = statcode - PJ_STATUS_FROM_STUN_CODE(0); + const pj_str_t cmsg = pj_stun_get_err_reason(stun_code); + pj_str_t errstr; + + buf[bufsize - 1] = '\0'; + + if (cmsg.slen == 0) { + /* Not found */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown STUN err-code %d", stun_code); + } else { + errstr.ptr = buf; + pj_strncpy(&errstr, &cmsg, bufsize); + if (errstr.slen < (int)bufsize) + buf[errstr.slen] = '\0'; + else + buf[bufsize - 1] = '\0'; + } + + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + + return errstr; +} + +PJ_DEF(pj_status_t) pjnath_init(void) +{ + pj_status_t status; + + status = pj_register_strerror(PJNATH_ERRNO_START, 299, &pjnath_strerror); + pj_assert(status == PJ_SUCCESS); + + status = pj_register_strerror(PJ_STATUS_FROM_STUN_CODE(300), 699 - 300, &pjnath_strerror2); + pj_assert(status == PJ_SUCCESS); + + return status; +} + +#if PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL + +PJ_DEF(void) pjnath_perror(const char *sender, const char *title, pj_status_t status) +{ +#if PJNATH_ERROR_LEVEL == 1 + PJ_PERROR(1, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 2 + PJ_PERROR(2, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 3 + PJ_PERROR(3, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 4 + PJ_PERROR(4, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 5 + PJ_PERROR(5, (sender, status, title)); +#else +#error Invalid PJNATH_ERROR_LEVEL value +#endif +} + +#endif /* PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL */ diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c new file mode 100755 index 000000000..7cfbcaa51 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c @@ -0,0 +1,3259 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* String names for candidate types */ +static const char *cand_type_names[] = {"host", "srflx", "prflx", "relay" + +}; + +/* String names for pj_ice_sess_check_state */ +#if PJ_LOG_MAX_LEVEL >= 4 +static const char *check_state_name[] = {"Frozen", "Waiting", "In Progress", "Succeeded", "Failed"}; + +static const char *clist_state_name[] = {"Idle", "Running", "Completed"}; +#endif /* PJ_LOG_MAX_LEVEL >= 4 */ + +static const char *role_names[] = {"Unknown", "Controlled", "Controlling"}; + +enum timer_type { + TIMER_NONE, /**< Timer not active */ + TIMER_COMPLETION_CALLBACK, /**< Call on_ice_complete() callback */ + TIMER_CONTROLLED_WAIT_NOM, /**< Controlled agent is waiting for + controlling agent to send connectivity + check with nominated flag after it has + valid check for every components. */ + TIMER_START_NOMINATED_CHECK, /**< Controlling agent start connectivity + checks with USE-CANDIDATE flag. */ + TIMER_KEEP_ALIVE /**< ICE keep-alive timer. */ + +}; + +/* Candidate type preference */ +static pj_uint8_t cand_type_prefs[PJ_ICE_CAND_TYPE_MAX] = { +#if PJ_ICE_CAND_TYPE_PREF_BITS < 8 + /* Keep it to 2 bits */ + 3, /**< PJ_ICE_HOST_PREF */ + 1, /**< PJ_ICE_SRFLX_PREF. */ + 2, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#else + /* Default ICE session preferences, according to draft-ice */ + 126, /**< PJ_ICE_HOST_PREF */ + 100, /**< PJ_ICE_SRFLX_PREF. */ + 110, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#endif +}; + +#define THIS_FILE "ice_session.c" +#define CHECK_NAME_LEN 128 +#define LOG4(expr) PJ_LOG(4, expr) +#define LOG5(expr) PJ_LOG(4, expr) +#define GET_LCAND_ID(cand) (unsigned)(cand - ice->lcand) +#define GET_CHECK_ID(cl, chk) (chk - (cl)->checks) + +/* The data that will be attached to the STUN session on each + * component. + */ +typedef struct stun_data { + pj_ice_sess *ice; + unsigned comp_id; + pj_ice_sess_comp *comp; +} stun_data; + +/* The data that will be attached to the timer to perform + * periodic check. + */ +typedef struct timer_data { + pj_ice_sess *ice; + pj_ice_sess_checklist *clist; +} timer_data; + +/* This is the data that will be attached as token to outgoing + * STUN messages. + */ + +/* Forward declarations */ +static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status); +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now); +static void ice_on_destroy(void *obj); +static void destroy_ice(pj_ice_sess *ice, pj_status_t reason); +static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_timer_entry *te); +static void start_nominated_check(pj_ice_sess *ice); +static void periodic_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void handle_incoming_check(pj_ice_sess *ice, const pj_ice_rx_check *rcheck); +static void end_of_cand_ind_timer(pj_timer_heap_t *th, pj_timer_entry *te); + +/* These are the callbacks registered to the STUN sessions */ +static pj_status_t on_stun_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); +static pj_status_t on_stun_rx_request(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_rx_data *rdata, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static void on_stun_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, + pj_stun_tx_data *tdata, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + +/* These are the callbacks for performing STUN authentication */ +static pj_status_t stun_auth_get_auth(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce); +static pj_status_t stun_auth_get_cred(const pj_stun_msg *msg, void *user_data, pj_pool_t *pool, pj_str_t *realm, + pj_str_t *username, pj_str_t *nonce, pj_stun_passwd_type *data_type, + pj_str_t *data); +static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, pj_pool_t *pool, pj_stun_passwd_type *data_type, + pj_str_t *data); + +PJ_DEF(const char *) pj_ice_get_cand_type_name(pj_ice_cand_type type) +{ + PJ_ASSERT_RETURN(type <= PJ_ICE_CAND_TYPE_RELAYED, "???"); + return cand_type_names[type]; +} + +PJ_DEF(const char *) pj_ice_sess_role_name(pj_ice_sess_role role) +{ + switch (role) { + case PJ_ICE_SESS_ROLE_UNKNOWN: + return "Unknown"; + case PJ_ICE_SESS_ROLE_CONTROLLED: + return "Controlled"; + case PJ_ICE_SESS_ROLE_CONTROLLING: + return "Controlling"; + default: + return "??"; + } +} + +/* Get the prefix for the foundation */ +static int get_type_prefix(pj_ice_cand_type type) +{ + switch (type) { + case PJ_ICE_CAND_TYPE_HOST: + return 'H'; + case PJ_ICE_CAND_TYPE_SRFLX: + return 'S'; + case PJ_ICE_CAND_TYPE_PRFLX: + return 'P'; + case PJ_ICE_CAND_TYPE_RELAYED: + return 'R'; + default: + pj_assert(!"Invalid type"); + return 'U'; + } +} + +/* Calculate foundation: + * Two candidates have the same foundation when they are "similar" - of + * the same type and obtained from the same host candidate and STUN + * server using the same protocol. Otherwise, their foundation is + * different. + */ +PJ_DEF(void) +pj_ice_calc_foundation(pj_pool_t *pool, pj_str_t *foundation, pj_ice_cand_type type, const pj_sockaddr *base_addr) +{ +#if PJNATH_ICE_PRIO_STD + char buf[64]; + pj_uint32_t val; + + if (base_addr->addr.sa_family == pj_AF_INET()) { + val = pj_ntohl(base_addr->ipv4.sin_addr.s_addr); + } else { + val = pj_hash_calc(0, pj_sockaddr_get_addr(base_addr), pj_sockaddr_get_addr_len(base_addr)); + } + pj_ansi_snprintf(buf, sizeof(buf), "%c%x", get_type_prefix(type), val); + pj_strdup2(pool, foundation, buf); +#else + /* Much shorter version, valid for candidates added by + * pj_ice_strans. + */ + foundation->ptr = (char *)pj_pool_alloc(pool, 1); + *foundation->ptr = (char)get_type_prefix(type); + foundation->slen = 1; + + PJ_UNUSED_ARG(base_addr); +#endif +} + +/* Init component */ +static pj_status_t init_comp(pj_ice_sess *ice, unsigned comp_id, pj_ice_sess_comp *comp) +{ + pj_stun_session_cb sess_cb; + pj_stun_auth_cred auth_cred; + stun_data *sd; + pj_status_t status; + + /* Init STUN callbacks */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &on_stun_request_complete; + sess_cb.on_rx_indication = &on_stun_rx_indication; + sess_cb.on_rx_request = &on_stun_rx_request; + sess_cb.on_send_msg = &on_stun_send_msg; + + /* Create STUN session for this candidate */ + status = pj_stun_session_create(&ice->stun_cfg, NULL, &sess_cb, PJ_TRUE, ice->grp_lock, &comp->stun_sess); + if (status != PJ_SUCCESS) + return status; + + /* Associate data with this STUN session */ + sd = PJ_POOL_ZALLOC_T(ice->pool, struct stun_data); + sd->ice = ice; + sd->comp_id = comp_id; + sd->comp = comp; + pj_stun_session_set_user_data(comp->stun_sess, sd); + + /* Init STUN authentication credential */ + pj_bzero(&auth_cred, sizeof(auth_cred)); + auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC; + auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth; + auth_cred.data.dyn_cred.get_cred = &stun_auth_get_cred; + auth_cred.data.dyn_cred.get_password = &stun_auth_get_password; + auth_cred.data.dyn_cred.user_data = comp->stun_sess; + pj_stun_session_set_credential(comp->stun_sess, PJ_STUN_AUTH_SHORT_TERM, &auth_cred); + + return PJ_SUCCESS; +} + +/* Init options with default values */ +PJ_DEF(void) pj_ice_sess_options_default(pj_ice_sess_options *opt) +{ + opt->aggressive = PJ_TRUE; + opt->nominated_check_delay = PJ_ICE_NOMINATED_CHECK_DELAY; + opt->controlled_agent_want_nom_timeout = ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT; + opt->trickle = PJ_ICE_SESS_TRICKLE_DISABLED; +} + +/* + * Create ICE session. + */ +PJ_DEF(pj_status_t) +pj_ice_sess_create(pj_stun_config *stun_cfg, const char *name, pj_ice_sess_role role, unsigned comp_cnt, + const pj_ice_sess_cb *cb, const pj_str_t *local_ufrag, const pj_str_t *local_passwd, + pj_grp_lock_t *grp_lock, pj_ice_sess **p_ice) +{ + pj_pool_t *pool; + pj_ice_sess *ice; + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && cb && p_ice, PJ_EINVAL); + + if (name == NULL) + name = "icess%p"; + + pool = pj_pool_create(stun_cfg->pf, name, PJNATH_POOL_LEN_ICE_SESS, PJNATH_POOL_INC_ICE_SESS, NULL); + ice = PJ_POOL_ZALLOC_T(pool, pj_ice_sess); + ice->pool = pool; + ice->role = role; + ice->tie_breaker.u32.hi = pj_rand(); + ice->tie_breaker.u32.lo = pj_rand(); + ice->prefs = cand_type_prefs; + pj_ice_sess_options_default(&ice->opt); + + pj_timer_entry_init(&ice->timer, TIMER_NONE, (void *)ice, &on_timer); + + pj_ansi_snprintf(ice->obj_name, sizeof(ice->obj_name), name, ice); + + if (grp_lock) { + ice->grp_lock = grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &ice->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(ice->grp_lock); + pj_grp_lock_add_handler(ice->grp_lock, pool, ice, &ice_on_destroy); + + pj_memcpy(&ice->cb, cb, sizeof(*cb)); + pj_memcpy(&ice->stun_cfg, stun_cfg, sizeof(*stun_cfg)); + + ice->comp_cnt = comp_cnt; + for (i = 0; i < comp_cnt; ++i) { + pj_ice_sess_comp *comp; + comp = &ice->comp[i]; + comp->valid_check = NULL; + comp->nominated_check = NULL; + + status = init_comp(ice, i + 1, comp); + if (status != PJ_SUCCESS) { + destroy_ice(ice, status); + return status; + } + } + + /* Initialize transport datas */ + for (i = 0; i < PJ_ARRAY_SIZE(ice->tp_data); ++i) { + ice->tp_data[i].transport_id = 0; + ice->tp_data[i].has_req_data = PJ_FALSE; + } + + if (local_ufrag == NULL) { + ice->rx_ufrag.ptr = (char *)pj_pool_alloc(ice->pool, PJ_ICE_UFRAG_LEN); + pj_create_random_string(ice->rx_ufrag.ptr, PJ_ICE_UFRAG_LEN); + ice->rx_ufrag.slen = PJ_ICE_UFRAG_LEN; + } else { + pj_strdup(ice->pool, &ice->rx_ufrag, local_ufrag); + } + + if (local_passwd == NULL) { + ice->rx_pass.ptr = (char *)pj_pool_alloc(ice->pool, PJ_ICE_PWD_LEN); + pj_create_random_string(ice->rx_pass.ptr, PJ_ICE_PWD_LEN); + ice->rx_pass.slen = PJ_ICE_PWD_LEN; + } else { + pj_strdup(ice->pool, &ice->rx_pass, local_passwd); + } + + pj_list_init(&ice->early_check); + + ice->valid_pair_found = PJ_FALSE; + + /* Done */ + *p_ice = ice; + + LOG4((ice->obj_name, "ICE session created, comp_cnt=%d, role is %s agent", comp_cnt, role_names[ice->role])); + + return PJ_SUCCESS; +} + +/* + * Get the value of various options of the ICE session. + */ +PJ_DEF(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice, pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + pj_memcpy(opt, &ice->opt, sizeof(*opt)); + return PJ_SUCCESS; +} + +/* + * Specify various options for this ICE session. + */ +PJ_DEF(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, const pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice && opt, PJ_EINVAL); + pj_memcpy(&ice->opt, opt, sizeof(*opt)); + ice->is_trickling = (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED); + if (ice->is_trickling) { + LOG5((ice->obj_name, "Trickle ICE is active (%s mode)", + (ice->opt.trickle == PJ_ICE_SESS_TRICKLE_HALF ? "half" : "full"))); + + if (ice->opt.aggressive) { + /* Disable aggressive when ICE trickle is active */ + ice->opt.aggressive = PJ_FALSE; + LOG4((ice->obj_name, "Warning: aggressive nomination is disabled" + " as trickle ICE is active")); + } + } + + LOG5((ice->obj_name, "ICE nomination type set to %s", (ice->opt.aggressive ? "aggressive" : "regular"))); + return PJ_SUCCESS; +} + +/* + * Callback to really destroy the session + */ +static void ice_on_destroy(void *obj) +{ + pj_ice_sess *ice = (pj_ice_sess *)obj; + + pj_pool_safe_release(&ice->pool); + + LOG4((THIS_FILE, "ICE session %p destroyed", ice)); +} + +/* + * Destroy + */ +static void destroy_ice(pj_ice_sess *ice, pj_status_t reason) +{ + unsigned i; + + if (reason == PJ_SUCCESS) { + LOG4((ice->obj_name, "Destroying ICE session %p", ice)); + } + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return; + } + + ice->is_destroying = PJ_TRUE; + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, PJ_FALSE); + + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].stun_sess) { + pj_stun_session_destroy(ice->comp[i].stun_sess); + ice->comp[i].stun_sess = NULL; + } + } + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->clist.timer, PJ_FALSE); + + pj_grp_lock_dec_ref(ice->grp_lock); + pj_grp_lock_release(ice->grp_lock); +} + +/* + * Destroy + */ +PJ_DEF(pj_status_t) pj_ice_sess_destroy(pj_ice_sess *ice) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + destroy_ice(ice, PJ_SUCCESS); + return PJ_SUCCESS; +} + +/* + * Detach ICE session from group lock. + */ +PJ_DEF(pj_status_t) pj_ice_sess_detach_grp_lock(pj_ice_sess *ice, pj_grp_lock_handler *handler) +{ + PJ_ASSERT_RETURN(ice && handler, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + pj_grp_lock_del_handler(ice->grp_lock, ice, &ice_on_destroy); + *handler = &ice_on_destroy; + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; +} + +/* + * Change session role. + */ +PJ_DEF(pj_status_t) pj_ice_sess_change_role(pj_ice_sess *ice, pj_ice_sess_role new_role) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + + if (new_role != ice->role) { + ice->role = new_role; + LOG4((ice->obj_name, "Role changed to %s", role_names[new_role])); + } + + return PJ_SUCCESS; +} + +/* + * Change type preference + */ +PJ_DEF(pj_status_t) pj_ice_sess_set_prefs(pj_ice_sess *ice, const pj_uint8_t prefs[4]) +{ + unsigned i; + PJ_ASSERT_RETURN(ice && prefs, PJ_EINVAL); + ice->prefs = (pj_uint8_t *)pj_pool_calloc(ice->pool, PJ_ICE_CAND_TYPE_MAX, sizeof(pj_uint8_t)); + for (i = 0; i < PJ_ICE_CAND_TYPE_MAX; ++i) { +#if PJ_ICE_CAND_TYPE_PREF_BITS < 8 + pj_assert(prefs[i] < (2 << PJ_ICE_CAND_TYPE_PREF_BITS)); +#endif + ice->prefs[i] = prefs[i]; + } + return PJ_SUCCESS; +} + +/* Find component by ID */ +static pj_ice_sess_comp *find_comp(const pj_ice_sess *ice, unsigned comp_id) +{ + /* Ticket #1844: possible wrong assertion when remote has less ICE comp */ + // pj_assert(comp_id > 0 && comp_id <= ice->comp_cnt); + if (comp_id > ice->comp_cnt) + return NULL; + + return (pj_ice_sess_comp *)&ice->comp[comp_id - 1]; +} + +/* Callback by STUN authentication when it needs to send 401 */ +static pj_status_t stun_auth_get_auth(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce) +{ + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(pool); + + realm->slen = 0; + nonce->slen = 0; + + return PJ_SUCCESS; +} + +/* Get credential to be sent with outgoing message */ +static pj_status_t stun_auth_get_cred(const pj_stun_msg *msg, void *user_data, pj_pool_t *pool, pj_str_t *realm, + pj_str_t *username, pj_str_t *nonce, pj_stun_passwd_type *data_type, + pj_str_t *data) +{ + pj_stun_session *sess = (pj_stun_session *)user_data; + stun_data *sd = (stun_data *)pj_stun_session_get_user_data(sess); + pj_ice_sess *ice = sd->ice; + + PJ_UNUSED_ARG(pool); + realm->slen = nonce->slen = 0; + + if (PJ_STUN_IS_RESPONSE(msg->hdr.type)) { + /* Outgoing responses need to have the same credential as + * incoming requests. + */ + *username = ice->rx_uname; + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->rx_pass; + } else { + *username = ice->tx_uname; + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->tx_pass; + } + + return PJ_SUCCESS; +} + +/* Get password to be used to authenticate incoming message */ +static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, pj_pool_t *pool, pj_stun_passwd_type *data_type, + pj_str_t *data) +{ + pj_stun_session *sess = (pj_stun_session *)user_data; + stun_data *sd = (stun_data *)pj_stun_session_get_user_data(sess); + pj_ice_sess *ice = sd->ice; + + PJ_UNUSED_ARG(realm); + PJ_UNUSED_ARG(pool); + + if (PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { + /* Incoming response is authenticated with TX credential */ + /* Verify username */ + if (pj_strcmp(username, &ice->tx_uname) != 0) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->tx_pass; + + } else { + /* Incoming request is authenticated with RX credential */ + /* The agent MUST accept a credential if the username consists + * of two values separated by a colon, where the first value is + * equal to the username fragment generated by the agent in an offer + * or answer for a session in-progress, and the MESSAGE-INTEGRITY + * is the output of a hash of the password and the STUN packet's + * contents. + */ + const char *pos; + pj_str_t ufrag; + + pos = (const char *)pj_memchr(username->ptr, ':', username->slen); + if (pos == NULL) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + + ufrag.ptr = (char *)username->ptr; + ufrag.slen = (pos - username->ptr); + + if (pj_strcmp(&ufrag, &ice->rx_ufrag) != 0) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->rx_pass; + } + + return PJ_SUCCESS; +} + +static pj_uint32_t CALC_CAND_PRIO(pj_ice_sess *ice, pj_ice_cand_type type, pj_uint32_t local_pref, pj_uint32_t comp_id) +{ +#if PJNATH_ICE_PRIO_STD + return ((ice->prefs[type] & 0xFF) << 24) + ((local_pref & 0xFFFF) << 8) + (((256 - comp_id) & 0xFF) << 0); +#else + enum { + type_mask = ((1 << PJ_ICE_CAND_TYPE_PREF_BITS) - 1), + local_mask = ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1), + comp_mask = ((1 << PJ_ICE_COMP_BITS) - 1), + + comp_shift = 0, + local_shift = (PJ_ICE_COMP_BITS), + type_shift = (comp_shift + local_shift), + + max_comp = (2 << PJ_ICE_COMP_BITS), + }; + + return ((ice->prefs[type] & type_mask) << type_shift) + ((local_pref & local_mask) << local_shift) + + (((max_comp - comp_id) & comp_mask) << comp_shift); +#endif +} + +/* + * Add ICE candidate + */ +PJ_DEF(pj_status_t) +pj_ice_sess_add_cand(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, pj_ice_cand_type type, + pj_uint16_t local_pref, const pj_str_t *foundation, const pj_sockaddr_t *addr, + const pj_sockaddr_t *base_addr, const pj_sockaddr_t *rel_addr, int addr_len, unsigned *p_cand_id) +{ + pj_ice_sess_cand *lcand; + pj_status_t status = PJ_SUCCESS; + char address[PJ_INET6_ADDRSTRLEN]; + unsigned i; + + PJ_ASSERT_RETURN(ice && comp_id && foundation && addr && base_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(comp_id <= ice->comp_cnt, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->lcand_cnt >= PJ_ARRAY_SIZE(ice->lcand)) { + status = PJ_ETOOMANY; + goto on_return; + } + + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) { + /* Trickle ICE: + * Make sure that candidate has not been added + */ + for (i = 0; i < ice->lcand_cnt; ++i) { + const pj_ice_sess_cand *c = &ice->lcand[i]; + if (c->comp_id == comp_id && c->type == type && pj_sockaddr_cmp(&c->addr, addr) == 0 && + pj_sockaddr_cmp(&c->base_addr, base_addr) == 0) { + break; + } + } + + /* Skip candidate, it has been added */ + if (i < ice->lcand_cnt) { + if (p_cand_id) + *p_cand_id = i; + goto on_return; + } + } + + lcand = &ice->lcand[ice->lcand_cnt]; + lcand->id = ice->lcand_cnt; + lcand->comp_id = (pj_uint8_t)comp_id; + lcand->transport_id = (pj_uint8_t)transport_id; + lcand->type = type; + pj_strdup(ice->pool, &lcand->foundation, foundation); + lcand->local_pref = local_pref; + lcand->prio = CALC_CAND_PRIO(ice, type, local_pref, lcand->comp_id); + pj_sockaddr_cp(&lcand->addr, addr); + pj_sockaddr_cp(&lcand->base_addr, base_addr); + if (rel_addr == NULL) + rel_addr = base_addr; + pj_memcpy(&lcand->rel_addr, rel_addr, addr_len); + + /* Update transport data */ + for (i = 0; i < PJ_ARRAY_SIZE(ice->tp_data); ++i) { + /* Check if this transport has been registered */ + if (ice->tp_data[i].transport_id == transport_id) + break; + + if (ice->tp_data[i].transport_id == 0) { + /* Found an empty slot, register this transport here */ + ice->tp_data[i].transport_id = transport_id; + break; + } + } + pj_assert(i < PJ_ARRAY_SIZE(ice->tp_data) && ice->tp_data[i].transport_id == transport_id); + + pj_ansi_strcpy(ice->tmp.txt, pj_sockaddr_print(&lcand->addr, address, sizeof(address), 2)); + LOG4((ice->obj_name, + "Candidate %d added: comp_id=%d, type=%s, foundation=%.*s, " + "addr=%s:%d, base=%s:%d, prio=0x%x (%u)", + lcand->id, lcand->comp_id, cand_type_names[lcand->type], (int)lcand->foundation.slen, lcand->foundation.ptr, + ice->tmp.txt, pj_sockaddr_get_port(&lcand->addr), + pj_sockaddr_print(&lcand->base_addr, address, sizeof(address), 2), pj_sockaddr_get_port(&lcand->base_addr), + lcand->prio, lcand->prio)); + + if (p_cand_id) + *p_cand_id = lcand->id; + + ++ice->lcand_cnt; + +on_return: + pj_grp_lock_release(ice->grp_lock); + return status; +} + +/* Find default candidate ID for the component */ +PJ_DEF(pj_status_t) pj_ice_sess_find_default_cand(pj_ice_sess *ice, unsigned comp_id, int *cand_id) +{ + unsigned i; + + PJ_ASSERT_RETURN(ice && comp_id && cand_id, PJ_EINVAL); + PJ_ASSERT_RETURN(comp_id <= ice->comp_cnt, PJ_EINVAL); + + *cand_id = -1; + + pj_grp_lock_acquire(ice->grp_lock); + + /* First find in valid list if we have nominated pair */ + for (i = 0; i < ice->valid_list.count; ++i) { + pj_ice_sess_check *check = &ice->valid_list.checks[i]; + + if (check->lcand->comp_id == comp_id) { + *cand_id = GET_LCAND_ID(check->lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* If there's no nominated pair, find relayed candidate */ + for (i = 0; i < ice->lcand_cnt; ++i) { + pj_ice_sess_cand *lcand = &ice->lcand[i]; + if (lcand->comp_id == comp_id && lcand->type == PJ_ICE_CAND_TYPE_RELAYED) { + *cand_id = GET_LCAND_ID(lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* If there's no relayed candidate, find reflexive candidate */ + for (i = 0; i < ice->lcand_cnt; ++i) { + pj_ice_sess_cand *lcand = &ice->lcand[i]; + if (lcand->comp_id == comp_id && + (lcand->type == PJ_ICE_CAND_TYPE_SRFLX || lcand->type == PJ_ICE_CAND_TYPE_PRFLX)) { + *cand_id = GET_LCAND_ID(lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* Otherwise return host candidate */ + for (i = 0; i < ice->lcand_cnt; ++i) { + pj_ice_sess_cand *lcand = &ice->lcand[i]; + if (lcand->comp_id == comp_id && lcand->type == PJ_ICE_CAND_TYPE_HOST) { + *cand_id = GET_LCAND_ID(lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* Still no candidate is found! :( */ + pj_grp_lock_release(ice->grp_lock); + + pj_assert(!"Should have a candidate by now"); + return PJ_EBUG; +} + +#ifndef MIN +#define MIN(a, b) (a < b ? a : b) +#endif + +#ifndef MAX +#define MAX(a, b) (a > b ? a : b) +#endif + +static pj_timestamp CALC_CHECK_PRIO(const pj_ice_sess *ice, const pj_ice_sess_cand *lcand, + const pj_ice_sess_cand *rcand) +{ + pj_uint32_t O, A; + pj_timestamp prio; + + /* Original formula: + * pair priority = 2^32*MIN(O,A) + 2*MAX(O,A) + (O>A?1:0) + */ + + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING) { + O = lcand->prio; + A = rcand->prio; + } else { + O = rcand->prio; + A = lcand->prio; + } + + /* + return ((pj_uint64_t)1 << 32) * MIN(O, A) + + (pj_uint64_t)2 * MAX(O, A) + (O>A ? 1 : 0); + */ + + prio.u32.hi = MIN(O, A); + prio.u32.lo = (MAX(O, A) << 1) + (O > A ? 1 : 0); + + return prio; +} + +PJ_INLINE(int) CMP_CHECK_STATE(const pj_ice_sess_check *c1, const pj_ice_sess_check *c2) +{ + /* SUCCEEDED has higher state than FAILED */ + if (c1->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED && c2->state == PJ_ICE_SESS_CHECK_STATE_FAILED) { + return 1; + } + if (c2->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED && c1->state == PJ_ICE_SESS_CHECK_STATE_FAILED) { + return -1; + } + + /* Other state, just compare the state value */ + return (c1->state - c2->state); +} + +PJ_INLINE(int) CMP_CHECK_PRIO(const pj_ice_sess_check *c1, const pj_ice_sess_check *c2) +{ + return pj_cmp_timestamp(&c1->prio, &c2->prio); +} + +#if PJ_LOG_MAX_LEVEL >= 4 +static const char *dump_check(char *buffer, unsigned bufsize, const pj_ice_sess_checklist *clist, + const pj_ice_sess_check *check) +{ + const pj_ice_sess_cand *lcand = check->lcand; + const pj_ice_sess_cand *rcand = check->rcand; + char laddr[PJ_INET6_ADDRSTRLEN], raddr[PJ_INET6_ADDRSTRLEN]; + int len; + + PJ_CHECK_STACK(); + + len = pj_ansi_snprintf(buffer, bufsize, "%d: [%d] %s:%d-->%s:%d", (int)GET_CHECK_ID(clist, check), + check->lcand->comp_id, pj_sockaddr_print(&lcand->addr, laddr, sizeof(laddr), 2), + pj_sockaddr_get_port(&lcand->addr), pj_sockaddr_print(&rcand->addr, raddr, sizeof(raddr), 2), + pj_sockaddr_get_port(&rcand->addr)); + + if (len < 0) + len = 0; + else if (len >= (int)bufsize) + len = bufsize - 1; + + buffer[len] = '\0'; + return buffer; +} + +static void dump_checklist(const char *title, pj_ice_sess *ice, const pj_ice_sess_checklist *clist) +{ + unsigned i; + + LOG4((ice->obj_name, "%s", title)); + for (i = 0; i < clist->count; ++i) { + const pj_ice_sess_check *c = &clist->checks[i]; + LOG4((ice->obj_name, " %s (%s, state=%s)", dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), clist, c), + (c->nominated ? "nominated" : "not nominated"), check_state_name[c->state])); + } +} + +#else +#define dump_checklist(title, ice, clist) +#endif + +static void check_set_state(pj_ice_sess *ice, pj_ice_sess_check *check, pj_ice_sess_check_state st, + pj_status_t err_code) +{ + LOG5((ice->obj_name, "Check %s: state changed from %s to %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), check_state_name[check->state], + check_state_name[st])); + + /* Put the assert after printing log for debugging purpose */ + // There is corner case, nomination (in non-aggressive ICE mode) may be + // done using an in-progress pair instead of successful pair, this is + // possible because host candidates actually share a single STUN transport + // and pair selection for nomination compares transport instead of + // candidate. So later the pair will receive double completions. + // pj_assert(check->state < PJ_ICE_SESS_CHECK_STATE_SUCCEEDED); + + check->state = st; + check->err_code = err_code; +} + +static void clist_set_state(pj_ice_sess *ice, pj_ice_sess_checklist *clist, pj_ice_sess_checklist_state st) +{ + if (clist->state != st) { + LOG5((ice->obj_name, "Checklist: state changed from %s to %s", clist_state_name[clist->state], + clist_state_name[st])); + clist->state = st; + } +} + +/* Sort checklist based on state & priority, we need to put Successful pairs + * on top of the list for pruning. + */ +static void sort_checklist(pj_ice_sess *ice, pj_ice_sess_checklist *clist) +{ + unsigned i; + pj_ice_sess_check **check_ptr[PJ_ICE_MAX_COMP * 2]; + unsigned check_ptr_cnt = 0; + + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check) { + check_ptr[check_ptr_cnt++] = &ice->comp[i].valid_check; + } + if (ice->comp[i].nominated_check) { + check_ptr[check_ptr_cnt++] = &ice->comp[i].nominated_check; + } + } + + pj_assert(clist->count > 0); + for (i = 0; i < clist->count - 1; ++i) { + unsigned j, highest = i; + + for (j = i + 1; j < clist->count; ++j) { + int cmp_state = CMP_CHECK_STATE(&clist->checks[j], &clist->checks[highest]); + if (cmp_state > 0 || (cmp_state == 0 && CMP_CHECK_PRIO(&clist->checks[j], &clist->checks[highest]) > 0)) { + highest = j; + } + } + + if (highest != i) { + pj_ice_sess_check tmp; + unsigned k; + + pj_memcpy(&tmp, &clist->checks[i], sizeof(pj_ice_sess_check)); + pj_memcpy(&clist->checks[i], &clist->checks[highest], sizeof(pj_ice_sess_check)); + pj_memcpy(&clist->checks[highest], &tmp, sizeof(pj_ice_sess_check)); + + /* Update valid and nominated check pointers, since we're moving + * around checks + */ + for (k = 0; k < check_ptr_cnt; ++k) { + if (*check_ptr[k] == &clist->checks[highest]) + *check_ptr[k] = &clist->checks[i]; + else if (*check_ptr[k] == &clist->checks[i]) + *check_ptr[k] = &clist->checks[highest]; + } + } + } +} + +/* Remove a check pair from checklist */ +static void remove_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, unsigned check_idx, const char *reason) +{ + LOG5((ice->obj_name, "Check %s pruned (%s)", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), clist, &clist->checks[check_idx]), reason)); + + pj_array_erase(clist->checks, sizeof(clist->checks[0]), clist->count, check_idx); + --clist->count; +} + +/* Prune checklist, this must have been done after the checklist + * is sorted. + */ +static pj_status_t prune_checklist(pj_ice_sess *ice, pj_ice_sess_checklist *clist) +{ + unsigned i; + + /* Since an agent cannot send requests directly from a reflexive + * candidate, but only from its base, the agent next goes through the + * sorted list of candidate pairs. For each pair where the local + * candidate is server reflexive, the server reflexive candidate MUST be + * replaced by its base. Once this has been done, the agent MUST prune + * the list. This is done by removing a pair if its local and remote + * candidates are identical to the local and remote candidates of a pair + * higher up on the priority list. The result is a sequence of ordered + * candidate pairs, called the check list for that media stream. + */ + /* First replace SRFLX candidates with their base */ + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_cand *srflx = clist->checks[i].lcand; + + if (srflx->type == PJ_ICE_CAND_TYPE_SRFLX || srflx->type == PJ_ICE_CAND_TYPE_PRFLX) { + /* Find the base for this candidate */ + unsigned j; + for (j = 0; j < ice->lcand_cnt; ++j) { + pj_ice_sess_cand *host = &ice->lcand[j]; + + if (host->type != PJ_ICE_CAND_TYPE_HOST) + continue; + + if (pj_sockaddr_cmp(&srflx->base_addr, &host->addr) == 0) { + /* Replace this SRFLX/PRFLX with its BASE */ + clist->checks[i].lcand = host; + break; + } + } + + if (j == ice->lcand_cnt) { + char baddr[PJ_INET6_ADDRSTRLEN]; + /* Host candidate not found this this srflx! */ + LOG4((ice->obj_name, "Base candidate %s:%d not found for srflx candidate %d", + pj_sockaddr_print(&srflx->base_addr, baddr, sizeof(baddr), 2), + pj_sockaddr_get_port(&srflx->base_addr), GET_LCAND_ID(srflx))); + return PJNATH_EICENOHOSTCAND; + } + } + } + + /* Next remove a pair if its local and remote candidates are identical + * to the local and remote candidates of a pair higher up on the priority + * list + */ + /* + * Not in ICE! + * Remove host candidates if their base are the the same! + */ + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_cand *licand = clist->checks[i].lcand; + pj_ice_sess_cand *ricand = clist->checks[i].rcand; + unsigned j; + + for (j = i + 1; j < clist->count;) { + pj_ice_sess_cand *ljcand = clist->checks[j].lcand; + pj_ice_sess_cand *rjcand = clist->checks[j].rcand; + const char *reason = NULL; + + /* Only discard Frozen/Waiting checks */ + if (clist->checks[j].state != PJ_ICE_SESS_CHECK_STATE_FROZEN && + clist->checks[j].state != PJ_ICE_SESS_CHECK_STATE_WAITING) { + ++j; + continue; + } + + if ((licand == ljcand) && (ricand == rjcand)) { + reason = "duplicate found"; + } else if ((rjcand == ricand) && (pj_sockaddr_cmp(&ljcand->base_addr, &licand->base_addr) == 0)) { + reason = "equal base"; + } + + if (reason != NULL) { + /* Found duplicate, remove it */ + remove_check(ice, clist, j, reason); + } else { + ++j; + } + } + } + + return PJ_SUCCESS; +} + +/* Timer callback */ +static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_ice_sess *ice = (pj_ice_sess *)te->user_data; + enum timer_type type = (enum timer_type)te->id; + + PJ_UNUSED_ARG(th); + + pj_grp_lock_acquire(ice->grp_lock); + + te->id = TIMER_NONE; + + if (ice->is_destroying) { + /* Stray timer, could happen when destroy is invoked while callback + * is pending. */ + pj_grp_lock_release(ice->grp_lock); + return; + } + + switch (type) { + case TIMER_CONTROLLED_WAIT_NOM: + LOG4((ice->obj_name, "Controlled agent timed-out in waiting for the controlling " + "agent to send nominated check. Setting state to fail now..")); + on_ice_complete(ice, PJNATH_EICENOMTIMEOUT); + break; + case TIMER_COMPLETION_CALLBACK: { + void (*on_ice_complete)(pj_ice_sess * ice, pj_status_t status); + pj_status_t ice_status; + + /* Start keep-alive timer but don't send any packets yet. + * Need to do it here just in case app destroy the session + * in the callback. + */ + if (ice->ice_status == PJ_SUCCESS) + ice_keep_alive(ice, PJ_FALSE); + + /* Release mutex in case app destroy us in the callback */ + ice_status = ice->ice_status; + on_ice_complete = ice->cb.on_ice_complete; + + /* Notify app about ICE completion*/ + if (on_ice_complete) + (*on_ice_complete)(ice, ice_status); + } break; + case TIMER_START_NOMINATED_CHECK: + start_nominated_check(ice); + break; + case TIMER_KEEP_ALIVE: + ice_keep_alive(ice, PJ_TRUE); + break; + case TIMER_NONE: + /* Nothing to do, just to get rid of gcc warning */ + break; + } + + pj_grp_lock_release(ice->grp_lock); +} + +/* Send keep-alive */ +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now) +{ + if (send_now) { + /* Send Binding Indication for the component */ + pj_ice_sess_comp *comp = &ice->comp[ice->comp_ka]; + pj_stun_tx_data *tdata; + pj_ice_sess_check *the_check; + pj_ice_msg_data *msg_data; + int addr_len; + pj_bool_t saved; + pj_status_t status; + + /* Must have nominated check by now */ + pj_assert(comp->nominated_check != NULL); + the_check = comp->nominated_check; + + /* Create the Binding Indication */ + status = pj_stun_session_create_ind(comp->stun_sess, PJ_STUN_BINDING_INDICATION, &tdata); + if (status != PJ_SUCCESS) + goto done; + + /* Need the transport_id */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = the_check->lcand->transport_id; + + /* RFC 5245 Section 10: + * The Binding Indication SHOULD contain the FINGERPRINT attribute + * to aid in demultiplexing, but SHOULD NOT contain any other + * attributes. + */ + saved = pj_stun_session_use_fingerprint(comp->stun_sess, PJ_TRUE); + + /* Send to session */ + addr_len = pj_sockaddr_get_len(&the_check->rcand->addr); + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, PJ_FALSE, PJ_FALSE, &the_check->rcand->addr, + addr_len, tdata); + + /* Restore FINGERPRINT usage */ + pj_stun_session_use_fingerprint(comp->stun_sess, saved); + + done: + ice->comp_ka = (ice->comp_ka + 1) % ice->comp_cnt; + } + + if (ice->timer.id == TIMER_NONE) { + pj_time_val delay = {0, 0}; + + delay.msec = + (PJ_ICE_SESS_KEEP_ALIVE_MIN + (pj_rand() % PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND)) * 1000 / ice->comp_cnt; + pj_time_val_normalize(&delay); + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, TIMER_KEEP_ALIVE, + ice->grp_lock); + + } else { + pj_assert(!"Not expected any timer active"); + } +} + +/* This function is called when ICE processing completes */ +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) +{ + if (!ice->is_complete) { + ice->is_complete = PJ_TRUE; + ice->ice_status = status; + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, TIMER_NONE); + + /* Log message */ + LOG4((ice->obj_name, "ICE process complete, status=%s", + pj_strerror(status, ice->tmp.errmsg, sizeof(ice->tmp.errmsg)).ptr)); + + dump_checklist("Valid list", ice, &ice->valid_list); + + /* Call callback */ + if (ice->cb.on_ice_complete) { + pj_time_val delay = {0, 0}; + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, TIMER_COMPLETION_CALLBACK, + ice->grp_lock); + } + } +} + +/* Update valid check and nominated check for the candidate */ +static void update_comp_check(pj_ice_sess *ice, unsigned comp_id, pj_ice_sess_check *check) +{ + pj_ice_sess_comp *comp; + + comp = find_comp(ice, comp_id); + if (comp->valid_check == NULL) { + comp->valid_check = check; + } else { + pj_bool_t update = PJ_FALSE; + + /* Update component's valid check with conditions: + * - it is the first nominated check, or + * - it has higher prio, as long as nomination status is NOT degraded + * (existing is nominated -> new is not-nominated). + */ + if (!comp->nominated_check && check->nominated) { + update = PJ_TRUE; + } else if (CMP_CHECK_PRIO(comp->valid_check, check) < 0 && (!comp->nominated_check || check->nominated)) { + update = PJ_TRUE; + } + + if (update) + comp->valid_check = check; + } + + if (check->nominated) { + /* Update the nominated check for the component */ + if (comp->nominated_check == NULL) { + comp->nominated_check = check; + } else { + if (CMP_CHECK_PRIO(comp->nominated_check, check) < 0) + comp->nominated_check = check; + } + } +} + +/* Check if ICE nego completed */ +static pj_bool_t check_ice_complete(pj_ice_sess *ice) +{ + unsigned i; + pj_bool_t no_pending_check = PJ_FALSE; + + /* Still in 8.2. Updating States + * + * o Once there is at least one nominated pair in the valid list for + * every component of at least one media stream and the state of the + * check list is Running: + * + * * The agent MUST change the state of processing for its check + * list for that media stream to Completed. + * + * * The agent MUST continue to respond to any checks it may still + * receive for that media stream, and MUST perform triggered + * checks if required by the processing of Section 7.2. + * + * * The agent MAY begin transmitting media for this media stream as + * described in Section 11.1 + */ + + /* See if all components have nominated pair. If they do, then mark + * ICE processing as success, otherwise wait. + */ + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].nominated_check == NULL) + break; + } + if (i == ice->comp_cnt) { + /* All components have nominated pair */ + on_ice_complete(ice, PJ_SUCCESS); + return PJ_TRUE; + } + + /* Note: this is the stuffs that we don't do in 7.1.2.2.2, since our + * ICE session only supports one media stream for now: + * + * 7.1.2.2.2. Updating Pair States + * + * 2. If there is a pair in the valid list for every component of this + * media stream (where this is the actual number of components being + * used, in cases where the number of components signaled in the SDP + * differs from offerer to answerer), the success of this check may + * unfreeze checks for other media streams. + */ + + /* 7.1.2.3. Check List and Timer State Updates + * Regardless of whether the check was successful or failed, the + * completion of the transaction may require updating of check list and + * timer states. + * + * If all of the pairs in the check list are now either in the Failed or + * Succeeded state, and there is not a pair in the valid list for each + * component of the media stream, the state of the check list is set to + * Failed. + */ + + /* + * See if all checks in the checklist have completed. If we do, + * then mark ICE processing as failed. + */ + if (!ice->is_trickling) { + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->state < PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { + break; + } + } + no_pending_check = (i == ice->clist.count); + } + + if (no_pending_check) { + /* All checks have completed, but we don't have nominated pair. + * If agent's role is controlled, check if all components have + * valid pair. If it does, this means the controlled agent has + * finished the check list and it's waiting for controlling + * agent to send checks with USE-CANDIDATE flag set. + */ + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED) { + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* This component ID doesn't have valid pair. + * Mark ICE as failed. + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + } else { + /* All components have a valid pair. + * We should wait until we receive nominated checks. + */ + if (ice->timer.id == TIMER_NONE && ice->opt.controlled_agent_want_nom_timeout >= 0) { + pj_time_val delay; + + delay.sec = 0; + delay.msec = ice->opt.controlled_agent_want_nom_timeout; + pj_time_val_normalize(&delay); + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, + TIMER_CONTROLLED_WAIT_NOM, ice->grp_lock); + + LOG5((ice->obj_name, + "All checks have completed. Controlled agent now " + "waits for nomination from controlling agent " + "(timeout=%d msec)", + ice->opt.controlled_agent_want_nom_timeout)); + } + return PJ_FALSE; + } + + /* Unreached */ + + } else if (ice->is_nominating) { + /* We are controlling agent and all checks have completed but + * there's at least one component without nominated pair (or + * more likely we don't have any nominated pairs at all). + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + + } else { + /* We are controlling agent and all checks have completed. If + * we have valid list for every component, then move on to + * sending nominated check, otherwise we have failed. + */ + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* At least one component doesn't have a valid check. Mark + * ICE as failed. + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + } + + /* Now it's time to send connectivity check with nomination + * flag set. + */ + LOG4((ice->obj_name, "All checks have completed, starting nominated checks now")); + start_nominated_check(ice); + return PJ_FALSE; + } + } + + /* If this connectivity check has been successful, scan all components + * and see if they have a valid pair, if we are controlling and we haven't + * started our nominated check yet. + */ + /* Always scan regardless the last connectivity check result */ + if (/*check->err_code == PJ_SUCCESS && */ + ice->role == PJ_ICE_SESS_ROLE_CONTROLLING && !ice->is_nominating && ice->timer.id == TIMER_NONE) { + pj_time_val delay; + + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* Some components still don't have valid pair, continue + * processing. + */ + return PJ_FALSE; + } + + LOG4((ice->obj_name, "Scheduling nominated check in %d ms", ice->opt.nominated_check_delay)); + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, TIMER_NONE); + + /* All components have valid pair. Let connectivity checks run for + * a little bit more time, then start our nominated check. + */ + delay.sec = 0; + delay.msec = ice->opt.nominated_check_delay; + pj_time_val_normalize(&delay); + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, TIMER_START_NOMINATED_CHECK, + ice->grp_lock); + return PJ_FALSE; + } + + /* We still have checks to perform */ + return PJ_FALSE; +} + +/* This function is called when one check completes */ +static pj_bool_t on_check_complete(pj_ice_sess *ice, pj_ice_sess_check *check) +{ + pj_ice_sess_comp *comp; + unsigned i; + + pj_assert(check->state >= PJ_ICE_SESS_CHECK_STATE_SUCCEEDED); + + comp = find_comp(ice, check->lcand->comp_id); + + /* 7.1.2.2.2. Updating Pair States + * + * The agent sets the state of the pair that generated the check to + * Succeeded. The success of this check might also cause the state of + * other checks to change as well. The agent MUST perform the following + * two steps: + * + * 1. The agent changes the states for all other Frozen pairs for the + * same media stream and same foundation to Waiting. Typically + * these other pairs will have different component IDs but not + * always. + */ + if (check->err_code == PJ_SUCCESS) { + + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->foundation_idx == check->foundation_idx && c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) { + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + } + + LOG5((ice->obj_name, "Check %d is successful%s", GET_CHECK_ID(&ice->clist, check), + (check->nominated ? " and nominated" : ""))); + + /* On the first valid pair, we call the callback, if present */ + if (ice->valid_pair_found == PJ_FALSE) { + ice->valid_pair_found = PJ_TRUE; + + if (ice->cb.on_valid_pair) { + (*ice->cb.on_valid_pair)(ice); + } + } + } + + /* 8.2. Updating States + * + * For both controlling and controlled agents, the state of ICE + * processing depends on the presence of nominated candidate pairs in + * the valid list and on the state of the check list: + * + * o If there are no nominated pairs in the valid list for a media + * stream and the state of the check list is Running, ICE processing + * continues. + * + * o If there is at least one nominated pair in the valid list: + * + * - The agent MUST remove all Waiting and Frozen pairs in the check + * list for the same component as the nominated pairs for that + * media stream + * + * - If an In-Progress pair in the check list is for the same + * component as a nominated pair, the agent SHOULD cease + * retransmissions for its check if its pair priority is lower + * than the lowest priority nominated pair for that component + */ + if (check->err_code == PJ_SUCCESS && check->nominated) { + + for (i = 0; i < ice->clist.count; ++i) { + + pj_ice_sess_check *c = &ice->clist.checks[i]; + + if (c->lcand->comp_id == check->lcand->comp_id) { + + if (c->state < PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + + /* Just fail Frozen/Waiting check */ + LOG5((ice->obj_name, "Check %s to be failed because state is %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, c), check_state_name[c->state])); + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_FAILED, PJ_ECANCELLED); + + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS && + (PJ_ICE_CANCEL_ALL || CMP_CHECK_PRIO(c, check) < 0)) { + + /* State is IN_PROGRESS, cancel transaction */ + if (c->tdata) { + LOG5((ice->obj_name, "Cancelling check %s (In Progress)", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, c))); + pj_stun_session_cancel_req(comp->stun_sess, c->tdata, PJ_FALSE, 0); + c->tdata = NULL; + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_FAILED, PJ_ECANCELLED); + } + } + } + } + } + + return check_ice_complete(ice); +} + +/* Get foundation index of a check pair. This function can also be used for + * adding a new foundation (combination of local & remote cands foundations) + * to checklist. + */ +static int get_check_foundation_idx(pj_ice_sess *ice, const pj_ice_sess_cand *lcand, const pj_ice_sess_cand *rcand, + pj_bool_t add_if_not_found) +{ + pj_ice_sess_checklist *clist = &ice->clist; + char fnd_str[65]; + unsigned i; + + pj_ansi_snprintf(fnd_str, sizeof(fnd_str), "%.*s|%.*s", (int)lcand->foundation.slen, lcand->foundation.ptr, + (int)rcand->foundation.slen, rcand->foundation.ptr); + for (i = 0; i < clist->foundation_cnt; ++i) { + if (pj_strcmp2(&clist->foundation[i], fnd_str) == 0) + return i; + } + + if (add_if_not_found && clist->foundation_cnt < PJ_ICE_MAX_CHECKS) { + pj_strdup2(ice->pool, &clist->foundation[i], fnd_str); + ++clist->foundation_cnt; + return i; + } + + return -1; +} + +/* Discard a pair check with Failed state or lowest prio (as long as lower + * than prio_lower_than. + */ +static int discard_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, const pj_timestamp *prio_lower_than) +{ + /* Discard any Failed check */ + unsigned k; + for (k = 0; k < clist->count; ++k) { + if (clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_FAILED) { + remove_check(ice, clist, k, "too many, drop Failed"); + return 1; + } + } + + /* If none, discard the lowest prio */ + /* Re-sort before discarding the last */ + sort_checklist(ice, clist); + if (!prio_lower_than || pj_cmp_timestamp(&clist->checks[k - 1].prio, prio_lower_than) < 0) { + remove_check(ice, clist, k - 1, "too many, drop low-prio"); + return 1; + } + + return 0; +} + +/* Timer callback for end of candidate indication from remote */ +static void end_of_cand_ind_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_ice_sess *ice = (pj_ice_sess *)te->user_data; + PJ_UNUSED_ARG(th); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_trickling && !ice->is_complete) { + LOG5((ice->obj_name, "End-of-candidate timer timeout, any future " + "remote candidate update will be ignored")); + ice->is_trickling = PJ_FALSE; + + /* ICE checks may have been completed/failed */ + check_ice_complete(ice); + } + + pj_grp_lock_release(ice->grp_lock); +} + +/* Add remote candidates and create/update checklist */ +static pj_status_t add_rcand_and_update_checklist(pj_ice_sess *ice, unsigned rem_cand_cnt, + const pj_ice_sess_cand rem_cand[], pj_bool_t trickle_done) +{ + pj_ice_sess_checklist *clist; + unsigned i, j, new_pair = 0; + pj_status_t status; + + /* Save remote candidates */ + for (i = 0; i < rem_cand_cnt; ++i) { + pj_ice_sess_cand *cn = &ice->rcand[ice->rcand_cnt]; + + /* Check component ID */ + if (rem_cand[i].comp_id == 0 || rem_cand[i].comp_id > ice->comp_cnt) { + continue; + } + + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) { + /* Trickle ICE: + * Make sure that candidate has not been added + */ + for (j = 0; j < ice->rcand_cnt; ++j) { + const pj_ice_sess_cand *c1 = &rem_cand[i]; + const pj_ice_sess_cand *c2 = &ice->rcand[j]; + if (c1->comp_id == c2->comp_id && c1->type == c2->type && pj_sockaddr_cmp(&c1->addr, &c2->addr) == 0) { + break; + } + } + + /* Skip candidate, it has been added */ + if (j < ice->rcand_cnt) + continue; + } + + /* Available cand slot? */ + if (ice->rcand_cnt >= PJ_ICE_MAX_CAND) { + char tmp[PJ_INET6_ADDRSTRLEN + 10]; + PJ_PERROR(3, (ice->obj_name, PJ_ETOOMANY, "Cannot add remote candidate %s", + pj_sockaddr_print(&rem_cand[i].addr, tmp, sizeof(tmp), 3))); + continue; + } + + /* Add this candidate */ + pj_memcpy(cn, &rem_cand[i], sizeof(pj_ice_sess_cand)); + pj_strdup(ice->pool, &cn->foundation, &rem_cand[i].foundation); + cn->id = ice->rcand_cnt++; + } + + /* Generate checklist */ + clist = &ice->clist; + for (i = 0; i < ice->lcand_cnt; ++i) { + /* First index of remote cand to be paired with this local cand */ + unsigned rstart = (i >= ice->lcand_paired) ? 0 : ice->rcand_paired; + for (j = rstart; j < ice->rcand_cnt; ++j) { + + pj_ice_sess_cand *lcand = &ice->lcand[i]; + pj_ice_sess_cand *rcand = &ice->rcand[j]; + pj_ice_sess_check *chk = NULL; + + if (clist->count >= PJ_ICE_MAX_CHECKS) { + // Instead of returning PJ_ETOOMANY, discard Failed/low-prio. + // If this check is actually the lowest prio, just skip it. + // return PJ_ETOOMANY; + pj_timestamp max_prio = CALC_CHECK_PRIO(ice, lcand, rcand); + if (discard_check(ice, clist, &max_prio) == 0) + continue; + } + + /* A local candidate is paired with a remote candidate if + * and only if the two candidates have the same component ID + * and have the same IP address version. + */ + if ((lcand->comp_id != rcand->comp_id) || (lcand->addr.addr.sa_family != rcand->addr.addr.sa_family)) { + continue; + } + +#if 0 + /* Trickle ICE: + * Make sure that pair has not been added to checklist + */ + // Should not happen, paired cands are already marked using + // lcand_paired & rcand_paired. + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) { + unsigned k; + for (k=0; kcount; ++k) { + chk = &clist->checks[k]; + if (chk->lcand == lcand && chk->rcand == rcand) + break; + } + + /* Pair already exists */ + if (k < clist->count) + continue; + } +#endif + + /* Add the pair */ + chk = &clist->checks[clist->count]; + chk->lcand = lcand; + chk->rcand = rcand; + chk->prio = CALC_CHECK_PRIO(ice, lcand, rcand); + chk->state = PJ_ICE_SESS_CHECK_STATE_FROZEN; + chk->foundation_idx = get_check_foundation_idx(ice, lcand, rcand, PJ_TRUE); + + /* Check if foundation cannot be added (e.g: list is full) */ + if (chk->foundation_idx < 0) + continue; + + /* Check if the check can be unfrozen */ + if (ice->is_trickling) { + unsigned k; + + /* For this foundation, unfreeze if this pair has the lowest + * comp ID, or the highest priority among existing pairs with + * same comp ID, or any other checks in Succeeded. + */ + for (k = 0; k < clist->count; ++k) { + if (clist->checks[k].foundation_idx != chk->foundation_idx) + continue; + + /* Unfreeze if there is already check in Succeeded */ + if (clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { + k = clist->count; + break; + } + + /* Don't unfreeze if there is already check in Waiting or + * In Progress. + */ + if (clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_WAITING || + clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + break; + } + + /* Don't unfreeze if this pair does not have the lowest + * comp ID. + */ + if (clist->checks[k].lcand->comp_id < lcand->comp_id) + break; + + /* Don't unfreeze if this pair has the lowest comp ID, but + * does not have the highest prio. + */ + if (clist->checks[k].lcand->comp_id == lcand->comp_id && + pj_cmp_timestamp(&clist->checks[k].prio, &chk->prio) > 0) { + break; + } + } + + /* Unfreeze */ + if (k == clist->count) + check_set_state(ice, chk, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + + clist->count++; + new_pair++; + } + } + + /* This could happen if candidates have no matching address families */ + if (clist->count == 0 && trickle_done) { + LOG4((ice->obj_name, "Error: no checklist can be created")); + return PJ_ENOTFOUND; + } + + /* Update paired candidate counts */ + ice->lcand_paired = ice->lcand_cnt; + ice->rcand_paired = ice->rcand_cnt; + + if (new_pair) { + /* Sort checklist based on priority */ + // dump_checklist("Checklist before sort:", ice, &ice->clist); + sort_checklist(ice, clist); + + /* Prune the checklist */ + // dump_checklist("Checklist before prune:", ice, &ice->clist); + status = prune_checklist(ice, clist); + if (status != PJ_SUCCESS) + return status; + } + + /* Regular ICE or trickle ICE after end-of-candidates indication: + * Disable our components which don't have matching component + */ + if (trickle_done) { + unsigned highest_comp = 0; + + for (i = 0; i < ice->rcand_cnt; ++i) { + if (ice->rcand[i].comp_id > highest_comp) + highest_comp = ice->rcand[i].comp_id; + } + + for (i = highest_comp; i < ice->comp_cnt; ++i) { + if (ice->comp[i].stun_sess) { + pj_stun_session_destroy(ice->comp[i].stun_sess); + pj_bzero(&ice->comp[i], sizeof(ice->comp[i])); + } + } + ice->comp_cnt = highest_comp; + + /* If using trickle ICE and end-of-candidate has been signalled, + * check for ICE nego completion. + */ + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) + check_ice_complete(ice); + } + + /* For trickle ICE: resume the periodic check, it may be halted when + * there is no available check pair. + */ + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED && clist->count > 0 && !ice->is_complete && + clist->state == PJ_ICE_SESS_CHECKLIST_ST_RUNNING) { + if (!pj_timer_entry_running(&clist->timer)) { + pj_time_val delay = {0, 0}; + status = pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &clist->timer, &delay, PJ_TRUE, + ice->grp_lock); + if (status == PJ_SUCCESS) { + LOG5((ice->obj_name, "Trickle ICE resumes periodic check because " + "check pair is available")); + } + } + } + + /* Stop the end-of-candidates indication timer if trickling is done */ + if (trickle_done && pj_timer_entry_running(&ice->timer_end_of_cand)) { + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer_end_of_cand, 0); + } + + return PJ_SUCCESS; +} + +/* Create checklist by pairing local candidates with remote candidates */ +PJ_DEF(pj_status_t) +pj_ice_sess_create_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[]) +{ + pj_ice_sess_checklist *clist; + char buf[128]; + pj_str_t username; + timer_data *td; + pj_status_t status; + + PJ_ASSERT_RETURN(ice && rem_ufrag && rem_passwd, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->tx_ufrag.slen) { + /* Checklist has been created */ + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Save credentials */ + username.ptr = buf; + + pj_strcpy(&username, rem_ufrag); + pj_strcat2(&username, ":"); + pj_strcat(&username, &ice->rx_ufrag); + pj_strdup(ice->pool, &ice->tx_uname, &username); + + pj_strdup(ice->pool, &ice->tx_ufrag, rem_ufrag); + pj_strdup(ice->pool, &ice->tx_pass, rem_passwd); + + pj_strcpy(&username, &ice->rx_ufrag); + pj_strcat2(&username, ":"); + pj_strcat(&username, rem_ufrag); + pj_strdup(ice->pool, &ice->rx_uname, &username); + + /* Init timer entry in the checklist. Initially the timer ID is FALSE + * because timer is not running. + */ + clist = &ice->clist; + clist->timer.id = PJ_FALSE; + td = PJ_POOL_ZALLOC_T(ice->pool, timer_data); + td->ice = ice; + td->clist = clist; + clist->timer.user_data = (void *)td; + clist->timer.cb = &periodic_timer; + + ice->clist.count = 0; + ice->lcand_paired = ice->rcand_paired = 0; + + /* Build checklist only if both sides have candidates already */ + if (ice->lcand_cnt > 0 && rem_cand_cnt > 0) { + status = add_rcand_and_update_checklist(ice, rem_cand_cnt, rem_cand, !ice->is_trickling); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice->grp_lock); + return status; + } + + /* Log checklist */ + dump_checklist("Checklist created:", ice, clist); + } + + pj_grp_lock_release(ice->grp_lock); + + return PJ_SUCCESS; +} + +/* Update checklist by pairing local candidates with remote candidates */ +PJ_DEF(pj_status_t) +pj_ice_sess_update_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[], pj_bool_t trickle_done) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(ice && ((rem_cand_cnt == 0) || (rem_ufrag && rem_passwd && rem_cand)), PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + /* Ignore if remote ufrag has not known yet */ + if (ice->tx_ufrag.slen == 0) { + LOG5((ice->obj_name, "Cannot update ICE checklist when remote ufrag is unknown")); + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + /* Ignore if trickle has been stopped (e.g: received end-of-candidate) */ + if (!ice->is_trickling && rem_cand_cnt) { + LOG5((ice->obj_name, "Ignored remote candidate update as ICE trickling has ended")); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Verify remote ufrag & passwd, if remote candidate specified */ + if (rem_cand_cnt && (pj_strcmp(&ice->tx_ufrag, rem_ufrag) || pj_strcmp(&ice->tx_pass, rem_passwd))) { + LOG5((ice->obj_name, "Ignored remote candidate update due to remote " + "ufrag/pwd mismatch")); + rem_cand_cnt = 0; + } + + if (status == PJ_SUCCESS) { + status = add_rcand_and_update_checklist(ice, rem_cand_cnt, rem_cand, trickle_done); + } + + /* Log checklist */ + if (status == PJ_SUCCESS) + dump_checklist("Checklist updated:", ice, &ice->clist); + + if (trickle_done && ice->is_trickling) { + LOG5((ice->obj_name, "Remote signalled end-of-candidates " + "and local candidates gathering completed, " + "will ignore any candidate update")); + ice->is_trickling = PJ_FALSE; + } + + pj_grp_lock_release(ice->grp_lock); + + return status; +} + +/* Perform check on the specified candidate pair. */ +static pj_status_t perform_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, unsigned check_id, pj_bool_t nominate) +{ + pj_ice_sess_comp *comp; + pj_ice_msg_data *msg_data; + pj_ice_sess_check *check; + const pj_ice_sess_cand *lcand; + const pj_ice_sess_cand *rcand; + pj_uint32_t prio; + pj_status_t status; + + check = &clist->checks[check_id]; + lcand = check->lcand; + rcand = check->rcand; + comp = find_comp(ice, lcand->comp_id); + + LOG5((ice->obj_name, "Sending connectivity check for check %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), clist, check))); + pj_log_push_indent(); + + /* Create request */ + status = pj_stun_session_create_req(comp->stun_sess, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, NULL, &check->tdata); + if (status != PJ_SUCCESS) { + pjnath_perror(ice->obj_name, "Error creating STUN request", status); + pj_log_pop_indent(); + return status; + } + + /* Attach data to be retrieved later when STUN request transaction + * completes and on_stun_request_complete() callback is called. + */ + msg_data = PJ_POOL_ZALLOC_T(check->tdata->pool, pj_ice_msg_data); + msg_data->transport_id = lcand->transport_id; + msg_data->has_req_data = PJ_TRUE; + msg_data->data.req.ice = ice; + msg_data->data.req.clist = clist; + msg_data->data.req.ckid = check_id; + msg_data->data.req.lcand = check->lcand; + msg_data->data.req.rcand = check->rcand; + + /* Add PRIORITY */ +#if PJNATH_ICE_PRIO_STD + prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535 - lcand->id, lcand->comp_id); +#else + prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) - lcand->id, lcand->comp_id); +#endif + pj_stun_msg_add_uint_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_PRIORITY, prio); + + /* Add USE-CANDIDATE and set this check to nominated. + * Also add ICE-CONTROLLING or ICE-CONTROLLED + */ + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING) { + if (nominate) { + pj_stun_msg_add_empty_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_USE_CANDIDATE); + check->nominated = PJ_TRUE; + } + + pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_ICE_CONTROLLING, + &ice->tie_breaker); + + } else { + pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_ICE_CONTROLLED, + &ice->tie_breaker); + } + + /* Note that USERNAME and MESSAGE-INTEGRITY will be added by the + * STUN session. + */ + + /* Initiate STUN transaction to send the request */ + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, PJ_FALSE, PJ_TRUE, &rcand->addr, + pj_sockaddr_get_len(&rcand->addr), check->tdata); + if (status != PJ_SUCCESS) { + check->tdata = NULL; + pjnath_perror(ice->obj_name, "Error sending STUN request", status); + pj_log_pop_indent(); + return status; + } + + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS, PJ_SUCCESS); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* Start periodic check for the specified checklist. + * This callback is called by timer on every Ta (20msec by default) + */ +static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_timer_entry *te) +{ + timer_data *td; + pj_ice_sess *ice; + pj_ice_sess_checklist *clist; + unsigned i, start_count = 0; + pj_status_t status; + + td = (struct timer_data *)te->user_data; + ice = td->ice; + clist = td->clist; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Set timer ID to FALSE first */ + te->id = PJ_FALSE; + + /* Set checklist state to Running */ + clist_set_state(ice, clist, PJ_ICE_SESS_CHECKLIST_ST_RUNNING); + + LOG5((ice->obj_name, "Starting checklist periodic check")); + pj_log_push_indent(); + + /* Send STUN Binding request for check with highest priority on + * Waiting state. + */ + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_check *check = &clist->checks[i]; + + if (check->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { + status = perform_check(ice, clist, i, ice->is_nominating); + if (status != PJ_SUCCESS) { + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + } + + ++start_count; + break; + } + } + + /* If we don't have anything in Waiting state, perform check to + * highest priority pair that is in Frozen state. + */ + if (start_count == 0) { + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_check *check = &clist->checks[i]; + + if (check->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) { + status = perform_check(ice, clist, i, ice->is_nominating); + if (status != PJ_SUCCESS) { + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + } + + ++start_count; + break; + } + } + } + + /* Schedule next check for next candidate pair, unless there is no + * suitable candidate pair (all pairs have been checked or empty + * checklist). + */ + if (start_count != 0) { + pj_time_val timeout = {0, PJ_ICE_TA_VAL}; + + pj_time_val_normalize(&timeout); + pj_timer_heap_schedule_w_grp_lock(th, te, &timeout, PJ_TRUE, ice->grp_lock); + } + + pj_grp_lock_release(ice->grp_lock); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* Start sending connectivity check with USE-CANDIDATE */ +static void start_nominated_check(pj_ice_sess *ice) +{ + pj_time_val delay; + unsigned i; + pj_status_t status; + + LOG4((ice->obj_name, "Starting nominated check..")); + pj_log_push_indent(); + + pj_assert(ice->is_nominating == PJ_FALSE); + + /* Stop trickling if not yet */ + if (ice->is_trickling) { + ice->is_trickling = PJ_FALSE; + LOG5((ice->obj_name, "Trickling stopped as nomination started.")); + } + + /* Stop our timer if it's active */ + if (ice->timer.id == TIMER_START_NOMINATED_CHECK) { + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, TIMER_NONE); + } + + /* For each component, set the check state of valid check with + * highest priority to Waiting (it should have Success state now). + */ + for (i = 0; i < ice->comp_cnt; ++i) { + unsigned j; + const pj_ice_sess_check *vc = ice->comp[i].valid_check; + + pj_assert(ice->comp[i].nominated_check == NULL); + pj_assert(vc->err_code == PJ_SUCCESS); + + for (j = 0; j < ice->clist.count; ++j) { + pj_ice_sess_check *c = &ice->clist.checks[j]; + if (c->lcand->transport_id == vc->lcand->transport_id && c->rcand == vc->rcand) { + pj_assert(c->err_code == PJ_SUCCESS); + c->state = PJ_ICE_SESS_CHECK_STATE_FROZEN; + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, PJ_SUCCESS); + break; + } + } + } + + /* And (re)start the periodic check */ + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->clist.timer, PJ_FALSE); + + delay.sec = delay.msec = 0; + status = + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->clist.timer, &delay, PJ_TRUE, ice->grp_lock); + if (status == PJ_SUCCESS) { + LOG5((ice->obj_name, "Periodic timer rescheduled..")); + } + + ice->is_nominating = PJ_TRUE; + pj_log_pop_indent(); +} + +/* Timer callback to perform periodic check */ +static void periodic_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + start_periodic_check(th, te); +} + +/* + * Start ICE periodic check. This function will return immediately, and + * application will be notified about the connectivity check status in + * #pj_ice_sess_cb callback. + */ +PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) +{ + pj_ice_sess_checklist *clist; + pj_ice_rx_check *rcheck; + unsigned i; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + + /* Checklist must have been created */ + if (ice->clist.count == 0 && !ice->is_trickling) + return PJ_EINVALIDOP; + + /* Lock session */ + pj_grp_lock_acquire(ice->grp_lock); + + LOG4((ice->obj_name, "Starting ICE check..")); + pj_log_push_indent(); + + /* If we are using aggressive nomination, set the is_nominating state */ + if (ice->opt.aggressive) + ice->is_nominating = PJ_TRUE; + + /* The agent examines the check list for the first media stream (a + * media stream is the first media stream when it is described by + * the first m-line in the SDP offer and answer). For that media + * stream, it: + * + * - Groups together all of the pairs with the same foundation, + * + * - For each group, sets the state of the pair with the lowest + * component ID to Waiting. If there is more than one such pair, + * the one with the highest priority is used. + */ + + clist = &ice->clist; + for (i = 0; i < clist->foundation_cnt; ++i) { + unsigned k; + pj_ice_sess_check *chk = NULL; + + for (k = 0; k < clist->count; ++k) { + pj_ice_sess_check *c = &clist->checks[k]; + if (c->foundation_idx != (int)i || c->state != PJ_ICE_SESS_CHECK_STATE_FROZEN) { + continue; + } + + /* First pair of this foundation */ + if (chk == NULL) { + chk = c; + continue; + } + + /* Found the lowest comp ID so far */ + if (c->lcand->comp_id < chk->lcand->comp_id) { + chk = c; + continue; + } + + /* Found the lowest comp ID and the highest prio so far */ + if (c->lcand->comp_id == chk->lcand->comp_id && pj_cmp_timestamp(&c->prio, &chk->prio) > 0) { + chk = c; + continue; + } + } + + /* Unfreeze */ + if (chk) + check_set_state(ice, chk, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + + /* First, perform all pending triggered checks, simultaneously. */ + rcheck = ice->early_check.next; + while (rcheck != &ice->early_check) { + LOG4((ice->obj_name, "Performing delayed triggerred check for component %d", rcheck->comp_id)); + pj_log_push_indent(); + handle_incoming_check(ice, rcheck); + rcheck = rcheck->next; + pj_log_pop_indent(); + } + pj_list_init(&ice->early_check); + + /* Start periodic check */ + /* We could start it immediately like below, but lets schedule timer + * instead to reduce stack usage: + * return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); + */ + if (!pj_timer_entry_running(&clist->timer)) { + pj_time_val delay = {0, 0}; + status = + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &clist->timer, &delay, PJ_TRUE, ice->grp_lock); + } + + /* For trickle ICE, start timer for end-of-candidates indication from + * remote. + */ + if (ice->is_trickling && !pj_timer_entry_running(&ice->timer_end_of_cand)) { + pj_time_val delay = {PJ_TRICKLE_ICE_END_OF_CAND_TIMEOUT, 0}; + pj_timer_entry_init(&ice->timer_end_of_cand, 0, ice, &end_of_cand_ind_timer); + status = pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer_end_of_cand, &delay, PJ_TRUE, + ice->grp_lock); + if (status != PJ_SUCCESS) { + LOG4((ice->obj_name, "Failed to schedule end-of-candidate indication timer")); + } + } + + pj_grp_lock_release(ice->grp_lock); + pj_log_pop_indent(); + return status; +} + +////////////////////////////////////////////////////////////////////////////// + +/* Callback called by STUN session to send the STUN message. + * STUN session also doesn't have a transport, remember?! + */ +static pj_status_t on_stun_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + stun_data *sd = (stun_data *)pj_stun_session_get_user_data(sess); + pj_ice_sess *ice = sd->ice; + pj_ice_msg_data *msg_data = (pj_ice_msg_data *)token; + pj_status_t status; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + /* Stray retransmit timer that could happen while + * we're being destroyed */ + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + status = (*ice->cb.on_tx_pkt)(ice, sd->comp_id, msg_data->transport_id, pkt, pkt_size, dst_addr, addr_len); + + pj_grp_lock_release(ice->grp_lock); + return status; +} + +/* This callback is called when outgoing STUN request completed */ +static void on_stun_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, + pj_stun_tx_data *tdata, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_ice_msg_data *msg_data = (pj_ice_msg_data *)token; + pj_ice_sess *ice; + pj_ice_sess_check *check, *new_check; + pj_ice_sess_cand *lcand; + pj_ice_sess_checklist *clist; + pj_stun_xor_mapped_addr_attr *xaddr; + const pj_sockaddr_t *source_addr = src_addr; + unsigned i, ckid; + + PJ_UNUSED_ARG(stun_sess); + PJ_UNUSED_ARG(src_addr_len); + + pj_assert(msg_data->has_req_data); + + ice = msg_data->data.req.ice; + clist = msg_data->data.req.clist; + ckid = msg_data->data.req.ckid; + check = &clist->checks[ckid]; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + /* Not sure if this is possible but just in case */ + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* Verify check (check ID may change as trickle ICE re-sort the list */ + if (tdata != check->tdata) { + /* Okay, it was re-sorted, lookup using lcand & rcand */ + for (i = 0; i < clist->count; ++i) { + if (clist->checks[i].lcand == msg_data->data.req.lcand && + clist->checks[i].rcand == msg_data->data.req.rcand) { + check = &clist->checks[i]; + ckid = i; + break; + } + } + if (i == clist->count) { + /* The check may have been pruned (due to low prio) */ + check->tdata = NULL; + pj_grp_lock_release(ice->grp_lock); + return; + } + } + + /* Mark STUN transaction as complete */ + // Find 'corner case ...'. + // pj_assert(tdata == check->tdata); + check->tdata = NULL; + + /* Init lcand to NULL. lcand will be found from the mapped address + * found in the response. + */ + lcand = NULL; + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + if (status == PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ROLE_CONFLICT)) { + + /* Role conclict response. + * + * 7.1.2.1. Failure Cases: + * + * If the request had contained the ICE-CONTROLLED attribute, + * the agent MUST switch to the controlling role if it has not + * already done so. If the request had contained the + * ICE-CONTROLLING attribute, the agent MUST switch to the + * controlled role if it has not already done so. Once it has + * switched, the agent MUST immediately retry the request with + * the ICE-CONTROLLING or ICE-CONTROLLED attribute reflecting + * its new role. + */ + pj_ice_sess_role new_role = PJ_ICE_SESS_ROLE_UNKNOWN; + pj_stun_msg *req = tdata->msg; + + if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_ICE_CONTROLLING, 0)) { + new_role = PJ_ICE_SESS_ROLE_CONTROLLED; + } else if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_ICE_CONTROLLED, 0)) { + new_role = PJ_ICE_SESS_ROLE_CONTROLLING; + } else { + pj_assert(!"We should have put CONTROLLING/CONTROLLED attr!"); + new_role = PJ_ICE_SESS_ROLE_CONTROLLED; + } + + if (new_role != ice->role) { + LOG4((ice->obj_name, "Changing role because of role conflict response")); + pj_ice_sess_change_role(ice, new_role); + } + + /* Resend request */ + LOG4((ice->obj_name, "Resending check because of role conflict")); + pj_log_push_indent(); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + perform_check(ice, clist, ckid, check->nominated || ice->is_nominating); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + pj_strerror(status, errmsg, sizeof(errmsg)); + LOG4((ice->obj_name, "Check %s%s: connectivity check FAILED: %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"), errmsg)); + pj_log_push_indent(); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* 7.1.2.1. Failure Cases + * + * The agent MUST check that the source IP address and port of the + * response equals the destination IP address and port that the Binding + * Request was sent to, and that the destination IP address and port of + * the response match the source IP address and port that the Binding + * Request was sent from. + */ + if (check->rcand->addr.addr.sa_family == pj_AF_INET() && + ((pj_sockaddr *)src_addr)->addr.sa_family == pj_AF_INET6()) { + /* If the address family is different, we need to check + * whether the two addresses are equivalent (i.e. the IPv6 + * is synthesized from IPv4). + */ + pj_sockaddr synth_addr; + + status = pj_sockaddr_synthesize(pj_AF_INET6(), &synth_addr, &check->rcand->addr); + if (status == PJ_SUCCESS && pj_sockaddr_cmp(&synth_addr, src_addr) == 0) { + source_addr = &check->rcand->addr; + } + } + + if (pj_sockaddr_cmp(&check->rcand->addr, source_addr) != 0) { + status = PJNATH_EICEINSRCADDR; + LOG4((ice->obj_name, "Check %s%s: connectivity check FAILED: source address mismatch", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"))); + pj_log_push_indent(); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* 7.1.2.2. Success Cases + * + * A check is considered to be a success if all of the following are + * true: + * + * o the STUN transaction generated a success response + * + * o the source IP address and port of the response equals the + * destination IP address and port that the Binding Request was sent + * to + * + * o the destination IP address and port of the response match the + * source IP address and port that the Binding Request was sent from + */ + + LOG4((ice->obj_name, "Check %s%s: connectivity check SUCCESS", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"))); + + /* Get the STUN XOR-MAPPED-ADDRESS attribute. */ + xaddr = (pj_stun_xor_mapped_addr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (!xaddr) { + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, PJNATH_ESTUNNOMAPPEDADDR); + on_check_complete(ice, check); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* Find local candidate that matches the XOR-MAPPED-ADDRESS */ + pj_assert(lcand == NULL); + for (i = 0; i < ice->lcand_cnt; ++i) { + /* Ticket #1891: apply additional check as there may be a shared + * mapped address for different base/local addresses. + */ + if (pj_sockaddr_cmp(&xaddr->sockaddr, &ice->lcand[i].addr) == 0 && + pj_sockaddr_cmp(&check->lcand->base_addr, &ice->lcand[i].base_addr) == 0) { + /* Match */ + lcand = &ice->lcand[i]; + +#if 0 + // The following code tries to verify if the STUN request belongs + // to the correct ICE check (so if it doesn't, it will set current + // ICE check state to FAILED (why?) and try to find the correct + // check). However, ICE check verification has been added in + // the beginning of this function, so the following block should + // not be needed anymore. + + /* Verify lcand==check->lcand, this may happen when a STUN socket + * corresponds to multiple host candidates. + */ + if (lcand != check->lcand) { + unsigned j; + + pj_log_push_indent(); + LOG4((ice->obj_name, + "Check %s%s: local candidate mismatch", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), + &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"))); + + + /* Local candidate does not belong to this check! Set current + * check state to Failed. + */ + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, + PJNATH_ESTUNNOMAPPEDADDR); + + /* Find the matching check */ + for (j = 0; j < clist->count; ++j) { + if (clist->checks[j].lcand == lcand && + clist->checks[j].rcand == check->rcand) + { + check = &clist->checks[j]; + break; + } + } + if (j == clist->count) { + on_check_complete(ice, check); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + pj_log_pop_indent(); + } +#endif + break; + } + } + + /* 7.1.2.2.1. Discovering Peer Reflexive Candidates + * If the transport address returned in XOR-MAPPED-ADDRESS does not match + * any of the local candidates that the agent knows about, the mapped + * address represents a new candidate - a peer reflexive candidate. + */ + if (lcand == NULL) { + unsigned cand_id = ice->lcand_cnt; + pj_str_t foundation; + + pj_ice_calc_foundation(ice->pool, &foundation, PJ_ICE_CAND_TYPE_PRFLX, &check->lcand->base_addr); + + /* Still in 7.1.2.2.1. Discovering Peer Reflexive Candidates + * Its priority is set equal to the value of the PRIORITY attribute + * in the Binding Request. + * + * I think the priority calculated by add_cand() should be the same + * as the one calculated in perform_check(), so there's no need to + * get the priority from the PRIORITY attribute. + */ + + /* Add new peer reflexive candidate */ + status = pj_ice_sess_add_cand(ice, check->lcand->comp_id, msg_data->transport_id, PJ_ICE_CAND_TYPE_PRFLX, +#if PJNATH_ICE_PRIO_STD + 65535 - (pj_uint16_t)ice->lcand_cnt, +#else + ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) - ice->lcand_cnt, +#endif + &foundation, &xaddr->sockaddr, &check->lcand->base_addr, &check->lcand->base_addr, + pj_sockaddr_get_len(&xaddr->sockaddr), &cand_id); + // Note: for IPv6, pj_ice_sess_add_cand can return SUCCESS + // without adding any candidates if the candidate is + // deprecated (because the ICE MUST NOT fail) + // In this case, cand_id == ice->lcand_cnt will be true. + if (status != PJ_SUCCESS || cand_id == ice->lcand_cnt) { + if (cand_id == ice->lcand_cnt) { + LOG4((ice->obj_name, "Cannot add any candidate, all IPv6 seems deprecated")); + } + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* Update local candidate */ + lcand = &ice->lcand[cand_id]; + } + + /* 7.1.2.2.3. Constructing a Valid Pair + * Next, the agent constructs a candidate pair whose local candidate + * equals the mapped address of the response, and whose remote candidate + * equals the destination address to which the request was sent. + */ + + /* Add pair to valid list, if it's not there, otherwise just update + * nominated flag + */ + for (i = 0; i < ice->valid_list.count; ++i) { + if (ice->valid_list.checks[i].lcand == lcand && ice->valid_list.checks[i].rcand == check->rcand) + break; + } + + if (i == ice->valid_list.count) { + pj_assert(ice->valid_list.count < PJ_ICE_MAX_CHECKS); + new_check = &ice->valid_list.checks[ice->valid_list.count++]; + new_check->lcand = lcand; + new_check->rcand = check->rcand; + new_check->prio = CALC_CHECK_PRIO(ice, lcand, check->rcand); + new_check->state = PJ_ICE_SESS_CHECK_STATE_SUCCEEDED; + new_check->nominated = check->nominated; + new_check->err_code = PJ_SUCCESS; + } else { + new_check = &ice->valid_list.checks[i]; + ice->valid_list.checks[i].nominated = check->nominated; + } + + /* Update valid check and nominated check for the component */ + update_comp_check(ice, new_check->lcand->comp_id, new_check); + + /* Sort valid_list (must do so after update_comp_check(), otherwise + * new_check will point to something else (#953) + */ + sort_checklist(ice, &ice->valid_list); + + /* 7.1.2.2.2. Updating Pair States + * + * The agent sets the state of the pair that generated the check to + * Succeeded. The success of this check might also cause the state of + * other checks to change as well. + */ + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, PJ_SUCCESS); + + /* Perform 7.1.2.2.2. Updating Pair States. + * This may terminate ICE processing. + */ + if (on_check_complete(ice, check)) { + /* ICE complete! */ + pj_grp_lock_release(ice->grp_lock); + return; + } + + pj_grp_lock_release(ice->grp_lock); +} + +/* This callback is called by the STUN session associated with a candidate + * when it receives incoming request. + */ +static pj_status_t on_stun_rx_request(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_rx_data *rdata, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + stun_data *sd; + const pj_stun_msg *msg = rdata->msg; + pj_ice_msg_data *msg_data; + pj_ice_sess *ice; + pj_stun_priority_attr *prio_attr; + pj_stun_use_candidate_attr *uc_attr; + pj_stun_uint64_attr *role_attr; + pj_stun_tx_data *tdata; + pj_ice_rx_check *rcheck, tmp_rcheck; + const pj_sockaddr_t *source_addr = src_addr; + unsigned source_addr_len = src_addr_len; + pj_status_t status; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + + /* Reject any requests except Binding request */ + if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) { + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST, NULL, token, PJ_TRUE, src_addr, src_addr_len); + return PJ_SUCCESS; + } + + sd = (stun_data *)pj_stun_session_get_user_data(sess); + ice = sd->ice; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + /* + * Note: + * Be aware that when STUN request is received, we might not get + * SDP answer yet, so we might not have remote candidates and + * checklist yet. This case will be handled after we send + * a response. + */ + + /* Get PRIORITY attribute */ + prio_attr = (pj_stun_priority_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0); + if (prio_attr == NULL) { + LOG5((ice->obj_name, "Received Binding request with no PRIORITY")); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Get USE-CANDIDATE attribute */ + uc_attr = (pj_stun_use_candidate_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0); + + /* Get ICE-CONTROLLING or ICE-CONTROLLED */ + role_attr = (pj_stun_uint64_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLING, 0); + if (role_attr == NULL) { + role_attr = (pj_stun_uint64_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLED, 0); + } + + /* Handle the case when request comes before SDP answer is received. + * We need to put credential in the response, and since we haven't + * got the SDP answer, copy the username from the request. + */ + if (ice->tx_ufrag.slen == 0) { + pj_stun_string_attr *uname_attr; + + uname_attr = (pj_stun_string_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0); + pj_assert(uname_attr != NULL); + pj_strdup(ice->pool, &ice->rx_uname, &uname_attr->value); + } + + /* 7.2.1.1. Detecting and Repairing Role Conflicts + */ + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING && role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLING) { + if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { + /* Switch role to controlled */ + LOG4((ice->obj_name, "Changing role because of ICE-CONTROLLING attribute")); + pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLED); + } else { + /* Generate 487 response */ + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, NULL, token, PJ_TRUE, src_addr, + src_addr_len); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + } else if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED && role_attr && + role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLED) { + if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { + /* Generate 487 response */ + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, NULL, token, PJ_TRUE, src_addr, + src_addr_len); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } else { + /* Switch role to controlled */ + LOG4((ice->obj_name, "Changing role because of ICE-CONTROLLED attribute")); + pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLING); + } + } + + /* + * First send response to this request + */ + status = pj_stun_session_create_res(sess, rdata, 0, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice->grp_lock); + return status; + } + + if (((pj_sockaddr *)src_addr)->addr.sa_family == pj_AF_INET6()) { + unsigned i; + unsigned transport_id = ((pj_ice_msg_data *)token)->transport_id; + pj_ice_sess_cand *lcand = NULL; + + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand->comp_id == sd->comp_id && c->lcand->transport_id == transport_id) { + lcand = c->lcand; + break; + } + } + + if (lcand != NULL && lcand->addr.addr.sa_family == pj_AF_INET()) { + /* We are behind NAT64, so src_addr is a synthesized IPv6 + * address. Instead of putting this synth IPv6 address as + * the XOR-MAPPED-ADDRESS, we need to find its original + * IPv4 address. + */ + for (i = 0; i < ice->rcand_cnt; ++i) { + pj_sockaddr synth_addr; + + if (ice->rcand[i].addr.addr.sa_family != pj_AF_INET()) + continue; + + status = pj_sockaddr_synthesize(pj_AF_INET6(), &synth_addr, &ice->rcand[i].addr); + if (status == PJ_SUCCESS && pj_sockaddr_cmp(src_addr, &synth_addr) == 0) { + /* We find the original IPv4 address. */ + source_addr = &ice->rcand[i].addr; + source_addr_len = pj_sockaddr_get_len(source_addr); + break; + } + } + } + } + + /* Add XOR-MAPPED-ADDRESS attribute */ + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, source_addr, + source_addr_len); + + /* Create a msg_data to be associated with this response */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = ((pj_ice_msg_data *)token)->transport_id; + msg_data->has_req_data = PJ_FALSE; + + /* Send the response */ + status = pj_stun_session_send_msg(sess, msg_data, PJ_TRUE, PJ_TRUE, src_addr, src_addr_len, tdata); + + /* + * Handling early check. + * + * It's possible that we receive this request before we receive SDP + * answer. In this case, we can't perform trigger check since we + * don't have checklist yet, so just save this check in a pending + * triggered check array to be acted upon later. + */ + if (ice->tx_ufrag.slen == 0) { + rcheck = PJ_POOL_ZALLOC_T(ice->pool, pj_ice_rx_check); + } else { + rcheck = &tmp_rcheck; + } + + /* Init rcheck */ + rcheck->comp_id = sd->comp_id; + rcheck->transport_id = ((pj_ice_msg_data *)token)->transport_id; + rcheck->src_addr_len = source_addr_len; + pj_sockaddr_cp(&rcheck->src_addr, source_addr); + rcheck->use_candidate = (uc_attr != NULL); + rcheck->priority = prio_attr->value; + rcheck->role_attr = role_attr; + + if (ice->tx_ufrag.slen == 0) { + /* We don't have answer yet, so keep this request for later */ + LOG4((ice->obj_name, "Received an early check for comp %d", rcheck->comp_id)); + pj_list_push_back(&ice->early_check, rcheck); + } else { + /* Handle this check */ + handle_incoming_check(ice, rcheck); + } + + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; +} + +/* Handle incoming Binding request and perform triggered check. + * This function may be called by on_stun_rx_request(), or when + * SDP answer is received and we have received early checks. + */ +static void handle_incoming_check(pj_ice_sess *ice, const pj_ice_rx_check *rcheck) +{ + pj_ice_sess_comp *comp; + pj_ice_sess_cand *lcand = NULL; + pj_ice_sess_cand *rcand; + unsigned i; + + comp = find_comp(ice, rcheck->comp_id); + + /* Find remote candidate based on the source transport address of + * the request. + */ + for (i = 0; i < ice->rcand_cnt; ++i) { + if (pj_sockaddr_cmp(&rcheck->src_addr, &ice->rcand[i].addr) == 0) + break; + } + + /* 7.2.1.3. Learning Peer Reflexive Candidates + * If the source transport address of the request does not match any + * existing remote candidates, it represents a new peer reflexive remote + * candidate. + */ + if (i == ice->rcand_cnt) { + char raddr[PJ_INET6_ADDRSTRLEN]; + void *p; + + if (ice->rcand_cnt >= PJ_ICE_MAX_CAND) { + LOG4((ice->obj_name, + "Unable to add new peer reflexive candidate: too many " + "candidates already (%d)", + PJ_ICE_MAX_CAND)); + return; + } + + rcand = &ice->rcand[ice->rcand_cnt++]; + rcand->comp_id = (pj_uint8_t)rcheck->comp_id; + rcand->type = PJ_ICE_CAND_TYPE_PRFLX; + rcand->prio = rcheck->priority; + pj_sockaddr_cp(&rcand->addr, &rcheck->src_addr); + + /* Foundation is random, unique from other foundation */ + rcand->foundation.ptr = p = (char *)pj_pool_alloc(ice->pool, 36); + rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 36, "f%p", p); + + LOG4((ice->obj_name, "Added new remote candidate from the request: %s:%d", + pj_sockaddr_print(&rcand->addr, raddr, sizeof(raddr), 2), pj_sockaddr_get_port(&rcand->addr))); + + } else { + /* Remote candidate found */ + rcand = &ice->rcand[i]; + } + +#if 0 + /* Find again the local candidate by matching the base address + * with the local candidates in the checklist. Checks may have + * been pruned before, so it's possible that if we use the lcand + * as it is, we wouldn't be able to find the check in the checklist + * and we will end up creating a new check unnecessarily. + */ + for (i=0; iclist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (/*c->lcand == lcand ||*/ + pj_sockaddr_cmp(&c->lcand->base_addr, &lcand->base_addr)==0) + { + lcand = c->lcand; + break; + } + } +#else + /* Just get candidate with the highest priority and same transport ID + * for the specified component ID in the checklist. + */ + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand->comp_id == rcheck->comp_id && c->lcand->transport_id == rcheck->transport_id) { + lcand = c->lcand; + break; + } + } + if (lcand == NULL) { + /* Should not happen, but just in case remote is sending a + * Binding request for a component which it doesn't have. + */ + LOG4((ice->obj_name, "Received Binding request but no local candidate is found!")); + return; + } +#endif + + /* + * Create candidate pair for this request. + */ + + /* + * 7.2.1.4. Triggered Checks + * + * Now that we have local and remote candidate, check if we already + * have this pair in our checklist. + */ + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand == lcand && c->rcand == rcand) + break; + } + + /* If the pair is already on the check list: + * - If the state of that pair is Waiting or Frozen, its state is + * changed to In-Progress and a check for that pair is performed + * immediately. This is called a triggered check. + * + * - If the state of that pair is In-Progress, the agent SHOULD + * generate an immediate retransmit of the Binding Request for the + * check in progress. This is to facilitate rapid completion of + * ICE when both agents are behind NAT. + * + * - If the state of that pair is Failed or Succeeded, no triggered + * check is sent. + */ + if (i != ice->clist.count) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + + /* If USE-CANDIDATE is present, set nominated flag + * Note: DO NOT overwrite nominated flag if one is already set. + */ + c->nominated = ((rcheck->use_candidate) || c->nominated); + + if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN || c->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { + /* If we are nominating in regular nomination, don't nominate this + * triggered check immediately, just wait for its scheduled check. + */ + if (ice->is_nominating && !ice->opt.aggressive) { + LOG5((ice->obj_name, + "Triggered check for check %d not " + "performed because nomination is in progress", + i)); + } else { + /* See if we shall nominate this check */ + pj_bool_t nominate = (c->nominated || ice->is_nominating); + + LOG5((ice->obj_name, + "Performing triggered check for " + "check %d", + i)); + pj_log_push_indent(); + perform_check(ice, &ice->clist, i, nominate); + pj_log_pop_indent(); + } + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + /* Should retransmit immediately + */ + LOG5((ice->obj_name, + "Triggered check for check %d not performed " + "because it's in progress. Retransmitting", + i)); + pj_log_push_indent(); + pj_stun_session_retransmit_req(comp->stun_sess, c->tdata, PJ_FALSE); + pj_log_pop_indent(); + + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { + /* Check complete for this component. + * Note this may end ICE process. + */ + pj_bool_t complete; + unsigned j; + + /* If this check is nominated, scan the valid_list for the + * same check and update the nominated flag. A controlled + * agent might have finished the check earlier. + */ + if (rcheck->use_candidate) { + for (j = 0; j < ice->valid_list.count; ++j) { + pj_ice_sess_check *vc = &ice->valid_list.checks[j]; + if (vc->lcand->transport_id == c->lcand->transport_id && vc->rcand == c->rcand) { + /* Set nominated flag */ + vc->nominated = PJ_TRUE; + + /* Update valid check and nominated check for the component */ + update_comp_check(ice, vc->lcand->comp_id, vc); + + LOG5((ice->obj_name, "Valid check %s is nominated", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->valid_list, vc))); + } + } + } + + LOG5((ice->obj_name, + "Triggered check for check %d not performed " + "because it's completed", + i)); + pj_log_push_indent(); + complete = on_check_complete(ice, c); + pj_log_pop_indent(); + if (complete) { + return; + } + } + + } + /* If the pair is not already on the check list: + * - The pair is inserted into the check list based on its priority. + * - Its state is set to In-Progress + * - A triggered check for that pair is performed immediately. + */ + /* Note: only do this if we don't have too many checks in checklist */ + else if (ice->clist.count < PJ_ICE_MAX_CHECKS) { + + pj_ice_sess_check *c = &ice->clist.checks[ice->clist.count]; + unsigned check_id = ice->clist.count; + + c->lcand = lcand; + c->rcand = rcand; + c->prio = CALC_CHECK_PRIO(ice, lcand, rcand); + c->state = PJ_ICE_SESS_CHECK_STATE_WAITING; + c->nominated = rcheck->use_candidate; + c->err_code = PJ_SUCCESS; + ++ice->clist.count; + + LOG4((ice->obj_name, "New triggered check added: %d", check_id)); + + /* If we are nominating in regular nomination, don't nominate this + * newly found pair. + */ + if (ice->is_nominating && !ice->opt.aggressive) { + LOG5((ice->obj_name, + "Triggered check for check %d not " + "performed because nomination is in progress", + check_id)); + + /* Just in case the periodic check has been stopped (due to no more + * pair to check), let's restart it for this pair. + */ + if (!pj_timer_entry_running(&ice->clist.timer)) { + pj_time_val delay = {0, 0}; + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->clist.timer, &delay, PJ_TRUE, + ice->grp_lock); + } + } else { + pj_bool_t nominate; + nominate = (c->nominated || ice->is_nominating); + + pj_log_push_indent(); + perform_check(ice, &ice->clist, check_id, nominate); + pj_log_pop_indent(); + } + + /* Re-sort the list because of the newly added pair. */ + sort_checklist(ice, &ice->clist); + + } else { + LOG4((ice->obj_name, "Error: unable to perform triggered check: " + "TOO MANY CHECKS IN CHECKLIST!")); + } +} + +static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + struct stun_data *sd; + + PJ_UNUSED_ARG(sess); + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(msg); + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sd = (struct stun_data *)pj_stun_session_get_user_data(sess); + + pj_log_push_indent(); + + if (msg->hdr.type == PJ_STUN_BINDING_INDICATION) { + LOG5((sd->ice->obj_name, + "Received Binding Indication keep-alive " + "for component %d", + sd->comp_id)); + } else { + LOG4((sd->ice->obj_name, + "Received unexpected %s indication " + "for component %d", + pj_stun_get_method_name(msg->hdr.type), sd->comp_id)); + } + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice, unsigned comp_id, const void *data, pj_size_t data_len) +{ + pj_status_t status = PJ_SUCCESS; + pj_ice_sess_comp *comp; + pj_ice_sess_cand *cand; + pj_uint8_t transport_id; + pj_sockaddr addr; + + PJ_ASSERT_RETURN(ice && comp_id, PJ_EINVAL); + + /* It is possible that comp_cnt is less than comp_id, when remote + * doesn't support all the components that we have. + */ + if (comp_id > ice->comp_cnt) { + return PJNATH_EICEINCOMPID; + } + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + comp = find_comp(ice, comp_id); + if (comp == NULL) { + status = PJNATH_EICEINCOMPID; + pj_grp_lock_release(ice->grp_lock); + goto on_return; + } + + if (comp->valid_check == NULL) { + status = PJNATH_EICEINPROGRESS; + pj_grp_lock_release(ice->grp_lock); + goto on_return; + } + + cand = comp->valid_check->lcand; + transport_id = cand->transport_id; + pj_sockaddr_cp(&addr, &comp->valid_check->rcand->addr); + + /* Release the mutex now to avoid deadlock (see ticket #1451). */ + pj_grp_lock_release(ice->grp_lock); + + PJ_RACE_ME(5); + + status = (*ice->cb.on_tx_pkt)(ice, comp_id, transport_id, data, data_len, &addr, pj_sockaddr_get_len(&addr)); + +on_return: + return status; +} + +PJ_DEF(pj_status_t) +pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *src_addr, int src_addr_len) +{ + pj_status_t status = PJ_SUCCESS; + pj_ice_sess_comp *comp; + pj_ice_msg_data *msg_data = NULL; + unsigned i; + + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + comp = find_comp(ice, comp_id); + if (comp == NULL) { + pj_grp_lock_release(ice->grp_lock); + return PJNATH_EICEINCOMPID; + } + + /* Find transport */ + for (i = 0; i < PJ_ARRAY_SIZE(ice->tp_data); ++i) { + if (ice->tp_data[i].transport_id == transport_id) { + msg_data = &ice->tp_data[i]; + break; + } + } + if (msg_data == NULL) { + pj_assert(!"Invalid transport ID"); + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVAL; + } + + /* Don't check fingerprint. We only need to distinguish STUN and non-STUN + * packets. We don't need to verify the STUN packet too rigorously, that + * will be done by the user. + */ + status = pj_stun_msg_check((const pj_uint8_t *)pkt, pkt_size, PJ_STUN_IS_DATAGRAM | PJ_STUN_NO_FINGERPRINT_CHECK); + if (status == PJ_SUCCESS) { + status = pj_stun_session_on_rx_pkt(comp->stun_sess, pkt, pkt_size, PJ_STUN_IS_DATAGRAM, msg_data, NULL, + src_addr, src_addr_len); + if (status != PJ_SUCCESS) { + pj_strerror(status, ice->tmp.errmsg, sizeof(ice->tmp.errmsg)); + LOG4((ice->obj_name, "Error processing incoming message: %s", ice->tmp.errmsg)); + } + pj_grp_lock_release(ice->grp_lock); + } else { + /* Not a STUN packet. Call application's callback instead, but release + * the mutex now or otherwise we may get deadlock. + */ + pj_grp_lock_release(ice->grp_lock); + + PJ_RACE_ME(5); + + (*ice->cb.on_rx_data)(ice, comp_id, transport_id, pkt, pkt_size, src_addr, src_addr_len); + status = PJ_SUCCESS; + } + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c new file mode 100755 index 000000000..4d3238837 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c @@ -0,0 +1,2596 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ENABLE_TRACE 0 + +#if defined(ENABLE_TRACE) && (ENABLE_TRACE != 0) +#define TRACE_PKT(expr) PJ_LOG(5, expr) +#else +#define TRACE_PKT(expr) +#endif + +/* Transport IDs */ +enum tp_type { TP_NONE, TP_STUN, TP_TURN }; + +#define CREATE_TP_ID(type, idx) (pj_uint8_t)((type << 6) | idx) +#define GET_TP_TYPE(transport_id) ((transport_id & 0xC0) >> 6) +#define GET_TP_IDX(transport_id) (transport_id & 0x3F) + +/* Candidate's local preference values. This is mostly used to + * specify preference among candidates with the same type. Since + * we don't have the facility to specify that, we'll just set it + * all to the same value. + */ +#if PJNATH_ICE_PRIO_STD +#define SRFLX_PREF 65535 +#define HOST_PREF 65535 +#define RELAY_PREF 65535 +#else +#define SRFLX_PREF ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) +#define HOST_PREF ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) +#define RELAY_PREF ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) +#endif + +/* The candidate type preference when STUN candidate is used */ +static pj_uint8_t srflx_pref_table[PJ_ICE_CAND_TYPE_MAX] = { +#if PJNATH_ICE_PRIO_STD + 100, /**< PJ_ICE_HOST_PREF */ + 110, /**< PJ_ICE_SRFLX_PREF */ + 126, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#else + /* Keep it to 2 bits */ + 1, /**< PJ_ICE_HOST_PREF */ + 2, /**< PJ_ICE_SRFLX_PREF */ + 3, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#endif +}; + +/* ICE callbacks */ +static void on_valid_pair(pj_ice_sess *ice); +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status); +static pj_status_t ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, + pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); +static void ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + +/* STUN socket callbacks */ +/* Notification when incoming packet has been received. */ +static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, + unsigned addr_len); +/* Notifification when asynchronous send operation has completed. */ +static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +/* Notification when the status of the STUN transport has changed. */ +static pj_bool_t stun_on_status(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status); + +/* TURN callbacks */ +static void turn_on_rx_data(pj_turn_sock *turn_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static pj_bool_t turn_on_data_sent(pj_turn_sock *turn_sock, pj_ssize_t sent); +static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, pj_turn_state_t new_state); + +/* Forward decls */ +static pj_bool_t on_data_sent(pj_ice_strans *ice_st, pj_ssize_t sent); +static void check_pending_send(pj_ice_strans *ice_st); +static void ice_st_on_destroy(void *obj); +static void destroy_ice_st(pj_ice_strans *ice_st); +#define ice_st_perror(ice_st, msg, rc) pjnath_perror(ice_st->obj_name, msg, rc) +static void sess_init_update(pj_ice_strans *ice_st); + +/** + * This structure describes an ICE stream transport component. A component + * in ICE stream transport typically corresponds to a single socket created + * for this component, and bound to a specific transport address. This + * component may have multiple alias addresses, for example one alias + * address for each interfaces in multi-homed host, another for server + * reflexive alias, and another for relayed alias. For each transport + * address alias, an ICE stream transport candidate (#pj_ice_sess_cand) will + * be created, and these candidates will eventually registered to the ICE + * session. + */ +typedef struct pj_ice_strans_comp { + pj_ice_strans *ice_st; /**< ICE stream transport. */ + unsigned comp_id; /**< Component ID. */ + + struct { + pj_stun_sock *sock; /**< STUN transport. */ + } stun[PJ_ICE_MAX_STUN]; + + struct { + pj_turn_sock *sock; /**< TURN relay transport. */ + pj_bool_t log_off; /**< TURN loggin off? */ + unsigned err_cnt; /**< TURN disconnected count. */ + } turn[PJ_ICE_MAX_TURN]; + + pj_bool_t creating; /**< Is creating the candidates?*/ + unsigned cand_cnt; /**< # of candidates/aliaes. */ + pj_ice_sess_cand cand_list[PJ_ICE_ST_MAX_CAND]; /**< Cand array */ + + pj_bool_t ipv4_mapped; /**< Is IPv6 addr mapped to IPv4?*/ + pj_sockaddr dst_addr; /**< Destination address */ + pj_sockaddr synth_addr; /**< Synthesized dest address */ + unsigned synth_addr_len; /**< Synthesized dest addr len */ + + unsigned default_cand; /**< Default candidate. */ + +} pj_ice_strans_comp; + +/* Pending send buffer */ +typedef struct pending_send { + void *buffer; + unsigned comp_id; + pj_size_t data_len; + pj_sockaddr dst_addr; + int dst_addr_len; +} pending_send; + +/** + * This structure represents the ICE stream transport. + */ +struct pj_ice_strans { + char *obj_name; /**< Log ID. */ + pj_pool_factory *pf; /**< Pool factory. */ + pj_pool_t *pool; /**< Pool used by this object. */ + void *user_data; /**< Application data. */ + pj_ice_strans_cfg cfg; /**< Configuration. */ + pj_ice_strans_cb cb; /**< Application callback. */ + pj_grp_lock_t *grp_lock; /**< Group lock. */ + + pj_ice_strans_state state; /**< Session state. */ + pj_ice_sess *ice; /**< ICE session. */ + pj_ice_sess *ice_prev; /**< Previous ICE session. */ + pj_grp_lock_handler ice_prev_hndlr; /**< Handler of prev ICE */ + pj_time_val start_time; /**< Time when ICE was started */ + + unsigned comp_cnt; /**< Number of components. */ + pj_ice_strans_comp **comp; /**< Components array. */ + + pj_pool_t *buf_pool; /**< Pool for buffers. */ + unsigned num_buf; /**< Number of buffers. */ + unsigned buf_idx; /**< Index of buffer. */ + unsigned empty_idx; /**< Index of empty buffer. */ + unsigned buf_size; /**< Buffer size. */ + pending_send *send_buf; /**< Send buffers. */ + pj_bool_t is_pending; /**< Any pending send? */ + + pj_timer_entry ka_timer; /**< STUN keep-alive timer. */ + + pj_bool_t destroy_req; /**< Destroy has been called? */ + pj_bool_t cb_called; /**< Init error callback called?*/ + pj_bool_t call_send_cb; /**< Need to call send cb? */ + + pj_bool_t rem_cand_end; /**< Trickle ICE: remote has + signalled end of candidate? */ + pj_bool_t loc_cand_end; /**< Trickle ICE: local has + signalled end of candidate? */ +}; + +/** + * This structure describe user data for STUN/TURN sockets of the + * ICE stream transport. + */ +typedef struct sock_user_data { + pj_ice_strans_comp *comp; + pj_uint8_t transport_id; + +} sock_user_data; + +/* Validate configuration */ +static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) +{ + pj_status_t status; + + status = pj_stun_config_check_valid(&cfg->stun_cfg); + if (!status) + return status; + + return PJ_SUCCESS; +} + +/* + * Initialize ICE transport configuration with default values. + */ +PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->af = pj_AF_INET(); + pj_stun_config_init(&cfg->stun_cfg, NULL, 0, NULL, NULL); + pj_ice_strans_stun_cfg_default(&cfg->stun); + pj_ice_strans_turn_cfg_default(&cfg->turn); + pj_ice_sess_options_default(&cfg->opt); + + cfg->num_send_buf = 4; +} + +/* + * Initialize ICE STUN transport configuration with default values. + */ +PJ_DEF(void) pj_ice_strans_stun_cfg_default(pj_ice_strans_stun_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->af = pj_AF_INET(); + cfg->port = PJ_STUN_PORT; + cfg->max_host_cands = 64; + cfg->ignore_stun_error = PJ_FALSE; + pj_stun_sock_cfg_default(&cfg->cfg); +} + +/* + * Initialize ICE TURN transport configuration with default values. + */ +PJ_DEF(void) pj_ice_strans_turn_cfg_default(pj_ice_strans_turn_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->af = pj_AF_INET(); + cfg->conn_type = PJ_TURN_TP_UDP; + pj_turn_alloc_param_default(&cfg->alloc_param); + pj_turn_sock_cfg_default(&cfg->cfg); +} + +/* + * Copy configuration. + */ +PJ_DEF(void) pj_ice_strans_cfg_copy(pj_pool_t *pool, pj_ice_strans_cfg *dst, const pj_ice_strans_cfg *src) +{ + unsigned i; + + pj_memcpy(dst, src, sizeof(*src)); + + if (src->stun.server.slen) + pj_strdup(pool, &dst->stun.server, &src->stun.server); + + for (i = 0; i < src->stun_tp_cnt; ++i) { + if (src->stun_tp[i].server.slen) + pj_strdup(pool, &dst->stun_tp[i].server, &src->stun_tp[i].server); + } + + if (src->turn.server.slen) + pj_strdup(pool, &dst->turn.server, &src->turn.server); + pj_stun_auth_cred_dup(pool, &dst->turn.auth_cred, &src->turn.auth_cred); + + for (i = 0; i < src->turn_tp_cnt; ++i) { + if (src->turn_tp[i].server.slen) + pj_strdup(pool, &dst->turn_tp[i].server, &src->turn_tp[i].server); + pj_stun_auth_cred_dup(pool, &dst->turn_tp[i].auth_cred, &src->turn_tp[i].auth_cred); + } +} + +/* + * Add or update TURN candidate. + */ +static pj_status_t add_update_turn(pj_ice_strans *ice_st, pj_ice_strans_comp *comp, unsigned idx, unsigned max_cand_cnt) +{ + pj_ice_sess_cand *cand = NULL; + pj_ice_strans_turn_cfg *turn_cfg = &ice_st->cfg.turn_tp[idx]; + pj_turn_sock_cfg *sock_cfg = &turn_cfg->cfg; + unsigned comp_idx = comp->comp_id - 1; + pj_turn_sock_cb turn_sock_cb; + sock_user_data *data; + unsigned i; + pj_bool_t new_cand = PJ_FALSE; + pj_uint8_t tp_id; + pj_status_t status; + + /* Check if TURN transport is configured */ + if (turn_cfg->server.slen == 0) + return PJ_SUCCESS; + + /* Find relayed candidate in the component */ + tp_id = CREATE_TP_ID(TP_TURN, idx); + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].transport_id == tp_id) { + cand = &comp->cand_list[i]; + break; + } + } + + /* If candidate is found, invalidate it first */ + if (cand) { + cand->status = PJ_EPENDING; + + /* Also if this component's default candidate is set to relay, + * move it temporarily to something else. + */ + if ((int)comp->default_cand == cand - comp->cand_list) { + /* Init to something */ + comp->default_cand = 0; + /* Use srflx candidate as the default, if any */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX) { + comp->default_cand = i; + if (ice_st->cfg.af == pj_AF_UNSPEC() || + comp->cand_list[i].base_addr.addr.sa_family == ice_st->cfg.af) { + break; + } + } + } + } + } + + /* Init TURN socket */ + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_rx_data = &turn_on_rx_data; + turn_sock_cb.on_data_sent = &turn_on_data_sent; + turn_sock_cb.on_state = &turn_on_state; + + /* Override with component specific QoS settings, if any */ + if (ice_st->cfg.comp[comp_idx].qos_type) + sock_cfg->qos_type = ice_st->cfg.comp[comp_idx].qos_type; + if (ice_st->cfg.comp[comp_idx].qos_params.flags) + pj_memcpy(&sock_cfg->qos_params, &ice_st->cfg.comp[comp_idx].qos_params, sizeof(sock_cfg->qos_params)); + + /* Override with component specific socket buffer size settings, if any */ + if (ice_st->cfg.comp[comp_idx].so_rcvbuf_size > 0) + sock_cfg->so_rcvbuf_size = ice_st->cfg.comp[comp_idx].so_rcvbuf_size; + if (ice_st->cfg.comp[comp_idx].so_sndbuf_size > 0) + sock_cfg->so_sndbuf_size = ice_st->cfg.comp[comp_idx].so_sndbuf_size; + + /* Add relayed candidate with pending status if there's no existing one */ + if (cand == NULL) { + PJ_ASSERT_RETURN(max_cand_cnt > 0, PJ_ETOOSMALL); + + cand = &comp->cand_list[comp->cand_cnt]; + cand->type = PJ_ICE_CAND_TYPE_RELAYED; + cand->status = PJ_EPENDING; + cand->local_pref = (pj_uint16_t)(RELAY_PREF - idx); + cand->transport_id = tp_id; + cand->comp_id = (pj_uint8_t)comp->comp_id; + new_cand = PJ_TRUE; + } + + /* Allocate and initialize TURN socket data */ + data = PJ_POOL_ZALLOC_T(ice_st->pool, sock_user_data); + data->comp = comp; + data->transport_id = cand->transport_id; + + /* Create the TURN transport */ + status = pj_turn_sock_create(&ice_st->cfg.stun_cfg, turn_cfg->af, turn_cfg->conn_type, &turn_sock_cb, sock_cfg, + data, &comp->turn[idx].sock); + if (status != PJ_SUCCESS) { + return status; + } + + if (new_cand) { + /* Commit the relayed candidate before pj_turn_sock_alloc(), as + * otherwise there can be race condition, please check + * https://github.com/pjsip/pjproject/pull/2525 for more info. + */ + comp->cand_cnt++; + } + + /* Add pending job */ + /// sess_add_ref(ice_st); + + /* Start allocation */ + status = pj_turn_sock_alloc(comp->turn[idx].sock, &turn_cfg->server, turn_cfg->port, ice_st->cfg.resolver, + &turn_cfg->auth_cred, &turn_cfg->alloc_param); + if (status != PJ_SUCCESS) { + /// sess_dec_ref(ice_st); + cand->status = status; + return status; + } + + PJ_LOG(4, (ice_st->obj_name, + "Comp %d/%d: TURN relay candidate (tpid=%d) " + "waiting for allocation", + comp->comp_id, comp->cand_cnt - 1, cand->transport_id)); + + return PJ_SUCCESS; +} + +static pj_bool_t ice_cand_equals(pj_ice_sess_cand *lcand, pj_ice_sess_cand *rcand) +{ + if (lcand == NULL && rcand == NULL) { + return PJ_TRUE; + } + if (lcand == NULL || rcand == NULL) { + return PJ_FALSE; + } + + if (lcand->type != rcand->type || lcand->status != rcand->status || lcand->comp_id != rcand->comp_id || + lcand->transport_id != rcand->transport_id + // local pref is no longer a constant, so it may be different + //|| lcand->local_pref != rcand->local_pref + || lcand->prio != rcand->prio || pj_sockaddr_cmp(&lcand->addr, &rcand->addr) != 0 || + pj_sockaddr_cmp(&lcand->base_addr, &rcand->base_addr) != 0) { + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static pj_status_t add_stun_and_host(pj_ice_strans *ice_st, pj_ice_strans_comp *comp, unsigned idx, + unsigned max_cand_cnt) +{ + pj_ice_sess_cand *cand; + pj_ice_strans_stun_cfg *stun_cfg = &ice_st->cfg.stun_tp[idx]; + pj_stun_sock_cfg *sock_cfg = &stun_cfg->cfg; + unsigned comp_idx = comp->comp_id - 1; + pj_stun_sock_cb stun_sock_cb; + sock_user_data *data; + pj_status_t status; + + PJ_ASSERT_RETURN(max_cand_cnt > 0, PJ_ETOOSMALL); + + /* Check if STUN transport or host candidate is configured */ + if (stun_cfg->server.slen == 0 && stun_cfg->max_host_cands == 0) + return PJ_SUCCESS; + + /* Initialize STUN socket callback */ + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_rx_data = &stun_on_rx_data; + stun_sock_cb.on_status = &stun_on_status; + stun_sock_cb.on_data_sent = &stun_on_data_sent; + + /* Override component specific QoS settings, if any */ + if (ice_st->cfg.comp[comp_idx].qos_type) { + sock_cfg->qos_type = ice_st->cfg.comp[comp_idx].qos_type; + } + if (ice_st->cfg.comp[comp_idx].qos_params.flags) { + pj_memcpy(&sock_cfg->qos_params, &ice_st->cfg.comp[comp_idx].qos_params, sizeof(sock_cfg->qos_params)); + } + + /* Override component specific socket buffer size settings, if any */ + if (ice_st->cfg.comp[comp_idx].so_rcvbuf_size > 0) { + sock_cfg->so_rcvbuf_size = ice_st->cfg.comp[comp_idx].so_rcvbuf_size; + } + if (ice_st->cfg.comp[comp_idx].so_sndbuf_size > 0) { + sock_cfg->so_sndbuf_size = ice_st->cfg.comp[comp_idx].so_sndbuf_size; + } + + /* Prepare srflx candidate with pending status. */ + cand = &comp->cand_list[comp->cand_cnt]; + cand->type = PJ_ICE_CAND_TYPE_SRFLX; + cand->status = PJ_EPENDING; + cand->local_pref = (pj_uint16_t)(SRFLX_PREF - idx); + cand->transport_id = CREATE_TP_ID(TP_STUN, idx); + cand->comp_id = (pj_uint8_t)comp->comp_id; + + /* Allocate and initialize STUN socket data */ + data = PJ_POOL_ZALLOC_T(ice_st->pool, sock_user_data); + data->comp = comp; + data->transport_id = cand->transport_id; + + /* Create the STUN transport */ + status = pj_stun_sock_create(&ice_st->cfg.stun_cfg, NULL, stun_cfg->af, &stun_sock_cb, sock_cfg, data, + &comp->stun[idx].sock); + if (status != PJ_SUCCESS) + return status; + + /* Start STUN Binding resolution and add srflx candidate only if server + * is set. When any error occur during STUN Binding resolution, let's + * just skip it and generate host candidates. + */ + while (stun_cfg->server.slen) { + pj_stun_sock_info stun_sock_info; + + /* Add pending job */ + /// sess_add_ref(ice_st); + + PJ_LOG(4, (ice_st->obj_name, + "Comp %d: srflx candidate (tpid=%d) starts " + "Binding discovery", + comp->comp_id, cand->transport_id)); + + pj_log_push_indent(); + + /* Start Binding resolution */ + status = pj_stun_sock_start(comp->stun[idx].sock, &stun_cfg->server, stun_cfg->port, ice_st->cfg.resolver); + if (status != PJ_SUCCESS) { + /// sess_dec_ref(ice_st); + PJ_PERROR(5, (ice_st->obj_name, status, + "Comp %d: srflx candidate (tpid=%d) failed in " + "pj_stun_sock_start()", + comp->comp_id, cand->transport_id)); + pj_log_pop_indent(); + break; + } + + /* Enumerate addresses */ + status = pj_stun_sock_get_info(comp->stun[idx].sock, &stun_sock_info); + if (status != PJ_SUCCESS) { + /// sess_dec_ref(ice_st); + PJ_PERROR(5, (ice_st->obj_name, status, + "Comp %d: srflx candidate (tpid=%d) failed in " + "pj_stun_sock_get_info()", + comp->comp_id, cand->transport_id)); + pj_log_pop_indent(); + break; + } + + /* Update and commit the srflx candidate. */ + pj_sockaddr_cp(&cand->base_addr, &stun_sock_info.aliases[0]); + pj_sockaddr_cp(&cand->rel_addr, &cand->base_addr); + pj_ice_calc_foundation(ice_st->pool, &cand->foundation, cand->type, &cand->base_addr); + comp->cand_cnt++; + max_cand_cnt--; + + /* Set default candidate to srflx */ + if (comp->cand_list[comp->default_cand].type != PJ_ICE_CAND_TYPE_SRFLX || + (ice_st->cfg.af != pj_AF_UNSPEC() && + comp->cand_list[comp->default_cand].base_addr.addr.sa_family != ice_st->cfg.af)) { + comp->default_cand = (unsigned)(cand - comp->cand_list); + } + + pj_log_pop_indent(); + + /* Not really a loop, just trying to avoid complex 'if' blocks */ + break; + } + + /* Add local addresses to host candidates, unless max_host_cands + * is set to zero. + */ + if (stun_cfg->max_host_cands) { + pj_stun_sock_info stun_sock_info; + unsigned i, cand_cnt = 0; + + /* Enumerate addresses */ + status = pj_stun_sock_get_info(comp->stun[idx].sock, &stun_sock_info); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed in querying STUN socket info")); + return status; + } + + for (i = 0; i < stun_sock_info.alias_cnt && cand_cnt < stun_cfg->max_host_cands; ++i) { + unsigned j; + pj_bool_t cand_duplicate = PJ_FALSE; + char addrinfo[PJ_INET6_ADDRSTRLEN + 10]; + const pj_sockaddr *addr = &stun_sock_info.aliases[i]; + + if (max_cand_cnt == 0) { + PJ_LOG(4, (ice_st->obj_name, "Too many host candidates")); + break; + } + + /* Ignore loopback addresses if cfg->stun.loop_addr is unset */ + if (stun_cfg->loop_addr == PJ_FALSE) { + if (stun_cfg->af == pj_AF_INET() && (pj_ntohl(addr->ipv4.sin_addr.s_addr) >> 24) == 127) { + continue; + } else if (stun_cfg->af == pj_AF_INET6()) { + pj_in6_addr in6addr = {{{0}}}; + in6addr.s6_addr[15] = 1; + if (pj_memcmp(&in6addr, &addr->ipv6.sin6_addr, sizeof(in6addr)) == 0) { + continue; + } + } + } + + /* Ignore IPv6 link-local address, unless it is the default + * address (first alias). + */ + if (stun_cfg->af == pj_AF_INET6() && i != 0) { + const pj_in6_addr *a = &addr->ipv6.sin6_addr; + if (a->s6_addr[0] == 0xFE && (a->s6_addr[1] & 0xC0) == 0x80) + continue; + } + + cand = &comp->cand_list[comp->cand_cnt]; + + cand->type = PJ_ICE_CAND_TYPE_HOST; + cand->status = PJ_SUCCESS; + cand->local_pref = (pj_uint16_t)(HOST_PREF - cand_cnt); + cand->transport_id = CREATE_TP_ID(TP_STUN, idx); + cand->comp_id = (pj_uint8_t)comp->comp_id; + pj_sockaddr_cp(&cand->addr, addr); + pj_sockaddr_cp(&cand->base_addr, addr); + pj_bzero(&cand->rel_addr, sizeof(cand->rel_addr)); + + /* Check if not already in list */ + for (j = 0; j < comp->cand_cnt; j++) { + if (ice_cand_equals(cand, &comp->cand_list[j])) { + cand_duplicate = PJ_TRUE; + break; + } + } + + if (cand_duplicate) { + PJ_LOG(4, (ice_st->obj_name, "Comp %d: host candidate %s (tpid=%d) is a duplicate", comp->comp_id, + pj_sockaddr_print(&cand->addr, addrinfo, sizeof(addrinfo), 3), cand->transport_id)); + + pj_bzero(&cand->addr, sizeof(cand->addr)); + pj_bzero(&cand->base_addr, sizeof(cand->base_addr)); + continue; + } else { + comp->cand_cnt += 1; + cand_cnt++; + max_cand_cnt--; + } + + pj_ice_calc_foundation(ice_st->pool, &cand->foundation, cand->type, &cand->base_addr); + + /* Set default candidate with the preferred default + * address family + */ + if (comp->ice_st->cfg.af != pj_AF_UNSPEC() && addr->addr.sa_family == comp->ice_st->cfg.af && + comp->cand_list[comp->default_cand].base_addr.addr.sa_family != ice_st->cfg.af) { + comp->default_cand = (unsigned)(cand - comp->cand_list); + } + + PJ_LOG(4, (ice_st->obj_name, "Comp %d/%d: host candidate %s (tpid=%d) added", comp->comp_id, + comp->cand_cnt - 1, pj_sockaddr_print(&cand->addr, addrinfo, sizeof(addrinfo), 3), + cand->transport_id)); + } + } + + return status; +} + +/* + * Create the component. + */ +static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id) +{ + pj_ice_strans_comp *comp = NULL; + unsigned i; + pj_status_t status; + + /* Verify arguments */ + PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL); + + /* Check that component ID present */ + PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID); + + /* Create component */ + comp = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_strans_comp); + comp->ice_st = ice_st; + comp->comp_id = comp_id; + comp->creating = PJ_TRUE; + + ice_st->comp[comp_id - 1] = comp; + + /* Initialize default candidate */ + comp->default_cand = 0; + + /* Create STUN transport if configured */ + for (i = 0; i < ice_st->cfg.stun_tp_cnt; ++i) { + unsigned max_cand_cnt = PJ_ICE_ST_MAX_CAND - comp->cand_cnt - ice_st->cfg.turn_tp_cnt; + + status = PJ_ETOOSMALL; + + if ((max_cand_cnt > 0) && (max_cand_cnt <= PJ_ICE_ST_MAX_CAND)) + status = add_stun_and_host(ice_st, comp, i, max_cand_cnt); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3, + (ice_st->obj_name, status, "Failed creating STUN transport #%d for comp %d", i, comp->comp_id)); + // return status; + } + } + + /* Create TURN relay if configured. */ + for (i = 0; i < ice_st->cfg.turn_tp_cnt; ++i) { + unsigned max_cand_cnt = PJ_ICE_ST_MAX_CAND - comp->cand_cnt; + + status = PJ_ETOOSMALL; + + if ((max_cand_cnt > 0) && (max_cand_cnt <= PJ_ICE_ST_MAX_CAND)) + status = add_update_turn(ice_st, comp, i, max_cand_cnt); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3, + (ice_st->obj_name, status, "Failed creating TURN transport #%d for comp %d", i, comp->comp_id)); + + // return status; + } else if (max_cand_cnt > 0) { + max_cand_cnt = PJ_ICE_ST_MAX_CAND - comp->cand_cnt; + } + } + + /* Done creating all the candidates */ + comp->creating = PJ_FALSE; + + /* It's possible that we end up without any candidates */ + if (comp->cand_cnt == 0) { + PJ_LOG(4, (ice_st->obj_name, "Error: no candidate is created due to settings")); + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + +static pj_status_t alloc_send_buf(pj_ice_strans *ice_st, unsigned buf_size) +{ + if (buf_size > ice_st->buf_size) { + unsigned i; + + if (ice_st->is_pending) { + /* The current buffer is insufficient, but still currently used.*/ + return PJ_EBUSY; + } + + pj_pool_safe_release(&ice_st->buf_pool); + + ice_st->buf_pool = + pj_pool_create(ice_st->pf, "ice_buf", (buf_size + sizeof(pending_send)) * ice_st->num_buf, 512, NULL); + if (!ice_st->buf_pool) + return PJ_ENOMEM; + + ice_st->buf_size = buf_size; + ice_st->send_buf = pj_pool_calloc(ice_st->buf_pool, ice_st->num_buf, sizeof(pending_send)); + for (i = 0; i < ice_st->num_buf; i++) { + ice_st->send_buf[i].buffer = pj_pool_alloc(ice_st->buf_pool, buf_size); + } + ice_st->buf_idx = ice_st->empty_idx = 0; + } + + return PJ_SUCCESS; +} + +/* + * Create ICE stream transport + */ +PJ_DEF(pj_status_t) +pj_ice_strans_create(const char *name, const pj_ice_strans_cfg *cfg, unsigned comp_cnt, void *user_data, + const pj_ice_strans_cb *cb, pj_ice_strans **p_ice_st) +{ + pj_pool_t *pool; + pj_ice_strans *ice_st; + unsigned i; + pj_status_t status; + + status = pj_ice_strans_cfg_check_valid(cfg); + if (status != PJ_SUCCESS) + return status; + + PJ_ASSERT_RETURN(comp_cnt && cb && p_ice_st && comp_cnt <= PJ_ICE_MAX_COMP, PJ_EINVAL); + + if (name == NULL) + name = "ice%p"; + + pool = pj_pool_create(cfg->stun_cfg.pf, name, PJNATH_POOL_LEN_ICE_STRANS, PJNATH_POOL_INC_ICE_STRANS, NULL); + ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans); + ice_st->pool = pool; + ice_st->pf = cfg->stun_cfg.pf; + ice_st->obj_name = pool->obj_name; + ice_st->user_data = user_data; + + PJ_LOG(4, (ice_st->obj_name, "Creating ICE stream transport with %d component(s)", comp_cnt)); + pj_log_push_indent(); + + status = pj_grp_lock_create(pool, NULL, &ice_st->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + pj_log_pop_indent(); + return status; + } + + /* Allocate send buffer */ + ice_st->num_buf = cfg->num_send_buf; + status = alloc_send_buf(ice_st, cfg->send_buf_size); + if (status != PJ_SUCCESS) { + destroy_ice_st(ice_st); + pj_log_pop_indent(); + return status; + } + + pj_grp_lock_add_ref(ice_st->grp_lock); + pj_grp_lock_add_handler(ice_st->grp_lock, pool, ice_st, &ice_st_on_destroy); + + pj_ice_strans_cfg_copy(pool, &ice_st->cfg, cfg); + + /* To maintain backward compatibility, check if old/deprecated setting is set + * and the new setting is not, copy the value to the new setting. + */ + if (cfg->stun_tp_cnt == 0 && (cfg->stun.server.slen || cfg->stun.max_host_cands)) { + ice_st->cfg.stun_tp_cnt = 1; + ice_st->cfg.stun_tp[0] = ice_st->cfg.stun; + } + if (cfg->turn_tp_cnt == 0 && cfg->turn.server.slen) { + ice_st->cfg.turn_tp_cnt = 1; + ice_st->cfg.turn_tp[0] = ice_st->cfg.turn; + } + + for (i = 0; i < ice_st->cfg.stun_tp_cnt; ++i) + ice_st->cfg.stun_tp[i].cfg.grp_lock = ice_st->grp_lock; + for (i = 0; i < ice_st->cfg.turn_tp_cnt; ++i) + ice_st->cfg.turn_tp[i].cfg.grp_lock = ice_st->grp_lock; + pj_memcpy(&ice_st->cb, cb, sizeof(*cb)); + + ice_st->comp_cnt = comp_cnt; + ice_st->comp = (pj_ice_strans_comp **)pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp *)); + + /* Move state to candidate gathering */ + ice_st->state = PJ_ICE_STRANS_STATE_INIT; + + /* Acquire initialization mutex to prevent callback to be + * called before we finish initialization. + */ + pj_grp_lock_acquire(ice_st->grp_lock); + + for (i = 0; i < comp_cnt; ++i) { + status = create_comp(ice_st, i + 1); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice_st->grp_lock); + destroy_ice_st(ice_st); + pj_log_pop_indent(); + return status; + } + } + + /* Done with initialization */ + pj_grp_lock_release(ice_st->grp_lock); + + PJ_LOG(4, (ice_st->obj_name, "ICE stream transport %p created", ice_st)); + + *p_ice_st = ice_st; + + /* Check if all candidates are ready (this may call callback) */ + sess_init_update(ice_st); + + /* If ICE init done, notify app about end of candidate gathering via + * on_new_candidate() callback. + */ + if (ice_st->state == PJ_ICE_STRANS_STATE_READY && ice_st->cb.on_new_candidate) { + (*ice_st->cb.on_new_candidate)(ice_st, NULL, PJ_TRUE); + } + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + +/* REALLY destroy ICE */ +static void ice_st_on_destroy(void *obj) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)obj; + + /* Destroy any previous ICE session */ + if (ice_st->ice_prev) { + (*ice_st->ice_prev_hndlr)(ice_st->ice_prev); + ice_st->ice_prev = NULL; + } + + PJ_LOG(4, (ice_st->obj_name, "ICE stream transport %p destroyed", obj)); + + /* Done */ + pj_pool_safe_release(&ice_st->buf_pool); + pj_pool_safe_release(&ice_st->pool); +} + +/* Destroy ICE */ +static void destroy_ice_st(pj_ice_strans *ice_st) +{ + unsigned i; + + PJ_LOG(5, (ice_st->obj_name, "ICE stream transport %p destroy request..", ice_st)); + pj_log_push_indent(); + + /* Reset callback and user data */ + pj_bzero(&ice_st->cb, sizeof(ice_st->cb)); + ice_st->user_data = NULL; + + pj_grp_lock_acquire(ice_st->grp_lock); + + if (ice_st->destroy_req) { + pj_grp_lock_release(ice_st->grp_lock); + return; + } + + ice_st->destroy_req = PJ_TRUE; + + /* Destroy ICE if we have ICE */ + if (ice_st->ice) { + pj_ice_sess *ice = ice_st->ice; + ice_st->ice = NULL; + pj_ice_sess_destroy(ice); + } + + /* Destroy all components */ + for (i = 0; i < ice_st->comp_cnt; ++i) { + if (ice_st->comp[i]) { + pj_ice_strans_comp *comp = ice_st->comp[i]; + unsigned j; + for (j = 0; j < ice_st->cfg.stun_tp_cnt; ++j) { + if (comp->stun[j].sock) { + pj_stun_sock_destroy(comp->stun[j].sock); + comp->stun[j].sock = NULL; + } + } + for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) { + if (comp->turn[j].sock) { + pj_turn_sock_destroy(comp->turn[j].sock); + comp->turn[j].sock = NULL; + } + } + } + } + + pj_grp_lock_dec_ref(ice_st->grp_lock); + pj_grp_lock_release(ice_st->grp_lock); + + pj_log_pop_indent(); +} + +/* Get ICE session state. */ +PJ_DEF(pj_ice_strans_state) pj_ice_strans_get_state(pj_ice_strans *ice_st) +{ + return ice_st->state; +} + +/* State string */ +PJ_DEF(const char *) pj_ice_strans_state_name(pj_ice_strans_state state) +{ + const char *names[] = {"Null", + "Candidate Gathering", + "Candidate Gathering Complete", + "Session Initialized", + "Negotiation In Progress", + "Negotiation Success", + "Negotiation Failed"}; + + PJ_ASSERT_RETURN(state <= PJ_ICE_STRANS_STATE_FAILED, "???"); + return names[state]; +} + +/* Notification about failure */ +static void sess_fail(pj_ice_strans *ice_st, pj_ice_strans_op op, const char *title, pj_status_t status) +{ + PJ_PERROR(4, (ice_st->obj_name, status, title)); + + pj_log_push_indent(); + + if (op == PJ_ICE_STRANS_OP_INIT && ice_st->cb_called) { + pj_log_pop_indent(); + return; + } + + ice_st->cb_called = PJ_TRUE; + + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, op, status); + + pj_log_pop_indent(); +} + +/* Update initialization status */ +static void sess_init_update(pj_ice_strans *ice_st) +{ + unsigned i; + pj_status_t status = PJ_EUNKNOWN; + + /* Ignore if ICE is destroying or init callback has been called */ + if (ice_st->destroy_req || ice_st->cb_called) + return; + + /* Notify application when all candidates have been gathered */ + for (i = 0; i < ice_st->comp_cnt; ++i) { + unsigned j; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + /* This function can be called when all components or candidates + * have not been created. + */ + if (!comp || comp->creating) { + PJ_LOG(5, (ice_st->obj_name, "ICE init update: creating comp %d", (comp ? comp->comp_id : (i + 1)))); + return; + } + + status = PJ_EUNKNOWN; + for (j = 0; j < comp->cand_cnt; ++j) { + pj_ice_sess_cand *cand = &comp->cand_list[j]; + + if (cand->status == PJ_EPENDING) { + PJ_LOG(5, (ice_st->obj_name, + "ICE init update: " + "comp %d/%d[%s] is pending", + comp->comp_id, j, pj_ice_get_cand_type_name(cand->type))); + return; + } + + if (status == PJ_EUNKNOWN) { + status = cand->status; + } else { + /* We only need one successful candidate. */ + if (cand->status == PJ_SUCCESS) + status = PJ_SUCCESS; + } + } + + if (status != PJ_SUCCESS) + break; + } + + /* All candidates have been gathered or there's no successful + * candidate for a component. + */ + ice_st->cb_called = PJ_TRUE; + ice_st->state = PJ_ICE_STRANS_STATE_READY; + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, status); + + /* Tell ICE session that trickling is done */ + ice_st->loc_cand_end = PJ_TRUE; + if (ice_st->ice && ice_st->ice->is_trickling && ice_st->rem_cand_end) { + pj_ice_sess_update_check_list(ice_st->ice, NULL, NULL, 0, NULL, PJ_TRUE); + } +} + +/* + * Destroy ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st) +{ + destroy_ice_st(ice_st); + return PJ_SUCCESS; +} + +/* + * Get user data + */ +PJ_DEF(void *) pj_ice_strans_get_user_data(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, NULL); + return ice_st->user_data; +} + +/* + * Get the value of various options of the ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st, pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL); + pj_memcpy(opt, &ice_st->cfg.opt, sizeof(*opt)); + return PJ_SUCCESS; +} + +/* + * Specify various options for this ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st, const pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL); + pj_memcpy(&ice_st->cfg.opt, opt, sizeof(*opt)); + if (ice_st->ice) + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + return PJ_SUCCESS; +} + +/* + * Update number of components of the ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_update_comp_cnt(pj_ice_strans *ice_st, unsigned comp_cnt) +{ + unsigned i; + + PJ_ASSERT_RETURN(ice_st && comp_cnt < ice_st->comp_cnt, PJ_EINVAL); + PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP); + + pj_grp_lock_acquire(ice_st->grp_lock); + + for (i = comp_cnt; i < ice_st->comp_cnt; ++i) { + pj_ice_strans_comp *comp = ice_st->comp[i]; + unsigned j; + + /* Destroy the component */ + for (j = 0; j < ice_st->cfg.stun_tp_cnt; ++j) { + if (comp->stun[j].sock) { + pj_stun_sock_destroy(comp->stun[j].sock); + comp->stun[j].sock = NULL; + } + } + for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) { + if (comp->turn[j].sock) { + pj_turn_sock_destroy(comp->turn[j].sock); + comp->turn[j].sock = NULL; + } + } + comp->cand_cnt = 0; + ice_st->comp[i] = NULL; + } + ice_st->comp_cnt = comp_cnt; + pj_grp_lock_release(ice_st->grp_lock); + + PJ_LOG(4, (ice_st->obj_name, "Updated ICE stream transport components number to %d", comp_cnt)); + + return PJ_SUCCESS; +} + +/** + * Get the group lock for this ICE stream transport. + */ +PJ_DEF(pj_grp_lock_t *) pj_ice_strans_get_grp_lock(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, NULL); + return ice_st->grp_lock; +} + +/* + * Create ICE! + */ +PJ_DEF(pj_status_t) +pj_ice_strans_init_ice(pj_ice_strans *ice_st, pj_ice_sess_role role, const pj_str_t *local_ufrag, + const pj_str_t *local_passwd) +{ + pj_status_t status; + unsigned i; + pj_ice_sess_cb ice_cb; + // const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 }; + + /* Check arguments */ + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + /* Must not have ICE */ + PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP); + /* Components must have been created */ + PJ_ASSERT_RETURN(ice_st->comp[0] != NULL, PJ_EINVALIDOP); + + /* Init callback */ + pj_bzero(&ice_cb, sizeof(ice_cb)); + ice_cb.on_valid_pair = &on_valid_pair; + ice_cb.on_ice_complete = &on_ice_complete; + ice_cb.on_rx_data = &ice_rx_data; + ice_cb.on_tx_pkt = &ice_tx_pkt; + + /* Release the pool of previous ICE session to avoid memory bloat, + * as otherwise it will only be released after ICE strans is destroyed + * (due to group lock). + */ + if (ice_st->ice_prev) { + (*ice_st->ice_prev_hndlr)(ice_st->ice_prev); + ice_st->ice_prev = NULL; + } + + /* Create! */ + status = pj_ice_sess_create(&ice_st->cfg.stun_cfg, ice_st->obj_name, role, ice_st->comp_cnt, &ice_cb, local_ufrag, + local_passwd, ice_st->grp_lock, &ice_st->ice); + if (status != PJ_SUCCESS) + return status; + + /* Associate user data */ + ice_st->ice->user_data = (void *)ice_st; + + /* Set options */ + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + + /* If default candidate for components are SRFLX one, upload a custom + * type priority to ICE session so that SRFLX candidates will get + * checked first. + */ + if (ice_st->comp[0]->cand_list[ice_st->comp[0]->default_cand].type == PJ_ICE_CAND_TYPE_SRFLX) { + pj_ice_sess_set_prefs(ice_st->ice, srflx_pref_table); + } + + /* Add components/candidates */ + for (i = 0; i < ice_st->comp_cnt; ++i) { + unsigned j; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + /* Re-enable logging for Send/Data indications */ + if (ice_st->cfg.turn_tp_cnt) { + PJ_LOG(5, (ice_st->obj_name, + "Enabling STUN Indication logging for " + "component %d", + i + 1)); + } + for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) { + if (comp->turn[j].sock) { + pj_turn_sock_set_log(comp->turn[j].sock, 0xFFFF); + comp->turn[j].log_off = PJ_FALSE; + } + } + + for (j = 0; j < comp->cand_cnt; ++j) { + pj_ice_sess_cand *cand = &comp->cand_list[j]; + unsigned ice_cand_id; + + /* Skip if candidate is not ready */ + if (cand->status != PJ_SUCCESS) { + PJ_LOG(5, (ice_st->obj_name, "Candidate %d of comp %d is not added (pending)", j, i)); + continue; + } + + /* Must have address */ + pj_assert(pj_sockaddr_has_addr(&cand->addr)); + + /* Skip if we are mapped to IPv4 address and this candidate + * is not IPv4. + */ + if (comp->ipv4_mapped && cand->addr.addr.sa_family != pj_AF_INET()) { + continue; + } + + /* Add the candidate */ + status = pj_ice_sess_add_cand(ice_st->ice, comp->comp_id, cand->transport_id, cand->type, cand->local_pref, + &cand->foundation, &cand->addr, &cand->base_addr, &cand->rel_addr, + pj_sockaddr_get_len(&cand->addr), (unsigned *)&ice_cand_id); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + /* ICE session is ready for negotiation */ + ice_st->state = PJ_ICE_STRANS_STATE_SESS_READY; + + return PJ_SUCCESS; + +on_error: + pj_ice_strans_stop_ice(ice_st); + return status; +} + +/* + * Check if the ICE stream transport has the ICE session created. + */ +PJ_DEF(pj_bool_t) pj_ice_strans_has_sess(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, PJ_FALSE); + return ice_st->ice != NULL; +} + +/* + * Check if ICE negotiation is still running. + */ +PJ_DEF(pj_bool_t) pj_ice_strans_sess_is_running(pj_ice_strans *ice_st) +{ + // Trickle ICE can start ICE before remote candidate list is received + return ice_st && ice_st->ice && /* ice_st->ice->rcand_cnt && */ + ice_st->ice->clist.state == PJ_ICE_SESS_CHECKLIST_ST_RUNNING && !pj_ice_strans_sess_is_complete(ice_st); +} + +/* + * Check if ICE negotiation has completed. + */ +PJ_DEF(pj_bool_t) pj_ice_strans_sess_is_complete(pj_ice_strans *ice_st) +{ + return ice_st && ice_st->ice && ice_st->ice->is_complete; +} + +/* + * Get the current/running component count. + */ +PJ_DEF(unsigned) pj_ice_strans_get_running_comp_cnt(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + + if (ice_st->ice && ice_st->ice->rcand_cnt) { + return ice_st->ice->comp_cnt; + } else { + return ice_st->comp_cnt; + } +} + +/* + * Get the ICE username fragment and password of the ICE session. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_get_ufrag_pwd(pj_ice_strans *ice_st, pj_str_t *loc_ufrag, pj_str_t *loc_pwd, pj_str_t *rem_ufrag, + pj_str_t *rem_pwd) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP); + + if (loc_ufrag) + *loc_ufrag = ice_st->ice->rx_ufrag; + if (loc_pwd) + *loc_pwd = ice_st->ice->rx_pass; + + if (rem_ufrag || rem_pwd) { + // In trickle ICE, remote may send initial SDP with empty candidates + // PJ_ASSERT_RETURN(ice_st->ice->rcand_cnt != 0, PJ_EINVALIDOP); + if (rem_ufrag) + *rem_ufrag = ice_st->ice->tx_ufrag; + if (rem_pwd) + *rem_pwd = ice_st->ice->tx_pass; + } + + return PJ_SUCCESS; +} + +/* + * Get number of candidates + */ +PJ_DEF(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st, unsigned comp_id) +{ + unsigned i, cnt; + + PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id && comp_id <= ice_st->comp_cnt, 0); + + cnt = 0; + for (i = 0; i < ice_st->ice->lcand_cnt; ++i) { + if (ice_st->ice->lcand[i].comp_id != comp_id) + continue; + ++cnt; + } + + return cnt; +} + +/* + * Enum candidates + */ +PJ_DEF(pj_status_t) +pj_ice_strans_enum_cands(pj_ice_strans *ice_st, unsigned comp_id, unsigned *count, pj_ice_sess_cand cand[]) +{ + unsigned i, cnt; + + PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id && comp_id <= ice_st->comp_cnt && count && cand, PJ_EINVAL); + + cnt = 0; + for (i = 0; i < ice_st->ice->lcand_cnt && cnt < *count; ++i) { + if (ice_st->ice->lcand[i].comp_id != comp_id) + continue; + pj_memcpy(&cand[cnt], &ice_st->ice->lcand[i], sizeof(pj_ice_sess_cand)); + ++cnt; + } + + *count = cnt; + return PJ_SUCCESS; +} + +/* + * Get default candidate. + */ +PJ_DEF(pj_status_t) pj_ice_strans_get_def_cand(pj_ice_strans *ice_st, unsigned comp_id, pj_ice_sess_cand *cand) +{ + const pj_ice_sess_check *valid_pair; + + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt && cand, PJ_EINVAL); + + valid_pair = pj_ice_strans_get_valid_pair(ice_st, comp_id); + if (valid_pair) { + pj_memcpy(cand, valid_pair->lcand, sizeof(pj_ice_sess_cand)); + } else { + pj_ice_strans_comp *comp = ice_st->comp[comp_id - 1]; + pj_assert(comp->default_cand < comp->cand_cnt); + pj_memcpy(cand, &comp->cand_list[comp->default_cand], sizeof(pj_ice_sess_cand)); + } + return PJ_SUCCESS; +} + +/* + * Get the current ICE role. + */ +PJ_DEF(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_ICE_SESS_ROLE_UNKNOWN); + return ice_st->ice->role; +} + +/* + * Change session role. + */ +PJ_DEF(pj_status_t) pj_ice_strans_change_role(pj_ice_strans *ice_st, pj_ice_sess_role new_role) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP); + return pj_ice_sess_change_role(ice_st->ice, new_role); +} + +static pj_status_t setup_turn_perm(pj_ice_strans *ice_st) +{ + unsigned n; + pj_status_t status; + + for (n = 0; n < ice_st->cfg.turn_tp_cnt; ++n) { + unsigned i, comp_cnt; + + comp_cnt = pj_ice_strans_get_running_comp_cnt(ice_st); + for (i = 0; i < comp_cnt; ++i) { + pj_ice_strans_comp *comp = ice_st->comp[i]; + pj_turn_session_info info; + pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND]; + unsigned j, count = 0; + unsigned rem_cand_cnt; + const pj_ice_sess_cand *rem_cand; + + if (!comp->turn[n].sock) + continue; + + status = pj_turn_sock_get_info(comp->turn[n].sock, &info); + if (status != PJ_SUCCESS || info.state != PJ_TURN_STATE_READY) + continue; + + /* Gather remote addresses for this component */ + rem_cand_cnt = ice_st->ice->rcand_cnt; + rem_cand = ice_st->ice->rcand; + if (status != PJ_SUCCESS) + continue; + + for (j = 0; j < rem_cand_cnt && count < PJ_ARRAY_SIZE(addrs); ++j) { + if (rem_cand[j].comp_id == i + 1 && rem_cand[j].addr.addr.sa_family == ice_st->cfg.turn_tp[n].af) { + pj_sockaddr_cp(&addrs[count++], &rem_cand[j].addr); + } + } + + if (count && !comp->turn[n].err_cnt && comp->turn[n].sock) { + status = pj_turn_sock_set_perm(comp->turn[n].sock, count, addrs, PJ_ICE_ST_USE_TURN_PERMANENT_PERM); + if (status != PJ_SUCCESS) { + pj_ice_strans_stop_ice(ice_st); + return status; + } + } + } + } + + return PJ_SUCCESS; +} + +/* + * Start ICE processing ! + */ +PJ_DEF(pj_status_t) +pj_ice_strans_start_ice(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[]) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP); + + /* Mark start time */ + pj_gettimeofday(&ice_st->start_time); + + /* Update check list */ + status = pj_ice_strans_update_check_list(ice_st, rem_ufrag, rem_passwd, rem_cand_cnt, rem_cand, + !ice_st->ice->is_trickling); + if (status != PJ_SUCCESS) + return status; + + /* If we have TURN candidate, now is the time to create the permissions */ + status = setup_turn_perm(ice_st); + if (status != PJ_SUCCESS) { + pj_ice_strans_stop_ice(ice_st); + return status; + } + + /* Start ICE negotiation! */ + status = pj_ice_sess_start_check(ice_st->ice); + if (status != PJ_SUCCESS) { + pj_ice_strans_stop_ice(ice_st); + return status; + } + + ice_st->state = PJ_ICE_STRANS_STATE_NEGO; + return status; +} + +/* + * Update check list after discovering and conveying new local ICE candidate, + * or receiving update of remote ICE candidates in trickle ICE. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_update_check_list(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[], pj_bool_t rcand_end) +{ + pj_bool_t checklist_created; + pj_status_t status; + + PJ_ASSERT_RETURN(ice_st && ((rem_cand_cnt == 0) || (rem_ufrag && rem_passwd && rem_cand)), PJ_EINVAL); + PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP); + + pj_grp_lock_acquire(ice_st->grp_lock); + + checklist_created = ice_st->ice->tx_ufrag.slen > 0; + + /* Create checklist (if not yet) */ + if (rem_ufrag && !checklist_created) { + status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd, rem_cand_cnt, rem_cand); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed setting up remote ufrag")); + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + /* Update checklist for trickling ICE */ + if (ice_st->ice->is_trickling) { + if (rcand_end && !ice_st->rem_cand_end) + ice_st->rem_cand_end = PJ_TRUE; + + status = + pj_ice_sess_update_check_list(ice_st->ice, rem_ufrag, rem_passwd, (checklist_created ? rem_cand_cnt : 0), + rem_cand, (ice_st->rem_cand_end && ice_st->loc_cand_end)); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed updating checklist")); + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + /* Update TURN permissions if periodic check has been started. */ + if (pj_ice_strans_sess_is_running(ice_st)) { + status = setup_turn_perm(ice_st); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed setting up TURN permission")); + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + pj_grp_lock_release(ice_st->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Get valid pair. + */ +PJ_DEF(const pj_ice_sess_check *) +pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st, unsigned comp_id) +{ + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt, NULL); + + if (ice_st->ice == NULL) + return NULL; + + return ice_st->ice->comp[comp_id - 1].valid_check; +} + +/* + * Stop ICE! + */ +PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + + /* Protect with group lock, since this may cause race condition with + * pj_ice_strans_sendto2(). + * See ticket #1877. + */ + pj_grp_lock_acquire(ice_st->grp_lock); + + if (ice_st->ice) { + ice_st->ice_prev = ice_st->ice; + ice_st->ice = NULL; + pj_ice_sess_detach_grp_lock(ice_st->ice_prev, &ice_st->ice_prev_hndlr); + pj_ice_sess_destroy(ice_st->ice_prev); + } + + ice_st->state = PJ_ICE_STRANS_STATE_INIT; + + pj_grp_lock_release(ice_st->grp_lock); + + return PJ_SUCCESS; +} + +static pj_status_t use_buffer(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len, void **buffer) +{ + unsigned idx; + pj_status_t status; + + /* Allocate send buffer, if necessary. */ + status = alloc_send_buf(ice_st, (unsigned)data_len); + if (status != PJ_SUCCESS) + return status; + + if (ice_st->is_pending && ice_st->empty_idx == ice_st->buf_idx) { + /* We don't use buffer or there's no more empty buffer. */ + return PJ_EBUSY; + } + + idx = ice_st->empty_idx; + ice_st->empty_idx = (ice_st->empty_idx + 1) % ice_st->num_buf; + ice_st->send_buf[idx].comp_id = comp_id; + ice_st->send_buf[idx].data_len = data_len; + pj_assert(ice_st->buf_size >= data_len); + pj_memcpy(ice_st->send_buf[idx].buffer, data, data_len); + pj_sockaddr_cp(&ice_st->send_buf[idx].dst_addr, dst_addr); + ice_st->send_buf[idx].dst_addr_len = dst_addr_len; + *buffer = ice_st->send_buf[idx].buffer; + + if (ice_st->is_pending) { + /* We'll continue later since there's still a pending send. */ + return PJ_EPENDING; + } + + ice_st->is_pending = PJ_TRUE; + ice_st->buf_idx = idx; + + return PJ_SUCCESS; +} + +/* + * Application wants to send outgoing packet. + */ +static pj_status_t send_data(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len, pj_bool_t use_buf, pj_bool_t call_cb) +{ + pj_ice_strans_comp *comp; + pj_ice_sess_cand *def_cand; + void *buf = (void *)data; + pj_status_t status; + + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt && dst_addr && dst_addr_len, PJ_EINVAL); + + comp = ice_st->comp[comp_id - 1]; + + /* Check that default candidate for the component exists */ + if (comp->default_cand >= comp->cand_cnt) { + status = PJ_EINVALIDOP; + if (call_cb) + on_data_sent(ice_st, -status); + return status; + } + + /* Protect with group lock, since this may cause race condition with + * pj_ice_strans_stop_ice(). + * See ticket #1877. + */ + pj_grp_lock_acquire(ice_st->grp_lock); + + if (use_buf && ice_st->num_buf > 0) { + status = use_buffer(ice_st, comp_id, data, data_len, dst_addr, dst_addr_len, &buf); + + if (status == PJ_EPENDING || status != PJ_SUCCESS) { + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + /* If ICE is available, send data with ICE. If ICE nego is not completed + * yet, ICE will try to send using any valid candidate pair. For any + * failure, it will fallback to sending with the default candidate + * selected during initialization. + * + * https://github.com/pjsip/pjproject/issues/1416: + * Once ICE has failed, also send data with the default candidate. + */ + if (ice_st->ice && ice_st->state <= PJ_ICE_STRANS_STATE_RUNNING) { + status = pj_ice_sess_send_data(ice_st->ice, comp_id, buf, data_len); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + pj_grp_lock_release(ice_st->grp_lock); + goto on_return; + } + } + + pj_grp_lock_release(ice_st->grp_lock); + + def_cand = &comp->cand_list[comp->default_cand]; + + if (def_cand->status == PJ_SUCCESS) { + unsigned tp_idx = GET_TP_IDX(def_cand->transport_id); + + if (def_cand->type == PJ_ICE_CAND_TYPE_RELAYED) { + + enum { msg_disable_ind = 0xFFFF & ~(PJ_STUN_SESS_LOG_TX_IND | PJ_STUN_SESS_LOG_RX_IND) }; + + /* https://github.com/pjsip/pjproject/issues/1316 */ + if (comp->turn[tp_idx].sock == NULL) { + /* TURN socket error */ + status = PJ_EINVALIDOP; + goto on_return; + } + + if (!comp->turn[tp_idx].log_off) { + /* Disable logging for Send/Data indications */ + PJ_LOG(5, (ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", + comp->comp_id)); + pj_turn_sock_set_log(comp->turn[tp_idx].sock, msg_disable_ind); + comp->turn[tp_idx].log_off = PJ_TRUE; + } + + status = pj_turn_sock_sendto(comp->turn[tp_idx].sock, (const pj_uint8_t *)buf, (unsigned)data_len, dst_addr, + dst_addr_len); + goto on_return; + } else { + const pj_sockaddr_t *dest_addr; + unsigned dest_addr_len; + + if (comp->ipv4_mapped) { + if (comp->synth_addr_len == 0 || pj_sockaddr_cmp(&comp->dst_addr, dst_addr) != 0) { + status = pj_sockaddr_synthesize(pj_AF_INET6(), &comp->synth_addr, dst_addr); + if (status != PJ_SUCCESS) + goto on_return; + + pj_sockaddr_cp(&comp->dst_addr, dst_addr); + comp->synth_addr_len = pj_sockaddr_get_len(&comp->synth_addr); + } + dest_addr = &comp->synth_addr; + dest_addr_len = comp->synth_addr_len; + } else { + dest_addr = dst_addr; + dest_addr_len = dst_addr_len; + } + + status = pj_stun_sock_sendto(comp->stun[tp_idx].sock, NULL, buf, (unsigned)data_len, 0, dest_addr, + dest_addr_len); + goto on_return; + } + + } else + status = PJ_EINVALIDOP; + +on_return: + /* We continue later in on_data_sent() callback. */ + if (status == PJ_EPENDING) + return status; + + if (call_cb) { + on_data_sent(ice_st, (status == PJ_SUCCESS ? data_len : -status)); + } else { + check_pending_send(ice_st); + } + + return status; +} + +#if !DEPRECATED_FOR_TICKET_2229 +/* + * Application wants to send outgoing packet. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_sendto(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len) +{ + pj_status_t status; + + PJ_LOG(1, (ice_st->obj_name, "pj_ice_strans_sendto() is deprecated. " + "Application is recommended to use " + "pj_ice_strans_sendto2() instead.")); + status = send_data(ice_st, comp_id, data, data_len, dst_addr, dst_addr_len, PJ_TRUE, PJ_FALSE); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + return status; +} +#endif + +/* + * Application wants to send outgoing packet. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_sendto2(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len) +{ + ice_st->call_send_cb = PJ_TRUE; + return send_data(ice_st, comp_id, data, data_len, dst_addr, dst_addr_len, PJ_TRUE, PJ_FALSE); +} + +static void on_valid_pair(pj_ice_sess *ice) +{ + pj_time_val t; + unsigned msec; + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + pj_ice_strans_cb cb = ice_st->cb; + pj_status_t status = PJ_SUCCESS; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + pj_gettimeofday(&t); + PJ_TIME_VAL_SUB(t, ice_st->start_time); + msec = PJ_TIME_VAL_MSEC(t); + + if (cb.on_valid_pair) { + unsigned i; + enum { msg_disable_ind = 0xFFFF & ~(PJ_STUN_SESS_LOG_TX_IND | PJ_STUN_SESS_LOG_RX_IND) }; + + PJ_LOG(4, (ice_st->obj_name, "First ICE candidate nominated in %ds:%03d", msec / 1000, msec % 1000)); + + for (i = 0; i < ice_st->comp_cnt; ++i) { + const pj_ice_sess_check *check; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + check = pj_ice_strans_get_valid_pair(ice_st, i + 1); + if (check) { + char lip[PJ_INET6_ADDRSTRLEN + 10]; + char rip[PJ_INET6_ADDRSTRLEN + 10]; + unsigned tp_idx = GET_TP_IDX(check->lcand->transport_id); + unsigned tp_typ = GET_TP_TYPE(check->lcand->transport_id); + + pj_sockaddr_print(&check->lcand->addr, lip, sizeof(lip), 3); + pj_sockaddr_print(&check->rcand->addr, rip, sizeof(rip), 3); + + if (tp_typ == TP_TURN) { + /* Activate channel binding for the remote address + * for more efficient data transfer using TURN. + */ + status = pj_turn_sock_bind_channel(comp->turn[tp_idx].sock, &check->rcand->addr, + sizeof(check->rcand->addr)); + + /* Disable logging for Send/Data indications */ + PJ_LOG(5, (ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", + i + 1)); + pj_turn_sock_set_log(comp->turn[tp_idx].sock, msg_disable_ind); + comp->turn[tp_idx].log_off = PJ_TRUE; + } + + PJ_LOG(4, (ice_st->obj_name, + " Comp %d: " + "sending from %s candidate %s to " + "%s candidate %s", + i + 1, pj_ice_get_cand_type_name(check->lcand->type), lip, + pj_ice_get_cand_type_name(check->rcand->type), rip)); + + } else { + PJ_LOG(4, (ice_st->obj_name, "Comp %d: disabled", i + 1)); + } + } + + ice_st->state = (status == PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : PJ_ICE_STRANS_STATE_FAILED; + + pj_log_push_indent(); + (*cb.on_valid_pair)(ice_st); + pj_log_pop_indent(); + } + + pj_grp_lock_dec_ref(ice_st->grp_lock); +} + +/* + * Callback called by ICE session when ICE processing is complete, either + * successfully or with failure. + */ +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + pj_time_val t; + unsigned msec; + pj_ice_strans_cb cb = ice_st->cb; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + pj_gettimeofday(&t); + PJ_TIME_VAL_SUB(t, ice_st->start_time); + msec = PJ_TIME_VAL_MSEC(t); + + if (cb.on_ice_complete) { + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "ICE negotiation failed after %ds:%03d", msec / 1000, msec % 1000)); + } else { + unsigned i; + enum { msg_disable_ind = 0xFFFF & ~(PJ_STUN_SESS_LOG_TX_IND | PJ_STUN_SESS_LOG_RX_IND) }; + + PJ_LOG(4, (ice_st->obj_name, "ICE negotiation success after %ds:%03d", msec / 1000, msec % 1000)); + + for (i = 0; i < ice_st->comp_cnt; ++i) { + const pj_ice_sess_check *check; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + check = pj_ice_strans_get_valid_pair(ice_st, i + 1); + if (check) { + char lip[PJ_INET6_ADDRSTRLEN + 10]; + char rip[PJ_INET6_ADDRSTRLEN + 10]; + unsigned tp_idx = GET_TP_IDX(check->lcand->transport_id); + unsigned tp_typ = GET_TP_TYPE(check->lcand->transport_id); + + pj_sockaddr_print(&check->lcand->addr, lip, sizeof(lip), 3); + pj_sockaddr_print(&check->rcand->addr, rip, sizeof(rip), 3); + + if (tp_typ == TP_TURN) { + /* Activate channel binding for the remote address + * for more efficient data transfer using TURN. + */ + status = pj_turn_sock_bind_channel(comp->turn[tp_idx].sock, &check->rcand->addr, + sizeof(check->rcand->addr)); + + /* Disable logging for Send/Data indications */ + PJ_LOG(5, (ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", + i + 1)); + pj_turn_sock_set_log(comp->turn[tp_idx].sock, msg_disable_ind); + comp->turn[tp_idx].log_off = PJ_TRUE; + } + + PJ_LOG(4, (ice_st->obj_name, + " Comp %d: " + "sending from %s candidate %s to " + "%s candidate %s", + i + 1, pj_ice_get_cand_type_name(check->lcand->type), lip, + pj_ice_get_cand_type_name(check->rcand->type), rip)); + + } else { + PJ_LOG(4, (ice_st->obj_name, "Comp %d: disabled", i + 1)); + } + } + } + + ice_st->state = (status == PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : PJ_ICE_STRANS_STATE_FAILED; + + pj_log_push_indent(); + (*cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, status); + pj_log_pop_indent(); + } + + pj_grp_lock_dec_ref(ice_st->grp_lock); +} + +/* + * Callback called by ICE session when it wants to send outgoing packet. + */ +static pj_status_t ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, + pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + pj_ice_strans_comp *comp; + pj_status_t status; + void *buf = (void *)pkt; + pj_bool_t use_buf = PJ_FALSE; +#if defined(ENABLE_TRACE) && (ENABLE_TRACE != 0) + char daddr[PJ_INET6_ADDRSTRLEN]; +#endif + unsigned tp_idx = GET_TP_IDX(transport_id); + unsigned tp_typ = GET_TP_TYPE(transport_id); + + PJ_ASSERT_RETURN(comp_id && comp_id <= ice_st->comp_cnt, PJ_EINVAL); + + pj_grp_lock_acquire(ice_st->grp_lock); + if (ice_st->num_buf > 0 && (!ice_st->send_buf || ice_st->send_buf[ice_st->buf_idx].buffer != pkt)) { + use_buf = PJ_TRUE; + status = use_buffer(ice_st, comp_id, pkt, size, dst_addr, dst_addr_len, &buf); + if (status == PJ_EPENDING || status != PJ_SUCCESS) { + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + pj_grp_lock_release(ice_st->grp_lock); + + comp = ice_st->comp[comp_id - 1]; + + TRACE_PKT((comp->ice_st->obj_name, "Component %d TX packet to %s:%d with transport %d", comp_id, + pj_sockaddr_print(dst_addr, daddr, sizeof(addr), 2), pj_sockaddr_get_port(dst_addr), tp_typ)); + + if (tp_typ == TP_TURN) { + if (comp->turn[tp_idx].sock) { + status = pj_turn_sock_sendto(comp->turn[tp_idx].sock, (const pj_uint8_t *)buf, (unsigned)size, dst_addr, + dst_addr_len); + } else { + status = PJ_EINVALIDOP; + } + } else if (tp_typ == TP_STUN) { + const pj_sockaddr_t *dest_addr; + unsigned dest_addr_len; + + if (comp->ipv4_mapped) { + if (comp->synth_addr_len == 0 || pj_sockaddr_cmp(&comp->dst_addr, dst_addr) != 0) { + status = pj_sockaddr_synthesize(pj_AF_INET6(), &comp->synth_addr, dst_addr); + if (status != PJ_SUCCESS) { + goto on_return; + } + + pj_sockaddr_cp(&comp->dst_addr, dst_addr); + comp->synth_addr_len = pj_sockaddr_get_len(&comp->synth_addr); + } + dest_addr = &comp->synth_addr; + dest_addr_len = comp->synth_addr_len; + } else { + dest_addr = dst_addr; + dest_addr_len = dst_addr_len; + } + + status = pj_stun_sock_sendto(comp->stun[tp_idx].sock, NULL, buf, (unsigned)size, 0, dest_addr, dest_addr_len); + } else { + pj_assert(!"Invalid transport ID"); + status = PJ_EINVALIDOP; + } + +on_return: + if (use_buf && status != PJ_EPENDING) { + pj_grp_lock_acquire(ice_st->grp_lock); + if (ice_st->num_buf > 0) { + ice_st->buf_idx = (ice_st->buf_idx + 1) % ice_st->num_buf; + pj_assert(ice_st->buf_idx == ice_st->empty_idx); + } + ice_st->is_pending = PJ_FALSE; + pj_grp_lock_release(ice_st->grp_lock); + } + + return status; +} + +/* + * Callback called by ICE session when it receives application data. + */ +static void ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + + PJ_UNUSED_ARG(transport_id); + + if (ice_st->cb.on_rx_data) { + (*ice_st->cb.on_rx_data)(ice_st, comp_id, pkt, size, src_addr, src_addr_len); + } +} + +static void check_pending_send(pj_ice_strans *ice_st) +{ + pj_grp_lock_acquire(ice_st->grp_lock); + + if (ice_st->num_buf > 0) + ice_st->buf_idx = (ice_st->buf_idx + 1) % ice_st->num_buf; + + if (ice_st->num_buf > 0 && ice_st->buf_idx != ice_st->empty_idx) { + /* There's some pending send. Send it one by one. */ + pending_send *ps = &ice_st->send_buf[ice_st->buf_idx]; + + pj_grp_lock_release(ice_st->grp_lock); + send_data(ice_st, ps->comp_id, ps->buffer, ps->data_len, &ps->dst_addr, ps->dst_addr_len, PJ_FALSE, PJ_TRUE); + } else { + ice_st->is_pending = PJ_FALSE; + pj_grp_lock_release(ice_st->grp_lock); + } +} + +/* Notifification when asynchronous send operation via STUN/TURN + * has completed. + */ +static pj_bool_t on_data_sent(pj_ice_strans *ice_st, pj_ssize_t sent) +{ + if (ice_st->destroy_req || !ice_st->is_pending) + return PJ_TRUE; + + if (ice_st->call_send_cb && ice_st->cb.on_data_sent) { + (*ice_st->cb.on_data_sent)(ice_st, sent); + } + + check_pending_send(ice_st); + + return PJ_TRUE; +} + +/* Notification when incoming packet has been received from + * the STUN socket. + */ +static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, + unsigned addr_len) +{ + sock_user_data *data; + pj_ice_strans_comp *comp; + pj_ice_strans *ice_st; + pj_status_t status; + + data = (sock_user_data *)pj_stun_sock_get_user_data(stun_sock); + if (data == NULL) { + /* We have disassociated ourselves from the STUN socket */ + return PJ_FALSE; + } + + comp = data->comp; + ice_st = comp->ice_st; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + if (ice_st->ice == NULL) { + /* The ICE session is gone, but we're still receiving packets. + * This could also happen if remote doesn't do ICE. So just + * report this to application. + */ + if (ice_st->cb.on_rx_data) { + (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id, pkt, pkt_len, src_addr, addr_len); + } + + } else { + + /* Hand over the packet to ICE session */ + status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id, data->transport_id, pkt, pkt_len, src_addr, + addr_len); + + if (status != PJ_SUCCESS) { + ice_st_perror(comp->ice_st, "Error processing packet", status); + } + } + + return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE; +} + +/* Notifification when asynchronous send operation to the STUN socket + * has completed. + */ +static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + sock_user_data *data; + + PJ_UNUSED_ARG(send_key); + + data = (sock_user_data *)pj_stun_sock_get_user_data(stun_sock); + if (!data || !data->comp || !data->comp->ice_st) + return PJ_TRUE; + + return on_data_sent(data->comp->ice_st, sent); +} + +/* Notification when the status of the STUN transport has changed. */ +static pj_bool_t stun_on_status(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status) +{ + sock_user_data *data; + pj_ice_strans_comp *comp; + pj_ice_strans *ice_st; + pj_ice_sess_cand *cand = NULL; + unsigned i; + int tp_idx; + + pj_assert(status != PJ_EPENDING); + + data = (sock_user_data *)pj_stun_sock_get_user_data(stun_sock); + comp = data->comp; + ice_st = comp->ice_st; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + /* Wait until initialization completes */ + pj_grp_lock_acquire(ice_st->grp_lock); + + /* Find the srflx cancidate */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX && + comp->cand_list[i].transport_id == data->transport_id) { + cand = &comp->cand_list[i]; + break; + } + } + + pj_grp_lock_release(ice_st->grp_lock); + + /* It is possible that we don't have srflx candidate even though this + * callback is called. This could happen when we cancel adding srflx + * candidate due to initialization error. + */ + if (cand == NULL) { + return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE; + } + + tp_idx = GET_TP_IDX(data->transport_id); + + switch (op) { + case PJ_STUN_SOCK_DNS_OP: + if (status != PJ_SUCCESS) { + /* May not have cand, e.g. when error during init */ + if (cand) + cand->status = status; + if (!ice_st->cfg.stun_tp[tp_idx].ignore_stun_error) { + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "DNS resolution failed", status); + } else { + PJ_LOG(4, (ice_st->obj_name, "STUN error is ignored for comp %d", comp->comp_id)); + } + } + break; + case PJ_STUN_SOCK_BINDING_OP: + case PJ_STUN_SOCK_MAPPED_ADDR_CHANGE: + if (status == PJ_SUCCESS) { + pj_stun_sock_info info; + + status = pj_stun_sock_get_info(stun_sock, &info); + if (status == PJ_SUCCESS) { + char ipaddr[PJ_INET6_ADDRSTRLEN + 10]; + const char *op_name = + (op == PJ_STUN_SOCK_BINDING_OP) ? "Binding discovery complete" : "srflx address changed"; + pj_bool_t dup = PJ_FALSE; + pj_bool_t init_done; + + if (info.mapped_addr.addr.sa_family == pj_AF_INET() && + cand->base_addr.addr.sa_family == pj_AF_INET6()) { + /* We get an IPv4 mapped address for our IPv6 + * host address. + */ + comp->ipv4_mapped = PJ_TRUE; + + /* Find other host candidates with the same (IPv6) + * address, and replace it with the new (IPv4) + * mapped address. + */ + for (i = 0; i < comp->cand_cnt; ++i) { + pj_sockaddr *a1, *a2; + + if (comp->cand_list[i].type != PJ_ICE_CAND_TYPE_HOST) + continue; + + a1 = &comp->cand_list[i].addr; + a2 = &cand->base_addr; + if (pj_memcmp(pj_sockaddr_get_addr(a1), pj_sockaddr_get_addr(a2), + pj_sockaddr_get_addr_len(a1)) == 0) { + pj_uint16_t port = pj_sockaddr_get_port(a1); + pj_sockaddr_cp(a1, &info.mapped_addr); + if (port != pj_sockaddr_get_port(a2)) + pj_sockaddr_set_port(a1, port); + pj_sockaddr_cp(&comp->cand_list[i].base_addr, a1); + } + } + pj_sockaddr_cp(&cand->base_addr, &info.mapped_addr); + pj_sockaddr_cp(&cand->rel_addr, &info.mapped_addr); + } + + /* Eliminate the srflx candidate if the address is + * equal to other (host) candidates. + */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_HOST && + pj_sockaddr_cmp(&comp->cand_list[i].addr, &info.mapped_addr) == 0) { + dup = PJ_TRUE; + break; + } + } + + if (dup) { + /* Duplicate found, remove the srflx candidate */ + unsigned idx = (unsigned)(cand - comp->cand_list); + + /* Update default candidate index */ + if (comp->default_cand > idx) { + --comp->default_cand; + } else if (comp->default_cand == idx) { + comp->default_cand = 0; + } + + /* Remove srflx candidate */ + pj_array_erase(comp->cand_list, sizeof(comp->cand_list[0]), comp->cand_cnt, idx); + --comp->cand_cnt; + } else { + /* Otherwise update the address */ + pj_sockaddr_cp(&cand->addr, &info.mapped_addr); + cand->status = PJ_SUCCESS; + + /* Add the candidate (for trickle ICE) */ + if (pj_ice_strans_has_sess(ice_st)) { + status = + pj_ice_sess_add_cand(ice_st->ice, comp->comp_id, cand->transport_id, cand->type, + cand->local_pref, &cand->foundation, &cand->addr, &cand->base_addr, + &cand->rel_addr, pj_sockaddr_get_len(&cand->addr), NULL); + } + } + + PJ_LOG(4, (comp->ice_st->obj_name, + "Comp %d: %s, " + "srflx address is %s", + comp->comp_id, op_name, pj_sockaddr_print(&info.mapped_addr, ipaddr, sizeof(ipaddr), 3))); + + sess_init_update(ice_st); + + /* Invoke on_new_candidate() callback */ + init_done = (ice_st->state == PJ_ICE_STRANS_STATE_READY); + if (op == PJ_STUN_SOCK_BINDING_OP && status == PJ_SUCCESS && ice_st->cb.on_new_candidate && + (!dup || init_done)) { + (*ice_st->cb.on_new_candidate)(ice_st, (dup ? NULL : cand), init_done); + } + + if (op == PJ_STUN_SOCK_MAPPED_ADDR_CHANGE && ice_st->cb.on_ice_complete) { + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_ADDR_CHANGE, status); + } + } + } + + if (status != PJ_SUCCESS) { + /* May not have cand, e.g. when error during init */ + if (cand) + cand->status = status; + if (!ice_st->cfg.stun_tp[tp_idx].ignore_stun_error || comp->cand_cnt == 1) { + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "STUN binding request failed", status); + } else { + pj_bool_t init_done; + + PJ_LOG(4, (ice_st->obj_name, "STUN error is ignored for comp %d", comp->comp_id)); + + if (cand) { + unsigned idx = (unsigned)(cand - comp->cand_list); + + /* Update default candidate index */ + if (comp->default_cand == idx) { + comp->default_cand = !idx; + } + } + + sess_init_update(ice_st); + + /* Invoke on_new_candidate() callback */ + init_done = (ice_st->state == PJ_ICE_STRANS_STATE_READY); + if (op == PJ_STUN_SOCK_BINDING_OP && ice_st->cb.on_new_candidate && init_done) { + (*ice_st->cb.on_new_candidate)(ice_st, NULL, PJ_TRUE); + } + } + } + break; + case PJ_STUN_SOCK_KEEP_ALIVE_OP: + if (status != PJ_SUCCESS) { + pj_assert(cand != NULL); + cand->status = status; + if (!ice_st->cfg.stun_tp[tp_idx].ignore_stun_error) { + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "STUN keep-alive failed", status); + } else { + PJ_LOG(4, (ice_st->obj_name, "STUN error is ignored")); + } + } + break; + } + + return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE; +} + +/* Callback when TURN socket has received a packet */ +static void turn_on_rx_data(pj_turn_sock *turn_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_ice_strans_comp *comp; + sock_user_data *data; + pj_status_t status; + + data = (sock_user_data *)pj_turn_sock_get_user_data(turn_sock); + if (data == NULL) { + /* We have disassociated ourselves from the TURN socket */ + return; + } + + comp = data->comp; + + pj_grp_lock_add_ref(comp->ice_st->grp_lock); + + if (comp->ice_st->ice == NULL) { + /* The ICE session is gone, but we're still receiving packets. + * This could also happen if remote doesn't do ICE and application + * specifies TURN as the default address in SDP. + * So in this case just give the packet to application. + */ + if (comp->ice_st->cb.on_rx_data) { + (*comp->ice_st->cb.on_rx_data)(comp->ice_st, comp->comp_id, pkt, pkt_len, peer_addr, addr_len); + } + + } else { + + /* Hand over the packet to ICE */ + status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id, data->transport_id, pkt, pkt_len, peer_addr, + addr_len); + + if (status != PJ_SUCCESS) { + ice_st_perror(comp->ice_st, "Error processing packet from TURN relay", status); + } + } + + pj_grp_lock_dec_ref(comp->ice_st->grp_lock); +} + +/* Notifification when asynchronous send operation to the TURN socket + * has completed. + */ +static pj_bool_t turn_on_data_sent(pj_turn_sock *turn_sock, pj_ssize_t sent) +{ + sock_user_data *data; + + data = (sock_user_data *)pj_turn_sock_get_user_data(turn_sock); + if (!data || !data->comp || !data->comp->ice_st) + return PJ_TRUE; + + return on_data_sent(data->comp->ice_st, sent); +} + +/* Callback when TURN client state has changed */ +static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, pj_turn_state_t new_state) +{ + pj_ice_strans_comp *comp; + sock_user_data *data; + int tp_idx; + + data = (sock_user_data *)pj_turn_sock_get_user_data(turn_sock); + if (data == NULL) { + /* Not interested in further state notification once the relay is + * disconnecting. + */ + return; + } + + comp = data->comp; + tp_idx = GET_TP_IDX(data->transport_id); + + PJ_LOG(5, (comp->ice_st->obj_name, "TURN client state changed %s --> %s", pj_turn_state_name(old_state), + pj_turn_state_name(new_state))); + pj_log_push_indent(); + + pj_grp_lock_add_ref(comp->ice_st->grp_lock); + + if (new_state == PJ_TURN_STATE_READY) { + pj_turn_session_info rel_info; + char ipaddr[PJ_INET6_ADDRSTRLEN + 8]; + pj_ice_sess_cand *cand = NULL; + unsigned i, cand_idx = 0xFF; + + comp->turn[tp_idx].err_cnt = 0; + + /* Get allocation info */ + pj_turn_sock_get_info(turn_sock, &rel_info); + + /* Wait until initialization completes */ + pj_grp_lock_acquire(comp->ice_st->grp_lock); + + /* Find relayed candidate in the component */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && + comp->cand_list[i].transport_id == data->transport_id) { + cand = &comp->cand_list[i]; + cand_idx = i; + break; + } + } + + pj_grp_lock_release(comp->ice_st->grp_lock); + + if (cand == NULL) + goto on_return; + + /* Update candidate */ + pj_sockaddr_cp(&cand->addr, &rel_info.relay_addr); + pj_sockaddr_cp(&cand->base_addr, &rel_info.relay_addr); + pj_sockaddr_cp(&cand->rel_addr, &rel_info.mapped_addr); + pj_ice_calc_foundation(comp->ice_st->pool, &cand->foundation, PJ_ICE_CAND_TYPE_RELAYED, &rel_info.relay_addr); + cand->status = PJ_SUCCESS; + + /* Set default candidate to relay */ + if (comp->cand_list[comp->default_cand].type != PJ_ICE_CAND_TYPE_RELAYED || + (comp->ice_st->cfg.af != pj_AF_UNSPEC() && + comp->cand_list[comp->default_cand].addr.addr.sa_family != comp->ice_st->cfg.af)) { + comp->default_cand = (unsigned)(cand - comp->cand_list); + } + + /* Prefer IPv4 relay as default candidate for better connectivity + * with IPv4 endpoints. + */ + /* + if (cand->addr.addr.sa_family != pj_AF_INET()) { + for (i=0; icand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && + comp->cand_list[i].addr.addr.sa_family == pj_AF_INET() && + comp->cand_list[i].status == PJ_SUCCESS) + { + comp->default_cand = i; + break; + } + } + } + */ + + PJ_LOG(4, (comp->ice_st->obj_name, + "Comp %d/%d: TURN allocation (tpid=%d) complete, " + "relay address is %s", + comp->comp_id, cand_idx, cand->transport_id, + pj_sockaddr_print(&rel_info.relay_addr, ipaddr, sizeof(ipaddr), 3))); + + /* For trickle ICE, add the candidate to ICE session and setup TURN + * permission for remote candidates. + */ + if (comp->ice_st->cfg.opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED && pj_ice_strans_has_sess(comp->ice_st)) { + pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND]; + pj_ice_sess *sess = comp->ice_st->ice; + unsigned j, count = 0; + pj_status_t status; + + /* Add the candidate */ + status = pj_ice_sess_add_cand(comp->ice_st->ice, comp->comp_id, cand->transport_id, cand->type, + cand->local_pref, &cand->foundation, &cand->addr, &cand->base_addr, + &cand->rel_addr, pj_sockaddr_get_len(&cand->addr), NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (comp->ice_st->obj_name, status, "Comp %d/%d: failed to add TURN (tpid=%d) to ICE", + comp->comp_id, cand_idx, cand->transport_id)); + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, "adding TURN candidate failed", status); + } + + /* Gather remote addresses for this component */ + for (j = 0; j < sess->rcand_cnt && count < PJ_ARRAY_SIZE(addrs); ++j) { + if (sess->rcand[j].addr.addr.sa_family == rel_info.relay_addr.addr.sa_family) { + pj_sockaddr_cp(&addrs[count++], &sess->rcand[j].addr); + } + } + + if (count) { + status = pj_turn_sock_set_perm(turn_sock, count, addrs, 0); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (comp->ice_st->obj_name, status, "Comp %d/%d: TURN set perm (tpid=%d) failed", + comp->comp_id, cand_idx, cand->transport_id)); + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, "TURN set permission failed", status); + } + } + } + + sess_init_update(comp->ice_st); + + /* Invoke on_new_candidate() callback */ + if (comp->ice_st->cb.on_new_candidate) { + (*comp->ice_st->cb.on_new_candidate)(comp->ice_st, cand, + (comp->ice_st->state == PJ_ICE_STRANS_STATE_READY)); + } + + } else if ((old_state == PJ_TURN_STATE_RESOLVING || old_state == PJ_TURN_STATE_RESOLVED || + old_state == PJ_TURN_STATE_ALLOCATING) && + new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_ice_sess_cand *cand = NULL; + unsigned i, cand_idx = 0xFF; + + /* DNS resolution or TURN transport creation/allocation + * has failed. + */ + ++comp->turn[tp_idx].err_cnt; + + /* Unregister ourself from the TURN relay */ + pj_turn_sock_set_user_data(turn_sock, NULL); + comp->turn[tp_idx].sock = NULL; + + /* Wait until initialization completes */ + pj_grp_lock_acquire(comp->ice_st->grp_lock); + + /* Find relayed candidate in the component */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && + comp->cand_list[i].transport_id == data->transport_id) { + cand = &comp->cand_list[i]; + cand_idx = i; + break; + } + } + + pj_grp_lock_release(comp->ice_st->grp_lock); + + /* If the error happens during pj_turn_sock_create() or + * pj_turn_sock_alloc(), the candidate hasn't been added + * to the list. + */ + if (cand) { + pj_turn_session_info info; + + pj_turn_sock_get_info(turn_sock, &info); + cand->status = (old_state == PJ_TURN_STATE_RESOLVING) ? PJ_ERESOLVE : info.last_status; + PJ_LOG(4, (comp->ice_st->obj_name, "Comp %d/%d: TURN error (tpid=%d) during state %s", comp->comp_id, + cand_idx, cand->transport_id, pj_turn_state_name(old_state))); + } + + sess_init_update(comp->ice_st); + + /* Invoke on_new_candidate() callback */ + if (comp->ice_st->cb.on_new_candidate && comp->ice_st->state == PJ_ICE_STRANS_STATE_READY) { + (*comp->ice_st->cb.on_new_candidate)(comp->ice_st, NULL, PJ_TRUE); + } + + } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_turn_session_info info; + + ++comp->turn[tp_idx].err_cnt; + + pj_turn_sock_get_info(turn_sock, &info); + + /* Unregister ourself from the TURN relay */ + pj_turn_sock_set_user_data(turn_sock, NULL); + comp->turn[tp_idx].sock = NULL; + + /* Set session to fail on error. last_status PJ_SUCCESS means normal + * deallocation, which should not trigger sess_fail as it may have + * been initiated by ICE destroy + */ + if (info.last_status != PJ_SUCCESS) { + if (comp->ice_st->state < PJ_ICE_STRANS_STATE_READY) { + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, "TURN allocation failed", info.last_status); + } else if (comp->turn[tp_idx].err_cnt > 1) { + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_KEEP_ALIVE, "TURN refresh failed", info.last_status); + } else { + PJ_PERROR(4, (comp->ice_st->obj_name, info.last_status, "Comp %d: TURN allocation failed, retrying", + comp->comp_id)); + add_update_turn(comp->ice_st, comp, tp_idx, PJ_ICE_ST_MAX_CAND - comp->cand_cnt); + } + } + } + +on_return: + pj_grp_lock_dec_ref(comp->ice_st->grp_lock); + + pj_log_pop_indent(); +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c new file mode 100755 index 000000000..2f2d2ad1e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *nat_type_names[] = {"Unknown", "ErrUnknown", "Open", "Blocked", "Symmetric UDP", + "Full Cone", "Symmetric", "Restricted", "Port Restricted"}; + +#define CHANGE_IP_FLAG 4 +#define CHANGE_PORT_FLAG 2 +#define CHANGE_IP_PORT_FLAG (CHANGE_IP_FLAG | CHANGE_PORT_FLAG) +#define TEST_INTERVAL 50 + +enum test_type { ST_TEST_1, ST_TEST_2, ST_TEST_3, ST_TEST_1B, ST_MAX }; + +static const char *test_names[] = { + "Test I: Binding request", "Test II: Binding request with change address and port request", + "Test III: Binding request with change port request", "Test IB: Binding request to alternate address"}; + +enum timer_type { TIMER_TEST = 1, TIMER_DESTROY = 2 }; + +typedef struct nat_detect_session { + pj_pool_t *pool; + pj_grp_lock_t *grp_lock; + + pj_timer_heap_t *timer_heap; + pj_timer_entry timer; + unsigned timer_executed; + + void *user_data; + pj_stun_nat_detect_cb *cb; + pj_sock_t sock; + pj_sockaddr local_addr; + pj_ioqueue_key_t *key; + pj_sockaddr server; + pj_sockaddr *cur_server; + pj_sockaddr cur_addr; + pj_stun_session *stun_sess; + + pj_ioqueue_op_key_t read_op, write_op; + pj_uint8_t rx_pkt[PJ_STUN_MAX_PKT_LEN]; + pj_ssize_t rx_pkt_len; + pj_sockaddr src_addr; + int src_addr_len; + + struct result { + pj_bool_t executed; + pj_bool_t complete; + pj_status_t status; + pj_sockaddr ma; + pj_sockaddr ca; + pj_stun_tx_data *tdata; + } result[ST_MAX]; + +} nat_detect_session; + +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); +static void on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +static pj_status_t on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + +static pj_status_t send_test(nat_detect_session *sess, enum test_type test_id, const pj_sockaddr *alt_addr, + pj_uint32_t change_flag); +static void on_sess_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void sess_destroy(nat_detect_session *sess); +static void sess_on_destroy(void *member); + +/* + * Get the NAT name from the specified NAT type. + */ +PJ_DEF(const char *) pj_stun_get_nat_name(pj_stun_nat_type type) +{ + PJ_ASSERT_RETURN(type >= 0 && type <= PJ_STUN_NAT_TYPE_PORT_RESTRICTED, "*Invalid*"); + + return nat_type_names[type]; +} + +static int test_executed(nat_detect_session *sess) +{ + unsigned i, count; + for (i = 0, count = 0; i < PJ_ARRAY_SIZE(sess->result); ++i) { + if (sess->result[i].executed) + ++count; + } + return count; +} + +static int test_completed(nat_detect_session *sess) +{ + unsigned i, count; + for (i = 0, count = 0; i < PJ_ARRAY_SIZE(sess->result); ++i) { + if (sess->result[i].complete) + ++count; + } + return count; +} + +static pj_status_t get_local_interface(const pj_sockaddr *server, pj_sockaddr *local_addr) +{ + pj_sock_t sock; + pj_sockaddr tmp, local; + int addr_len; + pj_status_t status; + + status = pj_sock_socket(server->addr.sa_family, pj_SOCK_DGRAM(), 0, &sock); + if (status != PJ_SUCCESS) + return status; + + addr_len = pj_sockaddr_get_len(server); + pj_sockaddr_init(server->addr.sa_family, &local, NULL, 0); + status = pj_sock_bind(sock, &local, addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + status = pj_sock_connect(sock, server, addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + status = pj_sock_getsockname(sock, &tmp, &addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + pj_sockaddr_cp(local_addr, &tmp); + + pj_sock_close(sock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_detect_nat_type(const pj_sockaddr_in *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb) +{ + pj_sockaddr srv; + + if (server) + pj_sockaddr_cp(&srv, server); + + return pj_stun_detect_nat_type2(&srv, stun_cfg, user_data, cb); +} + +PJ_DEF(pj_status_t) +pj_stun_detect_nat_type2(const pj_sockaddr *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb) +{ + pj_pool_t *pool; + nat_detect_session *sess; + pj_stun_session_cb sess_cb; + pj_ioqueue_callback ioqueue_cb; + int af, addr_len; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_status_t status; + + PJ_ASSERT_RETURN(server && stun_cfg, PJ_EINVAL); + PJ_ASSERT_RETURN(stun_cfg->pf && stun_cfg->ioqueue && stun_cfg->timer_heap, PJ_EINVAL); + + /* + * Init NAT detection session. + */ + pool = pj_pool_create(stun_cfg->pf, "natck%p", PJNATH_POOL_LEN_NATCK, PJNATH_POOL_INC_NATCK, NULL); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, nat_detect_session); + sess->pool = pool; + sess->user_data = user_data; + sess->cb = cb; + + status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); + if (status != PJ_SUCCESS) { + /* Group lock not created yet, just destroy pool and return */ + pj_pool_release(pool); + return status; + } + + pj_grp_lock_add_ref(sess->grp_lock); + pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &sess_on_destroy); + + pj_sockaddr_cp(&sess->server, server); + + /* + * Init timer to self-destroy. + */ + sess->timer_heap = stun_cfg->timer_heap; + sess->timer.cb = &on_sess_timer; + sess->timer.user_data = sess; + + /* + * Initialize socket. + */ + af = server->addr.sa_family; + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sess->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Bind to any. + */ + addr_len = pj_sockaddr_get_len(server); + pj_sockaddr_init(server->addr.sa_family, &sess->local_addr, NULL, 0); + status = pj_sock_bind(sess->sock, &sess->local_addr, addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Get local/bound address. + */ + status = pj_sock_getsockname(sess->sock, &sess->local_addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Find out which interface is used to send to the server. + */ + status = get_local_interface(server, &sess->local_addr); + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(5, (sess->pool->obj_name, "Local address is %s:%d", + pj_sockaddr_print(&sess->local_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&sess->local_addr))); + + PJ_LOG(5, (sess->pool->obj_name, "Server set to %s:%d", pj_sockaddr_print(server, addr, sizeof(addr), 2), + pj_sockaddr_get_port(server))); + + /* + * Register socket to ioqueue to receive asynchronous input + * notification. + */ + pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb)); + ioqueue_cb.on_read_complete = &on_read_complete; + + status = pj_ioqueue_register_sock2(sess->pool, stun_cfg->ioqueue, sess->sock, sess->grp_lock, sess, &ioqueue_cb, + &sess->key); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Create STUN session. + */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &on_request_complete; + sess_cb.on_send_msg = &on_send_msg; + status = pj_stun_session_create(stun_cfg, pool->obj_name, &sess_cb, PJ_FALSE, sess->grp_lock, &sess->stun_sess); + if (status != PJ_SUCCESS) + goto on_error; + + pj_stun_session_set_user_data(sess->stun_sess, sess); + + /* + * Kick-off ioqueue reading. + */ + pj_ioqueue_op_key_init(&sess->read_op, sizeof(sess->read_op)); + pj_ioqueue_op_key_init(&sess->write_op, sizeof(sess->write_op)); + on_read_complete(sess->key, &sess->read_op, 0); + + /* + * Start TEST_1 + */ + sess->timer.id = TIMER_TEST; + on_sess_timer(stun_cfg->timer_heap, &sess->timer); + + return PJ_SUCCESS; + +on_error: + sess_destroy(sess); + return status; +} + +static void sess_destroy(nat_detect_session *sess) +{ + if (sess->stun_sess) { + pj_stun_session_destroy(sess->stun_sess); + sess->stun_sess = NULL; + } + + if (sess->key) { + pj_ioqueue_unregister(sess->key); + sess->key = NULL; + sess->sock = PJ_INVALID_SOCKET; + } else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) { + pj_sock_close(sess->sock); + sess->sock = PJ_INVALID_SOCKET; + } + + if (sess->grp_lock) { + pj_grp_lock_dec_ref(sess->grp_lock); + } +} + +static void sess_on_destroy(void *member) +{ + nat_detect_session *sess = (nat_detect_session *)member; + if (sess->pool) { + pj_pool_release(sess->pool); + } +} + +static void end_session(nat_detect_session *sess, pj_status_t status, pj_stun_nat_type nat_type) +{ + pj_stun_nat_detect_result result; + char errmsg[PJ_ERR_MSG_SIZE]; + pj_time_val delay; + + if (sess->timer.id != 0) { + pj_timer_heap_cancel(sess->timer_heap, &sess->timer); + sess->timer.id = 0; + } + + pj_bzero(&result, sizeof(result)); + errmsg[0] = '\0'; + result.status_text = errmsg; + + result.status = status; + pj_strerror(status, errmsg, sizeof(errmsg)); + result.nat_type = nat_type; + result.nat_type_name = nat_type_names[result.nat_type]; + + if (sess->cb) + (*sess->cb)(sess->user_data, &result); + + delay.sec = 0; + delay.msec = 0; + + sess->timer.id = TIMER_DESTROY; + pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay); +} + +/* + * Callback upon receiving packet from network. + */ +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) +{ + nat_detect_session *sess; + pj_status_t status; + + sess = (nat_detect_session *)pj_ioqueue_get_user_data(key); + pj_assert(sess != NULL); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Ignore packet when STUN session has been destroyed */ + if (!sess->stun_sess) + goto on_return; + + if (bytes_read < 0) { + if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) { + /* Permanent error */ + end_session(sess, (pj_status_t)-bytes_read, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + goto on_return; + } + + } else if (bytes_read > 0) { + pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET, + NULL, NULL, &sess->src_addr, sess->src_addr_len); + } + + sess->rx_pkt_len = sizeof(sess->rx_pkt); + sess->src_addr_len = sizeof(sess->src_addr); + status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len, PJ_IOQUEUE_ALWAYS_ASYNC, &sess->src_addr, + &sess->src_addr_len); + + if (status != PJ_EPENDING) { + pj_assert(status != PJ_SUCCESS); + end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); +} + +/* + * Callback to send outgoing packet from STUN session. + */ +static pj_status_t on_send_msg(pj_stun_session *stun_sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + nat_detect_session *sess; + pj_ssize_t pkt_len; + pj_status_t status; + + PJ_UNUSED_ARG(token); + + sess = (nat_detect_session *)pj_stun_session_get_user_data(stun_sess); + + pkt_len = pkt_size; + status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0, dst_addr, addr_len); + + return status; +} + +/* + * Callback upon request completion. + */ +static void on_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + nat_detect_session *sess; + pj_stun_sockaddr_attr *mattr = NULL; + pj_stun_changed_addr_attr *ca = NULL; + pj_uint32_t *tsx_id; + int cmp; + unsigned test_id; + + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(tdata); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (nat_detect_session *)pj_stun_session_get_user_data(stun_sess); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Find errors in the response */ + if (status == PJ_SUCCESS) { + + /* Check error message */ + if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { + pj_stun_errcode_attr *eattr; + int err_code; + + eattr = (pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + + if (eattr != NULL) + err_code = eattr->err_code; + else + err_code = PJ_STUN_SC_SERVER_ERROR; + + status = PJ_STATUS_FROM_STUN_CODE(err_code); + + } else { + + /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ + mattr = (pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mattr == NULL) { + mattr = (pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); + } + + if (mattr == NULL) { + status = PJNATH_ESTUNNOMAPPEDADDR; + } + + /* Get CHANGED-ADDRESS attribute */ + ca = (pj_stun_changed_addr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); + + if (ca == NULL) { + status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); + } + } + } + + /* Save the result */ + tsx_id = (pj_uint32_t *)tdata->msg->hdr.tsx_id; + test_id = tsx_id[2]; + + if (test_id >= ST_MAX) { + PJ_LOG(4, (sess->pool->obj_name, "Invalid transaction ID %u in response", test_id)); + end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + goto on_return; + } + + PJ_PERROR(5, (sess->pool->obj_name, status, "Completed %s", test_names[test_id])); + + sess->result[test_id].complete = PJ_TRUE; + sess->result[test_id].status = status; + if (status == PJ_SUCCESS) { + pj_sockaddr_cp(&sess->result[test_id].ma, &mattr->sockaddr); + pj_sockaddr_cp(&sess->result[test_id].ca, &ca->sockaddr); + } + + /* Send Test 1B only when Test 2 completes. Must not send Test 1B + * before Test 2 completes to avoid creating mapping on the NAT. + */ + if (!sess->result[ST_TEST_1B].executed && sess->result[ST_TEST_2].complete && + sess->result[ST_TEST_2].status != PJ_SUCCESS && sess->result[ST_TEST_1].complete && + sess->result[ST_TEST_1].status == PJ_SUCCESS) { + cmp = pj_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma); + if (cmp != 0) + send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); + } + + if (test_completed(sess) < 3 || test_completed(sess) != test_executed(sess)) + goto on_return; + + /* Handle the test result according to RFC 3489 page 22: + + + +--------+ + | Test | + | 1 | + +--------+ + | + | + V + /\ /\ + N / \ Y / \ Y +--------+ + UDP <-------/Resp\--------->/ IP \------------->| Test | + Blocked \ ? / \Same/ | 2 | + \ / \? / +--------+ + \/ \/ | + | N | + | V + V /\ + +--------+ Sym. N / \ + | Test | UDP <---/Resp\ + | 2 | Firewall \ ? / + +--------+ \ / + | \/ + V |Y + /\ /\ | + Symmetric N / \ +--------+ N / \ V + NAT <--- / IP \<-----| Test |<--- /Resp\ Open + \Same/ | 1B | \ ? / Internet + \? / +--------+ \ / + \/ \/ + | |Y + | | + | V + | Full + | Cone + V /\ + +--------+ / \ Y + | Test |------>/Resp\---->Restricted + | 3 | \ ? / + +--------+ \ / + \/ + |N + | Port + +------>Restricted + + Figure 2: Flow for type discovery process + */ + + switch (sess->result[ST_TEST_1].status) { + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); + break; + case PJ_SUCCESS: + /* + * Test 1 is successful. Further tests are needed to detect + * NAT type. Compare the MAPPED-ADDRESS with the local address. + */ + cmp = pj_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma); + if (cmp == 0) { + /* + * MAPPED-ADDRESS and local address is equal. Need one more + * test to determine NAT type. + */ + switch (sess->result[ST_TEST_2].status) { + case PJ_SUCCESS: + /* + * Test 2 is also successful. We're in the open. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 2 has timed out. We're behind somekind of UDP + * firewall. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); + break; + default: + /* + * We've got other error with Test 2. + */ + end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } else { + /* + * MAPPED-ADDRESS is different than local address. + * We're behind NAT. + */ + switch (sess->result[ST_TEST_2].status) { + case PJ_SUCCESS: + /* + * Test 2 is successful. We're behind a full-cone NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 2 has timed-out Check result of test 1B.. + */ + switch (sess->result[ST_TEST_1B].status) { + case PJ_SUCCESS: + /* + * Compare the MAPPED-ADDRESS of test 1B with the + * MAPPED-ADDRESS returned in test 1.. + */ + cmp = pj_sockaddr_cmp(&sess->result[ST_TEST_1].ma, &sess->result[ST_TEST_1B].ma); + if (cmp != 0) { + /* + * MAPPED-ADDRESS is different, we're behind a + * symmetric NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC); + } else { + /* + * MAPPED-ADDRESS is equal. We're behind a restricted + * or port-restricted NAT, depending on the result of + * test 3. + */ + switch (sess->result[ST_TEST_3].status) { + case PJ_SUCCESS: + /* + * Test 3 is successful, we're behind a restricted + * NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 3 failed, we're behind a port restricted + * NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_PORT_RESTRICTED); + break; + default: + /* + * Got other error with test 3. + */ + end_session(sess, sess->result[ST_TEST_3].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Strangely test 1B has failed. Maybe connectivity was + * lost? Or perhaps port 3489 (the usual port number in + * CHANGED-ADDRESS) is blocked? + */ + switch (sess->result[ST_TEST_3].status) { + case PJ_SUCCESS: + /* Although test 1B failed, test 3 was successful. + * It could be that port 3489 is blocked, while the + * NAT itself looks to be a Restricted one. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); + break; + default: + /* Can't distinguish between Symmetric and Port + * Restricted, so set the type to Unknown + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + break; + default: + /* + * Got other error with test 1B. + */ + end_session(sess, sess->result[ST_TEST_1B].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + break; + default: + /* + * We've got other error with Test 2. + */ + end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } + break; + default: + /* + * We've got other error with Test 1. + */ + end_session(sess, sess->result[ST_TEST_1].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + +on_return: + pj_grp_lock_release(sess->grp_lock); +} + +/* Perform test */ +static pj_status_t send_test(nat_detect_session *sess, enum test_type test_id, const pj_sockaddr *alt_addr, + pj_uint32_t change_flag) +{ + pj_uint32_t magic, tsx_id[3]; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_status_t status; + + sess->result[test_id].executed = PJ_TRUE; + + /* Randomize tsx id */ + do { + magic = pj_rand(); + } while (magic == PJ_STUN_MAGIC); + + tsx_id[0] = pj_rand(); + tsx_id[1] = pj_rand(); + tsx_id[2] = test_id; + + /* Create BIND request */ + status = pj_stun_session_create_req(sess->stun_sess, PJ_STUN_BINDING_REQUEST, magic, (pj_uint8_t *)tsx_id, + &sess->result[test_id].tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add CHANGE-REQUEST attribute */ + status = pj_stun_msg_add_uint_attr(sess->pool, sess->result[test_id].tdata->msg, PJ_STUN_ATTR_CHANGE_REQUEST, + change_flag); + if (status != PJ_SUCCESS) + goto on_error; + + /* Configure alternate address, synthesize it if necessary */ + if (alt_addr) { + status = pj_sockaddr_synthesize(sess->server.addr.sa_family, &sess->cur_addr, alt_addr); + if (status != PJ_SUCCESS) + goto on_error; + + sess->cur_server = &sess->cur_addr; + } else { + sess->cur_server = &sess->server; + } + + PJ_LOG(5, (sess->pool->obj_name, "Performing %s to %s:%d", test_names[test_id], + pj_sockaddr_print(sess->cur_server, addr, sizeof(addr), 2), pj_sockaddr_get_port(sess->cur_server))); + + /* Send the request */ + status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE, PJ_TRUE, sess->cur_server, + pj_sockaddr_get_len(sess->cur_server), sess->result[test_id].tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + sess->result[test_id].complete = PJ_TRUE; + sess->result[test_id].status = status; + + return status; +} + +/* Timer callback */ +static void on_sess_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + nat_detect_session *sess; + + sess = (nat_detect_session *)te->user_data; + + if (te->id == TIMER_DESTROY) { + pj_grp_lock_acquire(sess->grp_lock); + pj_ioqueue_unregister(sess->key); + sess->key = NULL; + sess->sock = PJ_INVALID_SOCKET; + te->id = 0; + pj_grp_lock_release(sess->grp_lock); + + sess_destroy(sess); + + } else if (te->id == TIMER_TEST) { + + pj_bool_t next_timer; + + pj_grp_lock_acquire(sess->grp_lock); + + next_timer = PJ_FALSE; + + if (sess->timer_executed == 0) { + send_test(sess, ST_TEST_1, NULL, 0); + next_timer = PJ_TRUE; + } else if (sess->timer_executed == 1) { + send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG); + next_timer = PJ_TRUE; + } else if (sess->timer_executed == 2) { + send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG); + } else { + pj_assert(!"Shouldn't have timer at this state"); + } + + ++sess->timer_executed; + + if (next_timer) { + pj_time_val delay = {0, TEST_INTERVAL}; + pj_timer_heap_schedule(th, te, &delay); + } else { + te->id = 0; + } + + pj_grp_lock_release(sess->grp_lock); + + } else { + pj_assert(!"Invalid timer ID"); + } +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c new file mode 100755 index 000000000..382ccfef9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_auth.c" + +/* Duplicate credential */ +PJ_DEF(void) pj_stun_auth_cred_dup(pj_pool_t *pool, pj_stun_auth_cred *dst, const pj_stun_auth_cred *src) +{ + dst->type = src->type; + + switch (src->type) { + case PJ_STUN_AUTH_CRED_STATIC: + pj_strdup(pool, &dst->data.static_cred.realm, &src->data.static_cred.realm); + pj_strdup(pool, &dst->data.static_cred.username, &src->data.static_cred.username); + dst->data.static_cred.data_type = src->data.static_cred.data_type; + pj_strdup(pool, &dst->data.static_cred.data, &src->data.static_cred.data); + pj_strdup(pool, &dst->data.static_cred.nonce, &src->data.static_cred.nonce); + break; + case PJ_STUN_AUTH_CRED_DYNAMIC: + pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred, sizeof(src->data.dyn_cred)); + break; + } +} + +/* + * Duplicate request credential. + */ +PJ_DEF(void) pj_stun_req_cred_info_dup(pj_pool_t *pool, pj_stun_req_cred_info *dst, const pj_stun_req_cred_info *src) +{ + pj_strdup(pool, &dst->realm, &src->realm); + pj_strdup(pool, &dst->username, &src->username); + pj_strdup(pool, &dst->nonce, &src->nonce); + pj_strdup(pool, &dst->auth_key, &src->auth_key); +} + +/* Calculate HMAC-SHA1 key for long term credential, by getting + * MD5 digest of username, realm, and password. + */ +static void calc_md5_key(pj_uint8_t digest[16], const pj_str_t *realm, const pj_str_t *username, const pj_str_t *passwd) +{ + /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking + * the MD5 hash of the result of concatenating the following five + * fields: (1) The username, with any quotes and trailing nulls + * removed, (2) A single colon, (3) The realm, with any quotes and + * trailing nulls removed, (4) A single colon, and (5) The + * password, with any trailing nulls removed. + */ + pj_md5_context ctx; + pj_str_t s; + + pj_md5_init(&ctx); + +#define REMOVE_QUOTE(s) \ + if (s.slen && *s.ptr == '"') \ + s.ptr++, s.slen--; \ + if (s.slen && s.ptr[s.slen - 1] == '"') \ + s.slen--; + + /* Add username */ + s = *username; + REMOVE_QUOTE(s); + pj_md5_update(&ctx, (pj_uint8_t *)s.ptr, (unsigned)s.slen); + + /* Add single colon */ + pj_md5_update(&ctx, (pj_uint8_t *)":", 1); + + /* Add realm */ + s = *realm; + REMOVE_QUOTE(s); + pj_md5_update(&ctx, (pj_uint8_t *)s.ptr, (unsigned)s.slen); + +#undef REMOVE_QUOTE + + /* Another colon */ + pj_md5_update(&ctx, (pj_uint8_t *)":", 1); + + /* Add password */ + pj_md5_update(&ctx, (pj_uint8_t *)passwd->ptr, (unsigned)passwd->slen); + + /* Done */ + pj_md5_final(&ctx, digest); +} + +/* + * Create authentication key to be used for encoding the message with + * MESSAGE-INTEGRITY. + */ +PJ_DEF(void) +pj_stun_create_key(pj_pool_t *pool, pj_str_t *key, const pj_str_t *realm, const pj_str_t *username, + pj_stun_passwd_type data_type, const pj_str_t *data) +{ + PJ_ASSERT_ON_FAIL(pool && key && username && data, return ); + + if (realm && realm->slen) { + if (data_type == PJ_STUN_PASSWD_PLAIN) { + key->ptr = (char *)pj_pool_alloc(pool, 16); + calc_md5_key((pj_uint8_t *)key->ptr, realm, username, data); + key->slen = 16; + } else { + pj_strdup(pool, key, data); + } + } else { + pj_assert(data_type == PJ_STUN_PASSWD_PLAIN); + pj_strdup(pool, key, data); + } +} + +/*unused PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos) +{ + return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]); +}*/ + +PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval) +{ + buf[pos + 0] = (pj_uint8_t)((hval & 0xFF00) >> 8); + buf[pos + 1] = (pj_uint8_t)((hval & 0x00FF) >> 0); +} + +/* Send 401 response */ +static pj_status_t create_challenge(pj_pool_t *pool, const pj_stun_msg *msg, int err_code, const char *errstr, + const pj_str_t *realm, const pj_str_t *nonce, pj_stun_msg **p_response) +{ + pj_stun_msg *response; + pj_str_t tmp_nonce; + pj_str_t err_msg; + pj_status_t rc; + + rc = pj_stun_msg_create_response(pool, msg, err_code, (errstr ? pj_cstr(&err_msg, errstr) : NULL), &response); + if (rc != PJ_SUCCESS) + return rc; + + /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */ + if (err_code != 400 && realm && realm->slen) { + rc = pj_stun_msg_add_string_attr(pool, response, PJ_STUN_ATTR_REALM, realm); + if (rc != PJ_SUCCESS) + return rc; + + /* long term must include nonce */ + if (!nonce || nonce->slen == 0) { + tmp_nonce = pj_str("pjstun"); + nonce = &tmp_nonce; + } + } + + if (err_code != 400 && nonce && nonce->slen) { + rc = pj_stun_msg_add_string_attr(pool, response, PJ_STUN_ATTR_NONCE, nonce); + if (rc != PJ_SUCCESS) + return rc; + } + + *p_response = response; + + return PJ_SUCCESS; +} + +/* Verify credential in the request */ +PJ_DEF(pj_status_t) +pj_stun_authenticate_request(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, pj_stun_auth_cred *cred, + pj_pool_t *pool, pj_stun_req_cred_info *p_info, pj_stun_msg **p_response) +{ + pj_stun_req_cred_info tmp_info; + const pj_stun_msgint_attr *amsgi; + unsigned i, amsgi_pos; + pj_bool_t has_attr_beyond_mi; + const pj_stun_username_attr *auser; + const pj_stun_realm_attr *arealm; + const pj_stun_realm_attr *anonce; + pj_hmac_sha1_context ctx; + pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]; + pj_stun_status err_code; + const char *err_text = NULL; + pj_status_t status; + + /* msg and credential MUST be specified */ + PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL); + + /* If p_response is specified, pool MUST be specified. */ + PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL); + + if (p_response) + *p_response = NULL; + + if (!PJ_STUN_IS_REQUEST(msg->hdr.type)) + p_response = NULL; + + if (p_info == NULL) + p_info = &tmp_info; + + pj_bzero(p_info, sizeof(pj_stun_req_cred_info)); + + /* Get realm and nonce from credential */ + p_info->realm.slen = p_info->nonce.slen = 0; + if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { + p_info->realm = cred->data.static_cred.realm; + p_info->nonce = cred->data.static_cred.nonce; + } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data, pool, &p_info->realm, &p_info->nonce); + if (status != PJ_SUCCESS) + return status; + } else { + pj_assert(!"Invalid credential type"); + return PJ_EBUG; + } + + /* Look for MESSAGE-INTEGRITY while counting the position */ + amsgi_pos = 0; + has_attr_beyond_mi = PJ_FALSE; + amsgi = NULL; + for (i = 0; i < msg->attr_count; ++i) { + if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + amsgi = (const pj_stun_msgint_attr *)msg->attr[i]; + } else if (amsgi) { + has_attr_beyond_mi = PJ_TRUE; + break; + } else { + amsgi_pos += ((msg->attr[i]->length + 3) & ~0x03) + 4; + } + } + + if (amsgi == NULL) { + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400 + for short term, and 401 for long term. + The rule has been changed from rfc3489bis-06 + */ + err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED : PJ_STUN_SC_BAD_REQUEST; + goto on_auth_failed; + } + + /* Next check that USERNAME is present */ + auser = (const pj_stun_username_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0); + if (auser == NULL) { + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400 + for both short and long term, since M-I is present. + The rule has been changed from rfc3489bis-06 + */ + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing USERNAME"; + goto on_auth_failed; + } + + /* Get REALM, if any */ + arealm = (const pj_stun_realm_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0); + + /* Reject with 400 if we have long term credential and the request + * is missing REALM attribute. + */ + if (p_info->realm.slen && arealm == NULL) { + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing REALM"; + goto on_auth_failed; + } + + /* Check if username match */ + if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { + pj_bool_t username_ok; + username_ok = !pj_strcmp(&auser->value, &cred->data.static_cred.username); + if (username_ok) { + pj_strdup(pool, &p_info->username, &cred->data.static_cred.username); + pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm, &auser->value, cred->data.static_cred.data_type, + &cred->data.static_cred.data); + } else { + /* Username mismatch */ + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should + * return 401 + */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + goto on_auth_failed; + } + } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN; + pj_str_t password; + pj_status_t rc; + + rc = cred->data.dyn_cred.get_password(msg, cred->data.dyn_cred.user_data, (arealm ? &arealm->value : NULL), + &auser->value, pool, &data_type, &password); + if (rc == PJ_SUCCESS) { + pj_strdup(pool, &p_info->username, &auser->value); + pj_stun_create_key(pool, &p_info->auth_key, (arealm ? &arealm->value : NULL), &auser->value, data_type, + &password); + } else { + err_code = PJ_STUN_SC_UNAUTHORIZED; + goto on_auth_failed; + } + } else { + pj_assert(!"Invalid credential type"); + return PJ_EBUG; + } + + /* Get NONCE attribute */ + anonce = (pj_stun_nonce_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0); + + /* Check for long term/short term requirements. */ + if (p_info->realm.slen != 0 && arealm == NULL) { + /* Long term credential is required and REALM is not present */ + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing REALM"; + goto on_auth_failed; + + } else if (p_info->realm.slen != 0 && arealm != NULL) { + /* We want long term, and REALM is present */ + + /* NONCE must be present. */ + if (anonce == NULL && p_info->nonce.slen) { + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing NONCE"; + goto on_auth_failed; + } + + /* Verify REALM matches */ + if (pj_stricmp(&arealm->value, &p_info->realm)) { + /* REALM doesn't match */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "Invalid REALM"; + goto on_auth_failed; + } + + /* Valid case, will validate the message integrity later */ + + } else if (p_info->realm.slen == 0 && arealm != NULL) { + /* We want to use short term credential, but client uses long + * term credential. The draft doesn't mention anything about + * switching between long term and short term. + */ + + /* For now just accept the credential, anyway it will probably + * cause wrong message integrity value later. + */ + } else if (p_info->realm.slen == 0 && arealm == NULL) { + /* Short term authentication is wanted, and one is supplied */ + + /* Application MAY request NONCE to be supplied */ + if (p_info->nonce.slen != 0) { + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "NONCE required"; + goto on_auth_failed; + } + } + + /* If NONCE is present, validate it */ + if (anonce) { + pj_bool_t ok; + + if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC && cred->data.dyn_cred.verify_nonce != NULL) { + ok = cred->data.dyn_cred.verify_nonce(msg, cred->data.dyn_cred.user_data, (arealm ? &arealm->value : NULL), + &auser->value, &anonce->value); + } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + ok = PJ_TRUE; + } else { + if (p_info->nonce.slen) { + ok = !pj_strcmp(&anonce->value, &p_info->nonce); + } else { + ok = PJ_TRUE; + } + } + + if (!ok) { + err_code = PJ_STUN_SC_STALE_NONCE; + goto on_auth_failed; + } + } + + /* Now calculate HMAC of the message. */ + pj_hmac_sha1_init(&ctx, (pj_uint8_t *)p_info->auth_key.ptr, (unsigned)p_info->auth_key.slen); + +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* Pre rfc3489bis-06 style of calculation */ + pj_hmac_sha1_update(&ctx, pkt, 20); +#else + /* First calculate HMAC for the header. + * The calculation is different depending on whether FINGERPRINT attribute + * is present in the message. + */ + if (has_attr_beyond_mi) { + pj_uint8_t hdr_copy[20]; + pj_memcpy(hdr_copy, pkt, 20); + PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24)); + pj_hmac_sha1_update(&ctx, hdr_copy, 20); + } else { + pj_hmac_sha1_update(&ctx, pkt, 20); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + + /* Now update with the message body */ + pj_hmac_sha1_update(&ctx, pkt + 20, amsgi_pos); +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + // This is no longer necessary as per rfc3489bis-08 + if ((amsgi_pos + 20) & 0x3F) { + pj_uint8_t zeroes[64]; + pj_bzero(zeroes, sizeof(zeroes)); + pj_hmac_sha1_update(&ctx, zeroes, 64 - ((amsgi_pos + 20) & 0x3F)); + } +#endif + pj_hmac_sha1_final(&ctx, digest); + + /* Compare HMACs */ + if (pj_memcmp(amsgi->hmac, digest, 20)) { + /* HMAC value mismatch */ + /* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "MESSAGE-INTEGRITY mismatch"; + goto on_auth_failed; + } + + /* Everything looks okay! */ + return PJ_SUCCESS; + +on_auth_failed: + if (p_response) { + create_challenge(pool, msg, err_code, err_text, &p_info->realm, &p_info->nonce, p_response); + } + return PJ_STATUS_FROM_STUN_CODE(err_code); +} + +/* Determine if STUN message can be authenticated */ +PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg) +{ + unsigned msg_type = msg->hdr.type; + const pj_stun_errcode_attr *err_attr; + + /* STUN requests and success response can be authenticated */ + if (!PJ_STUN_IS_ERROR_RESPONSE(msg_type) && !PJ_STUN_IS_INDICATION(msg_type)) { + return PJ_TRUE; + } + + /* STUN Indication cannot be authenticated */ + if (PJ_STUN_IS_INDICATION(msg_type)) + return PJ_FALSE; + + /* Authentication for STUN error responses depend on the error + * code. + */ + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr == NULL) { + PJ_LOG(4, (THIS_FILE, "STUN error code attribute not present in " + "error response")); + return PJ_TRUE; + } + + switch (err_attr->err_code) { + case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */ + case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */ + case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */ + + /* Due to the way this response is generated here, we can't really + * authenticate 420 (Unknown Attribute) response */ + case PJ_STUN_SC_UNKNOWN_ATTRIBUTE: + return PJ_FALSE; + default: + return PJ_TRUE; + } +} + +/* Authenticate MESSAGE-INTEGRITY in the response */ +PJ_DEF(pj_status_t) +pj_stun_authenticate_response(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, const pj_str_t *key) +{ + const pj_stun_msgint_attr *amsgi; + unsigned i, amsgi_pos; + pj_bool_t has_attr_beyond_mi; + pj_hmac_sha1_context ctx; + pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]; + + PJ_ASSERT_RETURN(pkt && pkt_len && msg && key, PJ_EINVAL); + + /* First check that MESSAGE-INTEGRITY is present */ + amsgi = (const pj_stun_msgint_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0); + if (amsgi == NULL) { + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + } + + /* Check that message length is valid */ + if (msg->hdr.length < 24) { + return PJNATH_EINSTUNMSGLEN; + } + + /* Look for MESSAGE-INTEGRITY while counting the position */ + amsgi_pos = 0; + has_attr_beyond_mi = PJ_FALSE; + amsgi = NULL; + for (i = 0; i < msg->attr_count; ++i) { + if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + amsgi = (const pj_stun_msgint_attr *)msg->attr[i]; + } else if (amsgi) { + has_attr_beyond_mi = PJ_TRUE; + break; + } else { + amsgi_pos += ((msg->attr[i]->length + 3) & ~0x03) + 4; + } + } + + if (amsgi == NULL) { + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + } + + /* Now calculate HMAC of the message. */ + pj_hmac_sha1_init(&ctx, (pj_uint8_t *)key->ptr, (unsigned)key->slen); + +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* Pre rfc3489bis-06 style of calculation */ + pj_hmac_sha1_update(&ctx, pkt, 20); +#else + /* First calculate HMAC for the header. + * The calculation is different depending on whether FINGERPRINT attribute + * is present in the message. + */ + if (has_attr_beyond_mi) { + pj_uint8_t hdr_copy[20]; + pj_memcpy(hdr_copy, pkt, 20); + PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24)); + pj_hmac_sha1_update(&ctx, hdr_copy, 20); + } else { + pj_hmac_sha1_update(&ctx, pkt, 20); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + + /* Now update with the message body */ + pj_hmac_sha1_update(&ctx, pkt + 20, amsgi_pos); +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + // This is no longer necessary as per rfc3489bis-08 + if ((amsgi_pos + 20) & 0x3F) { + pj_uint8_t zeroes[64]; + pj_bzero(zeroes, sizeof(zeroes)); + pj_hmac_sha1_update(&ctx, zeroes, 64 - ((amsgi_pos + 20) & 0x3F)); + } +#endif + pj_hmac_sha1_final(&ctx, digest); + + /* Compare HMACs */ + if (pj_memcmp(amsgi->hmac, digest, 20)) { + /* HMAC value mismatch */ + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + } + + /* Everything looks okay! */ + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c new file mode 100755 index 000000000..c72ae0af7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c @@ -0,0 +1,2253 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_msg.c" +#define STUN_XOR_FINGERPRINT 0x5354554eL + +static int padding_char; + +static const char *stun_method_names[PJ_STUN_METHOD_MAX] = { + "Unknown", /* 0 */ + "Binding", /* 1 */ + "SharedSecret", /* 2 */ + "Allocate", /* 3 */ + "Refresh", /* 4 */ + "???", /* 5 */ + "Send", /* 6 */ + "Data", /* 7 */ + "CreatePermission", /* 8 */ + "ChannelBind", /* 9 */ + "Connect", /* 10 */ + "ConnectionBind", /* 11 */ + "ConnectionAttempt", /* 12 */ +}; + +static struct { + int err_code; + const char *err_msg; +} stun_err_msg_map[] = {{PJ_STUN_SC_TRY_ALTERNATE, "Try Alternate"}, + {PJ_STUN_SC_BAD_REQUEST, "Bad Request"}, + {PJ_STUN_SC_UNAUTHORIZED, "Unauthorized"}, + {PJ_STUN_SC_FORBIDDEN, "Forbidden"}, + {PJ_STUN_SC_UNKNOWN_ATTRIBUTE, "Unknown Attribute"}, + //{ PJ_STUN_SC_STALE_CREDENTIALS, "Stale Credentials"}, + //{ PJ_STUN_SC_INTEGRITY_CHECK_FAILURE, "Integrity Check Failure"}, + //{ PJ_STUN_SC_MISSING_USERNAME, "Missing Username"}, + //{ PJ_STUN_SC_USE_TLS, "Use TLS"}, + //{ PJ_STUN_SC_MISSING_REALM, "Missing Realm"}, + //{ PJ_STUN_SC_MISSING_NONCE, "Missing Nonce"}, + //{ PJ_STUN_SC_UNKNOWN_USERNAME, "Unknown Username"}, + {PJ_STUN_SC_ALLOCATION_MISMATCH, "Allocation Mismatch"}, + {PJ_STUN_SC_STALE_NONCE, "Stale Nonce"}, + {PJ_STUN_SC_TRANSITIONING, "Active Destination Already Set"}, + {PJ_STUN_SC_WRONG_CREDENTIALS, "Wrong Credentials"}, + {PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, "Unsupported Transport Protocol"}, + {PJ_STUN_SC_OPER_TCP_ONLY, "Operation for TCP Only"}, + {PJ_STUN_SC_CONNECTION_FAILURE, "Connection Failure"}, + {PJ_STUN_SC_CONNECTION_TIMEOUT, "Connection Timeout"}, + {PJ_STUN_SC_ALLOCATION_QUOTA_REACHED, "Allocation Quota Reached"}, + {PJ_STUN_SC_ROLE_CONFLICT, "Role Conflict"}, + {PJ_STUN_SC_SERVER_ERROR, "Server Error"}, + {PJ_STUN_SC_INSUFFICIENT_CAPACITY, "Insufficient Capacity"}, + {PJ_STUN_SC_GLOBAL_FAILURE, "Global Failure"}}; + +struct attr_desc { + const char *name; + pj_status_t (*decode_attr)(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, void **p_attr); + pj_status_t (*encode_attr)(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); + void *(*clone_attr)(pj_pool_t *pool, const void *src); +}; + +static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_sockaddr_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_string_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_string_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_msgint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_msgint_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_errcode_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_errcode_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_unknown_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_unknown_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_uint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_uint_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_uint64_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_uint64_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_binary_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_binary_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_empty_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_empty_attr(pj_pool_t *pool, const void *src); + +static struct attr_desc mandatory_attr_desc[] = { + {/* type zero */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_MAPPED_ADDR, */ + "MAPPED-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_RESPONSE_ADDR, */ + "RESPONSE-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_CHANGE_REQUEST, */ + "CHANGE-REQUEST", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_SOURCE_ADDR, */ + "SOURCE-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_CHANGED_ADDR, */ + "CHANGED-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_USERNAME, */ + "USERNAME", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_PASSWORD, */ + "PASSWORD", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_MESSAGE_INTEGRITY, */ + "MESSAGE-INTEGRITY", &decode_msgint_attr, &encode_msgint_attr, &clone_msgint_attr}, + {/* PJ_STUN_ATTR_ERROR_CODE, */ + "ERROR-CODE", &decode_errcode_attr, &encode_errcode_attr, &clone_errcode_attr}, + {/* PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, */ + "UNKNOWN-ATTRIBUTES", &decode_unknown_attr, &encode_unknown_attr, &clone_unknown_attr}, + {/* PJ_STUN_ATTR_REFLECTED_FROM, */ + "REFLECTED-FROM", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_CHANNEL_NUMBER (0x000C) */ + "CHANNEL-NUMBER", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_LIFETIME, */ + "LIFETIME", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x000E is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_MAGIC_COOKIE */ + "MAGIC-COOKIE", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_BANDWIDTH, */ + "BANDWIDTH", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x0011 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_XOR_PEER_ADDRESS, */ + "XOR-PEER-ADDRESS", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_DATA, */ + "DATA", &decode_binary_attr, &encode_binary_attr, &clone_binary_attr}, + {/* PJ_STUN_ATTR_REALM, */ + "REALM", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_NONCE, */ + "NONCE", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_XOR_RELAYED_ADDR, */ + "XOR-RELAYED-ADDRESS", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_REQUESTED_ADDR_FAMILY, */ + "REQUESTED-ADDRESS-FAMILY", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_EVEN_PORT, */ + "EVEN-PORT", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_REQUESTED_TRANSPORT, */ + "REQUESTED-TRANSPORT", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_DONT_FRAGMENT */ + "DONT-FRAGMENT", &decode_empty_attr, &encode_empty_attr, &clone_empty_attr}, + {/* ID 0x001B is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001C is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001D is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001E is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001F is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_XOR_MAPPED_ADDRESS, */ + "XOR-MAPPED-ADDRESS", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_TIMER_VAL, */ + "TIMER-VAL", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_RESERVATION_TOKEN, */ + "RESERVATION-TOKEN", &decode_uint64_attr, &encode_uint64_attr, &clone_uint64_attr}, + {/* PJ_STUN_ATTR_XOR_REFLECTED_FROM, */ + "XOR-REFLECTED-FROM", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_PRIORITY, */ + "PRIORITY", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_USE_CANDIDATE, */ + "USE-CANDIDATE", &decode_empty_attr, &encode_empty_attr, &clone_empty_attr}, + {/* ID 0x0026 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x0027 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x0028 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x0029 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_CONNECTION_ID, */ + "CONNECTION-ID", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x002b is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002c is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002d is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002e is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002f is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_ICMP, */ + "ICMP", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + + /* Sentinel */ + {/* PJ_STUN_ATTR_END_MANDATORY_ATTR */ + NULL, NULL, NULL, NULL}}; + +static struct attr_desc extended_attr_desc[] = { + {/* ID 0x8021 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_SOFTWARE, */ + "SOFTWARE", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_ALTERNATE_SERVER, */ + "ALTERNATE-SERVER", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_REFRESH_INTERVAL, */ + "REFRESH-INTERVAL", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x8025 is not assigned*/ + NULL, NULL, NULL, NULL}, + {/* PADDING, 0x8026 */ + NULL, NULL, NULL, NULL}, + {/* CACHE-TIMEOUT, 0x8027 */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_FINGERPRINT, */ + "FINGERPRINT", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_ICE_CONTROLLED, */ + "ICE-CONTROLLED", &decode_uint64_attr, &encode_uint64_attr, &clone_uint64_attr}, + {/* PJ_STUN_ATTR_ICE_CONTROLLING, */ + "ICE-CONTROLLING", &decode_uint64_attr, &encode_uint64_attr, &clone_uint64_attr}}; + +/* + * Get STUN message type name. + */ +PJ_DEF(const char *) pj_stun_get_method_name(unsigned msg_type) +{ + unsigned method = PJ_STUN_GET_METHOD(msg_type); + + if (method >= PJ_ARRAY_SIZE(stun_method_names)) + return "???"; + + return stun_method_names[method]; +} + +/* + * Get STUN message class name. + */ +PJ_DEF(const char *) pj_stun_get_class_name(unsigned msg_type) +{ + if (PJ_STUN_IS_REQUEST(msg_type)) + return "request"; + else if (PJ_STUN_IS_SUCCESS_RESPONSE(msg_type)) + return "success response"; + else if (PJ_STUN_IS_ERROR_RESPONSE(msg_type)) + return "error response"; + else if (PJ_STUN_IS_INDICATION(msg_type)) + return "indication"; + else + return "???"; +} + +static const struct attr_desc *find_attr_desc(unsigned attr_type) +{ + struct attr_desc *desc; + + /* Check that attr_desc array is valid */ + pj_assert(PJ_ARRAY_SIZE(mandatory_attr_desc) == PJ_STUN_ATTR_END_MANDATORY_ATTR + 1); + pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_END_MANDATORY_ATTR].decode_attr == NULL); + pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_USE_CANDIDATE].decode_attr == &decode_empty_attr); + pj_assert(PJ_ARRAY_SIZE(extended_attr_desc) == PJ_STUN_ATTR_END_EXTENDED_ATTR - PJ_STUN_ATTR_START_EXTENDED_ATTR); + + if (attr_type < PJ_STUN_ATTR_END_MANDATORY_ATTR) + desc = &mandatory_attr_desc[attr_type]; + else if (attr_type >= PJ_STUN_ATTR_START_EXTENDED_ATTR && attr_type < PJ_STUN_ATTR_END_EXTENDED_ATTR) + desc = &extended_attr_desc[attr_type - PJ_STUN_ATTR_START_EXTENDED_ATTR]; + else + return NULL; + + return desc->decode_attr == NULL ? NULL : desc; +} + +/* + * Get STUN attribute name. + */ +PJ_DEF(const char *) pj_stun_get_attr_name(unsigned attr_type) +{ + const struct attr_desc *attr_desc; + + attr_desc = find_attr_desc(attr_type); + if (!attr_desc || attr_desc->name == NULL) + return "???"; + + return attr_desc->name; +} + +/** + * Get STUN standard reason phrase for the specified error code. + */ +PJ_DEF(pj_str_t) pj_stun_get_err_reason(int err_code) +{ +#if 0 + /* Find error using linear search */ + unsigned i; + + for (i=0; i 0) { + int half = n / 2; + int mid = first + half; + + if (stun_err_msg_map[mid].err_code < err_code) { + first = mid + 1; + n -= (half + 1); + } else if (stun_err_msg_map[mid].err_code > err_code) { + n = half; + } else { + first = mid; + break; + } + } + + if (stun_err_msg_map[first].err_code == err_code) { + return pj_str((char *)stun_err_msg_map[first].err_msg); + } else { + return pj_str(NULL); + } +#endif +} + +/* + * Set padding character. + */ +PJ_DEF(int) pj_stun_set_padding_char(int chr) +{ + int old_pad = padding_char; + padding_char = chr; + return old_pad; +} + +////////////////////////////////////////////////////////////////////////////// + +#define INIT_ATTR(a, t, l) (a)->hdr.type = (pj_uint16_t)(t), (a)->hdr.length = (pj_uint16_t)(l) +#define ATTR_HDR_LEN sizeof(pj_stun_attr_hdr) + +static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos) +{ + return (pj_uint16_t)((buf[pos + 0] << 8) | (buf[pos + 1] << 0)); +} + +/*unused PJ_INLINE(pj_uint16_t) GETVAL16N(const pj_uint8_t *buf, unsigned pos) +{ + return pj_htons(GETVAL16H(buf,pos)); +}*/ + +static void PUTVAL16H(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval) +{ + buf[pos + 0] = (pj_uint8_t)((hval & 0xFF00) >> 8); + buf[pos + 1] = (pj_uint8_t)((hval & 0x00FF) >> 0); +} + +PJ_INLINE(pj_uint32_t) GETVAL32H(const pj_uint8_t *buf, unsigned pos) +{ + return (pj_uint32_t)((buf[pos + 0] << 24UL) | (buf[pos + 1] << 16UL) | (buf[pos + 2] << 8UL) | + (buf[pos + 3] << 0UL)); +} + +/*unused PJ_INLINE(pj_uint32_t) GETVAL32N(const pj_uint8_t *buf, unsigned pos) +{ + return pj_htonl(GETVAL32H(buf,pos)); +}*/ + +static void PUTVAL32H(pj_uint8_t *buf, unsigned pos, pj_uint32_t hval) +{ + buf[pos + 0] = (pj_uint8_t)((hval & 0xFF000000UL) >> 24); + buf[pos + 1] = (pj_uint8_t)((hval & 0x00FF0000UL) >> 16); + buf[pos + 2] = (pj_uint8_t)((hval & 0x0000FF00UL) >> 8); + buf[pos + 3] = (pj_uint8_t)((hval & 0x000000FFUL) >> 0); +} + +static void GETVAL64H(const pj_uint8_t *buf, unsigned pos, pj_timestamp *ts) +{ + ts->u32.hi = GETVAL32H(buf, pos); + ts->u32.lo = GETVAL32H(buf, pos + 4); +} + +static void PUTVAL64H(pj_uint8_t *buf, unsigned pos, const pj_timestamp *ts) +{ + PUTVAL32H(buf, pos, ts->u32.hi); + PUTVAL32H(buf, pos + 4, ts->u32.lo); +} + +static void GETATTRHDR(const pj_uint8_t *buf, pj_stun_attr_hdr *hdr) +{ + hdr->type = GETVAL16H(buf, 0); + hdr->length = GETVAL16H(buf, 2); +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic IP address container + */ +#define STUN_GENERIC_IPV4_ADDR_LEN 8 +#define STUN_GENERIC_IPV6_ADDR_LEN 20 + +/* + * Init sockaddr attr + */ +PJ_DEF(pj_status_t) +pj_stun_sockaddr_attr_init(pj_stun_sockaddr_attr *attr, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len) +{ + unsigned attr_len; + + PJ_ASSERT_RETURN(attr && addr_len && addr, PJ_EINVAL); + PJ_ASSERT_RETURN(addr_len == sizeof(pj_sockaddr_in) || addr_len == sizeof(pj_sockaddr_in6), PJ_EINVAL); + + attr_len = pj_sockaddr_get_addr_len(addr) + 4; + INIT_ATTR(attr, attr_type, attr_len); + + pj_memcpy(&attr->sockaddr, addr, addr_len); + attr->xor_ed = xor_ed; + + return PJ_SUCCESS; +} + +/* + * Create a generic STUN IP address attribute for IPv4 address. + */ +PJ_DEF(pj_status_t) +pj_stun_sockaddr_attr_create(pj_pool_t *pool, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len, pj_stun_sockaddr_attr **p_attr) +{ + pj_stun_sockaddr_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr); + *p_attr = attr; + return pj_stun_sockaddr_attr_init(attr, attr_type, xor_ed, addr, addr_len); +} + +/* + * Create and add generic STUN IP address attribute to a STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_bool_t xor_ed, + const pj_sockaddr_t *addr, unsigned addr_len) +{ + pj_stun_sockaddr_attr *attr; + pj_status_t status; + + status = pj_stun_sockaddr_attr_create(pool, attr_type, xor_ed, addr, addr_len, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_sockaddr_attr *attr; + int af; + unsigned addr_len; + pj_uint32_t val; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != STUN_GENERIC_IPV4_ADDR_LEN && attr->hdr.length != STUN_GENERIC_IPV6_ADDR_LEN) { + return PJNATH_ESTUNINATTRLEN; + } + + /* Check address family */ + val = *(pj_uint8_t *)(buf + ATTR_HDR_LEN + 1); + + /* Check address family is valid */ + if (val == 1) { + if (attr->hdr.length != STUN_GENERIC_IPV4_ADDR_LEN) + return PJNATH_ESTUNINATTRLEN; + af = pj_AF_INET(); + addr_len = 4; + } else if (val == 2) { + if (attr->hdr.length != STUN_GENERIC_IPV6_ADDR_LEN) + return PJNATH_ESTUNINATTRLEN; + af = pj_AF_INET6(); + addr_len = 16; + } else { + /* Invalid address family */ + return PJNATH_EINVAF; + } + + /* Get port and address */ + pj_sockaddr_init(af, &attr->sockaddr, NULL, 0); + pj_sockaddr_set_port(&attr->sockaddr, GETVAL16H(buf, ATTR_HDR_LEN + 2)); + pj_memcpy(pj_sockaddr_get_addr(&attr->sockaddr), buf + ATTR_HDR_LEN + 4, addr_len); + + /* Done */ + *p_attr = (void *)attr; + + return PJ_SUCCESS; +} + +static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_sockaddr_attr *attr; + pj_status_t status; + + status = decode_sockaddr_attr(pool, buf, msghdr, p_attr); + if (status != PJ_SUCCESS) + return status; + + attr = *(pj_stun_sockaddr_attr **)p_attr; + + attr->xor_ed = PJ_TRUE; + + if (attr->sockaddr.addr.sa_family == pj_AF_INET()) { + attr->sockaddr.ipv4.sin_port ^= pj_htons(PJ_STUN_MAGIC >> 16); + attr->sockaddr.ipv4.sin_addr.s_addr ^= pj_htonl(PJ_STUN_MAGIC); + } else if (attr->sockaddr.addr.sa_family == pj_AF_INET6()) { + unsigned i; + pj_uint8_t *dst = (pj_uint8_t *)&attr->sockaddr.ipv6.sin6_addr; + pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC); + + attr->sockaddr.ipv6.sin6_port ^= pj_htons(PJ_STUN_MAGIC >> 16); + + /* If the IP address family is IPv6, X-Address is computed by + * taking the mapped IP address in host byte order, XOR'ing it + * with the concatenation of the magic cookie and the 96-bit + * transaction ID, and converting the result to network byte + * order. + */ + for (i = 0; i < 4; ++i) { + dst[i] ^= ((const pj_uint8_t *)&magic)[i]; + } + pj_assert(sizeof(msghdr->tsx_id[0]) == 1); + for (i = 0; i < 12; ++i) { + dst[i + 4] ^= msghdr->tsx_id[i]; + } + + } else { + return PJNATH_EINVAF; + } + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + pj_uint8_t *start_buf = buf; + const pj_stun_sockaddr_attr *ca = (const pj_stun_sockaddr_attr *)a; + + PJ_CHECK_STACK(); + + /* Common: attribute type */ + PUTVAL16H(buf, 0, ca->hdr.type); + + if (ca->sockaddr.addr.sa_family == pj_AF_INET()) { + enum { ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IPV4_ADDR_LEN }; + + if (len < ATTR_LEN) + return PJ_ETOOSMALL; + + /* attribute len */ + PUTVAL16H(buf, 2, STUN_GENERIC_IPV4_ADDR_LEN); + buf += ATTR_HDR_LEN; + + /* Ignored */ + *buf++ = '\0'; + + /* Address family, 1 for IPv4 */ + *buf++ = 1; + + /* IPv4 address */ + if (ca->xor_ed) { + pj_uint32_t addr; + pj_uint16_t port; + + addr = ca->sockaddr.ipv4.sin_addr.s_addr; + port = ca->sockaddr.ipv4.sin_port; + + port ^= pj_htons(PJ_STUN_MAGIC >> 16); + addr ^= pj_htonl(PJ_STUN_MAGIC); + + /* Port */ + pj_memcpy(buf, &port, 2); + buf += 2; + + /* Address */ + pj_memcpy(buf, &addr, 4); + buf += 4; + + } else { + /* Port */ + pj_memcpy(buf, &ca->sockaddr.ipv4.sin_port, 2); + buf += 2; + + /* Address */ + pj_memcpy(buf, &ca->sockaddr.ipv4.sin_addr, 4); + buf += 4; + } + + pj_assert(buf - start_buf == ATTR_LEN); + + } else if (ca->sockaddr.addr.sa_family == pj_AF_INET6()) { + /* IPv6 address */ + enum { ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IPV6_ADDR_LEN }; + + if (len < ATTR_LEN) + return PJ_ETOOSMALL; + + /* attribute len */ + PUTVAL16H(buf, 2, STUN_GENERIC_IPV6_ADDR_LEN); + buf += ATTR_HDR_LEN; + + /* Ignored */ + *buf++ = '\0'; + + /* Address family, 2 for IPv6 */ + *buf++ = 2; + + /* IPv6 address */ + if (ca->xor_ed) { + unsigned i; + pj_uint8_t *dst; + const pj_uint8_t *src; + pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC); + pj_uint16_t port = ca->sockaddr.ipv6.sin6_port; + + /* Port */ + port ^= pj_htons(PJ_STUN_MAGIC >> 16); + pj_memcpy(buf, &port, 2); + buf += 2; + + /* Address */ + dst = buf; + src = (const pj_uint8_t *)&ca->sockaddr.ipv6.sin6_addr; + for (i = 0; i < 4; ++i) { + dst[i] = (pj_uint8_t)(src[i] ^ ((const pj_uint8_t *)&magic)[i]); + } + pj_assert(sizeof(msghdr->tsx_id[0]) == 1); + for (i = 0; i < 12; ++i) { + dst[i + 4] = (pj_uint8_t)(src[i + 4] ^ msghdr->tsx_id[i]); + } + + buf += 16; + + } else { + /* Port */ + pj_memcpy(buf, &ca->sockaddr.ipv6.sin6_port, 2); + buf += 2; + + /* Address */ + pj_memcpy(buf, &ca->sockaddr.ipv6.sin6_addr, 16); + buf += 16; + } + + pj_assert(buf - start_buf == ATTR_LEN); + + } else { + return PJNATH_EINVAF; + } + + /* Done */ + *printed = (unsigned)(buf - start_buf); + + return PJ_SUCCESS; +} + +static void *clone_sockaddr_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_sockaddr_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_sockaddr_attr); + pj_memcpy(dst, src, sizeof(pj_stun_sockaddr_attr)); + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic string attribute + */ + +/* + * Initialize a STUN generic string attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_string_attr_init(pj_stun_string_attr *attr, pj_pool_t *pool, int attr_type, const pj_str_t *value) +{ + if (value && value->slen) { + INIT_ATTR(attr, attr_type, value->slen); + attr->value.slen = value->slen; + pj_strdup(pool, &attr->value, value); + } else { + INIT_ATTR(attr, attr_type, 0); + } + return PJ_SUCCESS; +} + +/* + * Create a STUN generic string attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_string_attr_create(pj_pool_t *pool, int attr_type, const pj_str_t *value, pj_stun_string_attr **p_attr) +{ + pj_stun_string_attr *attr; + + PJ_ASSERT_RETURN(pool && value && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr); + *p_attr = attr; + + return pj_stun_string_attr_init(attr, pool, attr_type, value); +} + +/* + * Create and add STUN generic string attribute to the message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_string_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_str_t *value) +{ + pj_stun_string_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_string_attr_create(pool, attr_type, value, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_string_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_string_attr *attr; + pj_str_t value; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Get pointer to the string in the message */ + value.ptr = ((char *)buf + ATTR_HDR_LEN); + value.slen = attr->hdr.length; + + /* Copy the string to the attribute */ + pj_strdup(pool, &attr->value, &value); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_string_attr *ca = (const pj_stun_string_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Calculated total attr_len (add padding if necessary) */ + *printed = ((unsigned)ca->value.slen + ATTR_HDR_LEN + 3) & (~3); + if (len < *printed) { + *printed = 0; + return PJ_ETOOSMALL; + } + + PUTVAL16H(buf, 0, ca->hdr.type); + + /* Special treatment for SOFTWARE attribute: + * This attribute had caused interop problem when talking to + * legacy RFC 3489 STUN servers, due to different "length" + * rules with RFC 5389. + */ + if (msghdr->magic != PJ_STUN_MAGIC || ca->hdr.type == PJ_STUN_ATTR_SOFTWARE) { + /* Set the length to be 4-bytes aligned so that we can + * communicate with RFC 3489 endpoints + */ + PUTVAL16H(buf, 2, (pj_uint16_t)((ca->value.slen + 3) & (~3))); + } else { + /* Use RFC 5389 rule */ + PUTVAL16H(buf, 2, (pj_uint16_t)ca->value.slen); + } + + /* Copy the string */ + pj_memcpy(buf + ATTR_HDR_LEN, ca->value.ptr, ca->value.slen); + + /* Add padding character, if string is not 4-bytes aligned. */ + if (ca->value.slen & 0x03) { + pj_uint8_t pad[3]; + pj_memset(pad, padding_char, sizeof(pad)); + pj_memcpy(buf + ATTR_HDR_LEN + ca->value.slen, pad, 4 - (ca->value.slen & 0x03)); + } + + /* Done */ + return PJ_SUCCESS; +} + +static void *clone_string_attr(pj_pool_t *pool, const void *src) +{ + const pj_stun_string_attr *asrc = (const pj_stun_string_attr *)src; + pj_stun_string_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_string_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_attr_hdr)); + pj_strdup(pool, &dst->value, &asrc->value); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN empty attribute (used by USE-CANDIDATE). + */ + +/* + * Create a STUN empty attribute. + */ +PJ_DEF(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool, int attr_type, pj_stun_empty_attr **p_attr) +{ + pj_stun_empty_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr); + INIT_ATTR(attr, attr_type, 0); + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* + * Create STUN empty attribute and add the attribute to the message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_empty_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type) +{ + pj_stun_empty_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_empty_attr_create(pool, attr_type, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_empty_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_empty_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Check that the struct address is valid */ + pj_assert(sizeof(pj_stun_empty_attr) == ATTR_HDR_LEN); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != 0) + return PJNATH_ESTUNINATTRLEN; + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_empty_attr *ca = (pj_stun_empty_attr *)a; + + PJ_UNUSED_ARG(msghdr); + + if (len < ATTR_HDR_LEN) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, 0); + + /* Done */ + *printed = ATTR_HDR_LEN; + + return PJ_SUCCESS; +} + +static void *clone_empty_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_empty_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_empty_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_empty_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic 32bit integer attribute. + */ + +/* + * Create a STUN generic 32bit value attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_uint_attr_create(pj_pool_t *pool, int attr_type, pj_uint32_t value, pj_stun_uint_attr **p_attr) +{ + pj_stun_uint_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr); + INIT_ATTR(attr, attr_type, 4); + attr->value = value; + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* Create and add STUN generic 32bit value attribute to the message. */ +PJ_DEF(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_uint32_t value) +{ + pj_stun_uint_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_uint_attr_create(pool, attr_type, value, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_uint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_uint_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != 4) + return PJNATH_ESTUNINATTRLEN; + + attr->value = GETVAL32H(buf, 4); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_uint_attr *ca = (const pj_stun_uint_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < 8) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)4); + PUTVAL32H(buf, 4, ca->value); + + /* Done */ + *printed = 8; + + return PJ_SUCCESS; +} + +static void *clone_uint_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_uint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_uint_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * Create a STUN generic 64bit value attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_uint64_attr_create(pj_pool_t *pool, int attr_type, const pj_timestamp *value, pj_stun_uint64_attr **p_attr) +{ + pj_stun_uint64_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr); + INIT_ATTR(attr, attr_type, 8); + + if (value) { + attr->value.u32.hi = value->u32.hi; + attr->value.u32.lo = value->u32.lo; + } + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* Create and add STUN generic 64bit value attribute to the message. */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_uint64_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_timestamp *value) +{ + pj_stun_uint64_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_uint64_attr_create(pool, attr_type, value, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_uint64_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_uint64_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr); + GETATTRHDR(buf, &attr->hdr); + + if (attr->hdr.length != 8) + return PJNATH_ESTUNINATTRLEN; + + GETVAL64H(buf, 4, &attr->value); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_uint64_attr *ca = (const pj_stun_uint64_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < 12) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)8); + PUTVAL64H(buf, 4, &ca->value); + + /* Done */ + *printed = 12; + + return PJ_SUCCESS; +} + +static void *clone_uint64_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_uint64_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint64_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_uint64_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN MESSAGE-INTEGRITY attribute. + */ + +/* + * Create a STUN MESSAGE-INTEGRITY attribute. + */ +PJ_DEF(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool, pj_stun_msgint_attr **p_attr) +{ + pj_stun_msgint_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr); + INIT_ATTR(attr, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 20); + + *p_attr = attr; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool, pj_stun_msg *msg) +{ + pj_stun_msgint_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_msgint_attr_create(pool, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_msgint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_msgint_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != 20) + return PJNATH_ESTUNINATTRLEN; + + /* Copy hmac */ + pj_memcpy(attr->hmac, buf + 4, 20); + + /* Done */ + *p_attr = attr; + return PJ_SUCCESS; +} + +static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_msgint_attr *ca = (const pj_stun_msgint_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < 24) + return PJ_ETOOSMALL; + + /* Copy and convert attribute to network byte order */ + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, ca->hdr.length); + + pj_memcpy(buf + 4, ca->hmac, 20); + + /* Done */ + *printed = 24; + + return PJ_SUCCESS; +} + +static void *clone_msgint_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_msgint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_msgint_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_msgint_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN ERROR-CODE + */ + +/* + * Create a STUN ERROR-CODE attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_errcode_attr_create(pj_pool_t *pool, int err_code, const pj_str_t *err_reason, pj_stun_errcode_attr **p_attr) +{ + pj_stun_errcode_attr *attr; + char err_buf[80]; + pj_str_t str; + + PJ_ASSERT_RETURN(pool && err_code && p_attr, PJ_EINVAL); + + if (err_reason == NULL) { + str = pj_stun_get_err_reason(err_code); + if (str.slen == 0) { + str.slen = pj_ansi_snprintf(err_buf, sizeof(err_buf), "Unknown error %d", err_code); + str.ptr = err_buf; + } + err_reason = &str; + } + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr); + INIT_ATTR(attr, PJ_STUN_ATTR_ERROR_CODE, 4 + err_reason->slen); + attr->err_code = err_code; + pj_strdup(pool, &attr->reason, err_reason); + + *p_attr = attr; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_msg_add_errcode_attr(pj_pool_t *pool, pj_stun_msg *msg, int err_code, const pj_str_t *err_reason) +{ + pj_stun_errcode_attr *err_attr = NULL; + pj_status_t status; + + status = pj_stun_errcode_attr_create(pool, err_code, err_reason, &err_attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &err_attr->hdr); +} + +static pj_status_t decode_errcode_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_errcode_attr *attr; + pj_str_t value; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length < 4) + return PJNATH_ESTUNINATTRLEN; + + attr->err_code = buf[6] * 100 + buf[7]; + + /* Get pointer to the string in the message */ + value.ptr = ((char *)buf + ATTR_HDR_LEN + 4); + value.slen = attr->hdr.length - 4; + + /* Copy the string to the attribute */ + pj_strdup(pool, &attr->reason, &value); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_errcode_attr *ca = (const pj_stun_errcode_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < ATTR_HDR_LEN + 4 + (unsigned)ca->reason.slen) + return PJ_ETOOSMALL; + + /* Copy and convert attribute to network byte order */ + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)(4 + ca->reason.slen)); + PUTVAL16H(buf, 4, 0); + buf[6] = (pj_uint8_t)(ca->err_code / 100); + buf[7] = (pj_uint8_t)(ca->err_code % 100); + + /* Copy error string */ + pj_memcpy(buf + ATTR_HDR_LEN + 4, ca->reason.ptr, ca->reason.slen); + + /* Done */ + *printed = (ATTR_HDR_LEN + 4 + (unsigned)ca->reason.slen + 3) & (~3); + + return PJ_SUCCESS; +} + +static void *clone_errcode_attr(pj_pool_t *pool, const void *src) +{ + const pj_stun_errcode_attr *asrc = (const pj_stun_errcode_attr *)src; + pj_stun_errcode_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_errcode_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_errcode_attr)); + pj_strdup(pool, &dst->reason, &asrc->reason); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN UNKNOWN-ATTRIBUTES attribute + */ + +/* + * Create an empty instance of STUN UNKNOWN-ATTRIBUTES attribute. + * + * @param pool The pool to allocate memory from. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DEF(pj_status_t) +pj_stun_unknown_attr_create(pj_pool_t *pool, unsigned attr_cnt, const pj_uint16_t attr_array[], + pj_stun_unknown_attr **p_attr) +{ + pj_stun_unknown_attr *attr; + unsigned i; + + PJ_ASSERT_RETURN(pool && attr_cnt < PJ_STUN_MAX_ATTR && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr); + INIT_ATTR(attr, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, attr_cnt * 2); + + attr->attr_count = attr_cnt; + for (i = 0; i < attr_cnt; ++i) { + attr->attrs[i] = attr_array[i]; + } + + /* If the number of unknown attributes is an odd number, one of the + * attributes MUST be repeated in the list. + */ + /* No longer necessary + if ((attr_cnt & 0x01)) { + attr->attrs[attr_cnt] = attr_array[attr_cnt-1]; + } + */ + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_unknown_attr(pj_pool_t *pool, pj_stun_msg *msg, unsigned attr_cnt, const pj_uint16_t attr_type[]) +{ + pj_stun_unknown_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_unknown_attr_create(pool, attr_cnt, attr_type, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_unknown_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_unknown_attr *attr; + const pj_uint16_t *punk_attr; + unsigned i; + + PJ_UNUSED_ARG(msghdr); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr); + GETATTRHDR(buf, &attr->hdr); + + attr->attr_count = (attr->hdr.length >> 1); + if (attr->attr_count > PJ_STUN_MAX_ATTR) + return PJ_ETOOMANY; + + punk_attr = (const pj_uint16_t *)(buf + ATTR_HDR_LEN); + for (i = 0; i < attr->attr_count; ++i) { + attr->attrs[i] = pj_ntohs(punk_attr[i]); + } + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_unknown_attr *ca = (const pj_stun_unknown_attr *)a; + pj_uint16_t *dst_unk_attr; + unsigned i; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Check that buffer is enough */ + if (len < ATTR_HDR_LEN + (ca->attr_count << 1)) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)(ca->attr_count << 1)); + + /* Copy individual attribute */ + dst_unk_attr = (pj_uint16_t *)(buf + ATTR_HDR_LEN); + for (i = 0; i < ca->attr_count; ++i, ++dst_unk_attr) { + *dst_unk_attr = pj_htons(ca->attrs[i]); + } + + /* Done */ + *printed = (ATTR_HDR_LEN + (ca->attr_count << 1) + 3) & (~3); + + return PJ_SUCCESS; +} + +static void *clone_unknown_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_unknown_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_unknown_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_unknown_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic binary attribute + */ + +/* + * Initialize STUN binary attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_binary_attr_init(pj_stun_binary_attr *attr, pj_pool_t *pool, int attr_type, const pj_uint8_t *data, + unsigned length) +{ + PJ_ASSERT_RETURN(attr_type, PJ_EINVAL); + + INIT_ATTR(attr, attr_type, length); + + attr->magic = PJ_STUN_MAGIC; + + if (data && length) { + attr->length = length; + attr->data = (pj_uint8_t *)pj_pool_alloc(pool, length); + pj_memcpy(attr->data, data, length); + } else { + attr->data = NULL; + attr->length = 0; + } + + return PJ_SUCCESS; +} + +/* + * Create a blank binary attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_binary_attr_create(pj_pool_t *pool, int attr_type, const pj_uint8_t *data, unsigned length, + pj_stun_binary_attr **p_attr) +{ + pj_stun_binary_attr *attr; + + PJ_ASSERT_RETURN(pool && attr_type && p_attr, PJ_EINVAL); + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr); + *p_attr = attr; + return pj_stun_binary_attr_init(attr, pool, attr_type, data, length); +} + +/* Create and add binary attr. */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_binary_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_uint8_t *data, unsigned length) +{ + pj_stun_binary_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_binary_attr_create(pool, attr_type, data, length, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_binary_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_binary_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Copy the data to the attribute */ + attr->length = attr->hdr.length; + attr->data = (pj_uint8_t *)pj_pool_alloc(pool, attr->length); + pj_memcpy(attr->data, buf + ATTR_HDR_LEN, attr->length); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_binary_attr *ca = (const pj_stun_binary_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Calculated total attr_len (add padding if necessary) */ + *printed = (ca->length + ATTR_HDR_LEN + 3) & (~3); + if (len < *printed) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)ca->length); + + /* Copy the data */ + pj_memcpy(buf + ATTR_HDR_LEN, ca->data, ca->length); + + /* Done */ + return PJ_SUCCESS; +} + +static void *clone_binary_attr(pj_pool_t *pool, const void *src) +{ + const pj_stun_binary_attr *asrc = (const pj_stun_binary_attr *)src; + pj_stun_binary_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_binary_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_binary_attr)); + + if (asrc->length) { + dst->data = (pj_uint8_t *)pj_pool_alloc(pool, asrc->length); + pj_memcpy(dst->data, asrc->data, asrc->length); + } + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * Initialize a generic STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_init(pj_stun_msg *msg, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12]) +{ + PJ_ASSERT_RETURN(msg && msg_type, PJ_EINVAL); + + msg->hdr.type = (pj_uint16_t)msg_type; + msg->hdr.length = 0; + msg->hdr.magic = magic; + msg->attr_count = 0; + + if (tsx_id) { + pj_memcpy(&msg->hdr.tsx_id, tsx_id, sizeof(msg->hdr.tsx_id)); + } else { + struct transaction_id { + pj_uint32_t proc_id; + pj_uint32_t random; + pj_uint32_t counter; + } id; + static pj_uint32_t pj_stun_tsx_id_counter; + + if (!pj_stun_tsx_id_counter) + pj_stun_tsx_id_counter = pj_rand(); + + id.proc_id = pj_getpid(); + id.random = pj_rand(); + id.counter = pj_stun_tsx_id_counter++; + + pj_memcpy(&msg->hdr.tsx_id, &id, sizeof(msg->hdr.tsx_id)); + } + + return PJ_SUCCESS; +} + +/* + * Create a blank STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_create(pj_pool_t *pool, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_msg **p_msg) +{ + pj_stun_msg *msg; + + PJ_ASSERT_RETURN(pool && msg_type && p_msg, PJ_EINVAL); + + msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg); + *p_msg = msg; + return pj_stun_msg_init(msg, msg_type, magic, tsx_id); +} + +/* + * Clone a STUN message with all of its attributes. + */ +PJ_DEF(pj_stun_msg *) pj_stun_msg_clone(pj_pool_t *pool, const pj_stun_msg *src) +{ + pj_stun_msg *dst; + unsigned i; + + PJ_ASSERT_RETURN(pool && src, NULL); + + dst = PJ_POOL_ZALLOC_T(pool, pj_stun_msg); + pj_memcpy(dst, src, sizeof(pj_stun_msg)); + + /* Duplicate the attributes */ + for (i = 0, dst->attr_count = 0; i < src->attr_count; ++i) { + dst->attr[dst->attr_count] = pj_stun_attr_clone(pool, src->attr[i]); + if (dst->attr[dst->attr_count]) + ++dst->attr_count; + } + + return dst; +} + +/* + * Add STUN attribute to STUN message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg, pj_stun_attr_hdr *attr) +{ + PJ_ASSERT_RETURN(msg && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(msg->attr_count < PJ_STUN_MAX_ATTR, PJ_ETOOMANY); + + msg->attr[msg->attr_count++] = attr; + return PJ_SUCCESS; +} + +/* + * Check that the PDU is potentially a valid STUN message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options) +{ + pj_uint32_t msg_len; + + PJ_ASSERT_RETURN(pdu, PJ_EINVAL); + + if (pdu_len < sizeof(pj_stun_msg_hdr)) + return PJNATH_EINSTUNMSGLEN; + + /* First byte of STUN message is always 0x00 or 0x01. */ + if (*pdu != 0x00 && *pdu != 0x01) + return PJNATH_EINSTUNMSGTYPE; + + /* Check the PDU length */ + msg_len = GETVAL16H(pdu, 2); + if ((msg_len + 20 > pdu_len) || ((options & PJ_STUN_IS_DATAGRAM) && msg_len + 20 != pdu_len)) { + return PJNATH_EINSTUNMSGLEN; + } + + /* STUN message is always padded to the nearest 4 bytes, thus + * the last two bits of the length field are always zero. + */ + if ((msg_len & 0x03) != 0) { + return PJNATH_EINSTUNMSGLEN; + } + + /* If magic is set, then there is great possibility that this is + * a STUN message. + */ + if (GETVAL32H(pdu, 4) == PJ_STUN_MAGIC) { + + /* Check if FINGERPRINT attribute is present */ + if ((options & PJ_STUN_NO_FINGERPRINT_CHECK) == 0 && + GETVAL16H(pdu, msg_len + 20 - 8) == PJ_STUN_ATTR_FINGERPRINT) { + pj_uint16_t attr_len = GETVAL16H(pdu, msg_len + 20 - 8 + 2); + pj_uint32_t fingerprint = GETVAL32H(pdu, msg_len + 20 - 8 + 4); + pj_uint32_t crc; + + if (attr_len != 4) + return PJNATH_ESTUNINATTRLEN; + + crc = pj_crc32_calc(pdu, msg_len + 20 - 8); + crc ^= STUN_XOR_FINGERPRINT; + + if (crc != fingerprint) + return PJNATH_ESTUNFINGERPRINT; + } + } + + /* Could be a STUN message */ + return PJ_SUCCESS; +} + +/* Create error response */ +PJ_DEF(pj_status_t) +pj_stun_msg_create_response(pj_pool_t *pool, const pj_stun_msg *req_msg, unsigned err_code, const pj_str_t *err_msg, + pj_stun_msg **p_response) +{ + unsigned msg_type = req_msg->hdr.type; + pj_stun_msg *response = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && p_response, PJ_EINVAL); + + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(msg_type), PJNATH_EINSTUNMSGTYPE); + + /* Create response or error response */ + if (err_code) + msg_type |= PJ_STUN_ERROR_RESPONSE_BIT; + else + msg_type |= PJ_STUN_SUCCESS_RESPONSE_BIT; + + status = pj_stun_msg_create(pool, msg_type, req_msg->hdr.magic, req_msg->hdr.tsx_id, &response); + if (status != PJ_SUCCESS) { + return status; + } + + /* Add error code attribute */ + if (err_code) { + status = pj_stun_msg_add_errcode_attr(pool, response, err_code, err_msg); + if (status != PJ_SUCCESS) { + return status; + } + } + + *p_response = response; + return PJ_SUCCESS; +} + +/* + * Parse incoming packet into STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_decode(pj_pool_t *pool, const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options, pj_stun_msg **p_msg, + pj_size_t *p_parsed_len, pj_stun_msg **p_response) +{ + + pj_stun_msg *msg; + const pj_uint8_t *start_pdu = pdu; + pj_bool_t has_msg_int = PJ_FALSE; + pj_bool_t has_fingerprint = PJ_FALSE; + pj_status_t status; + + PJ_UNUSED_ARG(options); + + PJ_ASSERT_RETURN(pool && pdu && pdu_len && p_msg, PJ_EINVAL); + PJ_ASSERT_RETURN(sizeof(pj_stun_msg_hdr) == 20, PJ_EBUG); + + if (p_parsed_len) + *p_parsed_len = 0; + if (p_response) + *p_response = NULL; + + /* Check if this is a STUN message, if necessary */ + if (options & PJ_STUN_CHECK_PACKET) { + status = pj_stun_msg_check(pdu, pdu_len, options); + if (status != PJ_SUCCESS) + return status; + } else { + /* For safety, verify packet length at least */ + pj_uint32_t msg_len = GETVAL16H(pdu, 2) + 20; + if (msg_len > pdu_len || ((options & PJ_STUN_IS_DATAGRAM) && msg_len != pdu_len)) { + return PJNATH_EINSTUNMSGLEN; + } + } + + /* Create the message, copy the header, and convert to host byte order */ + msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg); + pj_memcpy(&msg->hdr, pdu, sizeof(pj_stun_msg_hdr)); + msg->hdr.type = pj_ntohs(msg->hdr.type); + msg->hdr.length = pj_ntohs(msg->hdr.length); + msg->hdr.magic = pj_ntohl(msg->hdr.magic); + + pdu += sizeof(pj_stun_msg_hdr); + /* pdu_len -= sizeof(pj_stun_msg_hdr); */ + pdu_len = msg->hdr.length; + + /* No need to create response if this is not a request */ + if (!PJ_STUN_IS_REQUEST(msg->hdr.type)) + p_response = NULL; + + /* Parse attributes */ + while (pdu_len >= ATTR_HDR_LEN) { + unsigned attr_type, attr_val_len; + const struct attr_desc *adesc; + + /* Get attribute type and length. If length is not aligned + * to 4 bytes boundary, add padding. + */ + attr_type = GETVAL16H(pdu, 0); + attr_val_len = GETVAL16H(pdu, 2); + attr_val_len = (attr_val_len + 3) & (~3); + + /* Check length */ + if (pdu_len < attr_val_len + ATTR_HDR_LEN) { + pj_str_t err_msg; + char err_msg_buf[80]; + + err_msg.ptr = err_msg_buf; + err_msg.slen = pj_ansi_snprintf(err_msg_buf, sizeof(err_msg_buf), "Attribute %s has invalid length", + pj_stun_get_attr_name(attr_type)); + + PJ_LOG(4, (THIS_FILE, "Error decoding message: %.*s", (int)err_msg.slen, err_msg.ptr)); + + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, &err_msg, p_response); + } + return PJNATH_ESTUNINATTRLEN; + } + + /* Get the attribute descriptor */ + adesc = find_attr_desc(attr_type); + + if (adesc == NULL) { + /* Unrecognized attribute */ + pj_stun_binary_attr *attr = NULL; + + PJ_LOG(5, (THIS_FILE, "Unrecognized attribute type 0x%x", attr_type)); + + /* Is this a fatal condition? */ + if (attr_type <= 0x7FFF) { + /* This is a mandatory attribute, we must return error + * if we don't understand the attribute. + */ + if (p_response) { + unsigned err_code = PJ_STUN_SC_UNKNOWN_ATTRIBUTE; + + status = pj_stun_msg_create_response(pool, msg, err_code, NULL, p_response); + if (status == PJ_SUCCESS) { + pj_uint16_t d = (pj_uint16_t)attr_type; + pj_stun_msg_add_unknown_attr(pool, *p_response, 1, &d); + } + } + + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_ATTRIBUTE); + } + + /* Make sure we have rooms for the new attribute */ + if (msg->attr_count >= PJ_STUN_MAX_ATTR) { + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_SERVER_ERROR, NULL, p_response); + } + return PJNATH_ESTUNTOOMANYATTR; + } + + /* Create binary attribute to represent this */ + status = pj_stun_binary_attr_create(pool, attr_type, pdu + 4, GETVAL16H(pdu, 2), &attr); + if (status != PJ_SUCCESS) { + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_SERVER_ERROR, NULL, p_response); + } + + PJ_LOG(4, (THIS_FILE, "Error parsing unknown STUN attribute type %d", attr_type)); + + return status; + } + + /* Add the attribute */ + msg->attr[msg->attr_count++] = &attr->hdr; + + } else { + void *attr; + char err_msg1[PJ_ERR_MSG_SIZE], err_msg2[PJ_ERR_MSG_SIZE]; + + /* Parse the attribute */ + status = (adesc->decode_attr)(pool, pdu, &msg->hdr, &attr); + + if (status != PJ_SUCCESS) { + pj_strerror(status, err_msg1, sizeof(err_msg1)); + + if (p_response) { + pj_str_t e; + + e.ptr = err_msg2; + e.slen = pj_ansi_snprintf(err_msg2, sizeof(err_msg2), "%s in %s", err_msg1, + pj_stun_get_attr_name(attr_type)); + if (e.slen < 1 || e.slen >= (int)sizeof(err_msg2)) + e.slen = sizeof(err_msg2) - 1; + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, &e, p_response); + } + + PJ_LOG(4, + (THIS_FILE, "Error parsing STUN attribute %s: %s", pj_stun_get_attr_name(attr_type), err_msg1)); + + return status; + } + + if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY && !has_fingerprint) { + if (has_msg_int) { + /* Already has MESSAGE-INTEGRITY */ + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, NULL, p_response); + } + return PJNATH_ESTUNDUPATTR; + } + has_msg_int = PJ_TRUE; + + } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) { + if (has_fingerprint) { + /* Already has FINGERPRINT */ + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, NULL, p_response); + } + return PJNATH_ESTUNDUPATTR; + } + has_fingerprint = PJ_TRUE; + } else { + if (has_fingerprint) { + /* Another attribute is found which is not FINGERPRINT + * after FINGERPRINT. Note that non-FINGERPRINT is + * allowed to appear after M-I + */ + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, NULL, p_response); + } + return PJNATH_ESTUNFINGERPOS; + } + } + + /* Make sure we have rooms for the new attribute */ + if (msg->attr_count >= PJ_STUN_MAX_ATTR) { + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_SERVER_ERROR, NULL, p_response); + } + return PJNATH_ESTUNTOOMANYATTR; + } + + /* Add the attribute */ + msg->attr[msg->attr_count++] = (pj_stun_attr_hdr *)attr; + } + + /* Next attribute */ + if (attr_val_len + 4 >= pdu_len) { + pdu += pdu_len; + pdu_len = 0; + } else { + pdu += (attr_val_len + 4); + pdu_len -= (attr_val_len + 4); + } + } + + if (pdu_len > 0) { + /* Stray trailing bytes */ + PJ_LOG(4, (THIS_FILE, "Error decoding STUN message: unparsed trailing %d bytes", pdu_len)); + return PJNATH_EINSTUNMSGLEN; + } + + *p_msg = msg; + + if (p_parsed_len) + *p_parsed_len = (pdu - start_pdu); + + return PJ_SUCCESS; +} + +/* +static char *print_binary(const pj_uint8_t *data, unsigned data_len) +{ + static char static_buffer[1024]; + char *buffer = static_buffer; + unsigned length=sizeof(static_buffer), i; + + if (length < data_len * 2 + 8) + return ""; + + pj_ansi_sprintf(buffer, ", data="); + buffer += 7; + + for (i=0; ihdr.type); + PUTVAL16H(buf, 2, 0); /* length will be calculated later */ + PUTVAL32H(buf, 4, msg->hdr.magic); + pj_memcpy(buf + 8, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)); + + buf += sizeof(pj_stun_msg_hdr); + buf_size -= sizeof(pj_stun_msg_hdr); + + /* Encode each attribute to the message */ + for (i = 0; i < msg->attr_count; ++i) { + const struct attr_desc *adesc; + const pj_stun_attr_hdr *attr_hdr = msg->attr[i]; + + if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + pj_assert(amsgint == NULL); + amsgint = (pj_stun_msgint_attr *)attr_hdr; + + /* Stop when encountering MESSAGE-INTEGRITY */ + break; + + } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) { + afingerprint = (pj_stun_fingerprint_attr *)attr_hdr; + break; + } + + adesc = find_attr_desc(attr_hdr->type); + if (adesc) { + status = adesc->encode_attr(attr_hdr, buf, (unsigned)buf_size, &msg->hdr, &printed); + } else { + /* This may be a generic attribute */ + const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr *)attr_hdr; + PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, PJ_EBUG); + status = encode_binary_attr(bin_attr, buf, (unsigned)buf_size, &msg->hdr, &printed); + } + + if (status != PJ_SUCCESS) + return status; + + buf += printed; + buf_size -= printed; + } + + /* We may have stopped printing attribute because we found + * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the + * attributes. + */ + for (++i; i < msg->attr_count; ++i) { + const pj_stun_attr_hdr *attr_hdr = msg->attr[i]; + + /* There mustn't any attribute after FINGERPRINT */ + PJ_ASSERT_RETURN(afingerprint == NULL, PJNATH_ESTUNFINGERPOS); + + if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + /* There mustn't be MESSAGE-INTEGRITY before */ + PJ_ASSERT_RETURN(amsgint == NULL, PJNATH_ESTUNMSGINTPOS); + amsgint = (pj_stun_msgint_attr *)attr_hdr; + + } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) { + afingerprint = (pj_stun_fingerprint_attr *)attr_hdr; + } + } + +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* + * This is the old style MESSAGE-INTEGRITY and FINGERPRINT + * calculation, used in rfc3489bis-06 and older. + */ + /* We MUST update the message length in the header NOW before + * calculating MESSAGE-INTEGRITY and FINGERPRINT. + * Note that length is not including the 20 bytes header. + */ + if (amsgint && afingerprint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 24 + 8); + } else if (amsgint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 24); + } else if (afingerprint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 8); + } else { + body_len = (pj_uint16_t)((buf - start) - 20); + } +#else + /* If MESSAGE-INTEGRITY is present, include the M-I attribute + * in message length before calculating M-I + */ + if (amsgint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 24); + } else { + body_len = (pj_uint16_t)((buf - start) - 20); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + + /* hdr->length = pj_htons(length); */ + PUTVAL16H(start, 2, (pj_uint16_t)body_len); + + /* Calculate message integrity, if present */ + if (amsgint != NULL) { + pj_hmac_sha1_context ctx; + + /* Key MUST be specified */ + PJ_ASSERT_RETURN(key, PJ_EINVALIDOP); + + /* MESSAGE-INTEGRITY must be the last attribute in the message, or + * the last attribute before FINGERPRINT. + */ + if (msg->attr_count > 1 && i < msg->attr_count - 2) { + /* Should not happen for message generated by us */ + pj_assert(PJ_FALSE); + return PJNATH_ESTUNMSGINTPOS; + + } else if (i == msg->attr_count - 2) { + if (msg->attr[i + 1]->type != PJ_STUN_ATTR_FINGERPRINT) { + /* Should not happen for message generated by us */ + pj_assert(PJ_FALSE); + return PJNATH_ESTUNMSGINTPOS; + } else { + afingerprint = (pj_stun_fingerprint_attr *)msg->attr[i + 1]; + } + } + + /* Calculate HMAC-SHA1 digest, add zero padding to input + * if necessary to make the input 64 bytes aligned. + */ + pj_hmac_sha1_init(&ctx, (const pj_uint8_t *)key->ptr, (unsigned)key->slen); + pj_hmac_sha1_update(&ctx, (const pj_uint8_t *)start, (unsigned)(buf - start)); +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + // These are obsoleted in rfc3489bis-08 + if ((buf - start) & 0x3F) { + pj_uint8_t zeroes[64]; + pj_bzero(zeroes, sizeof(zeroes)); + pj_hmac_sha1_update(&ctx, zeroes, 64 - ((buf - start) & 0x3F)); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + pj_hmac_sha1_final(&ctx, amsgint->hmac); + + /* Put this attribute in the message */ + status = encode_msgint_attr(amsgint, buf, (unsigned)buf_size, &msg->hdr, &printed); + if (status != PJ_SUCCESS) + return status; + + buf += printed; + buf_size -= printed; + } + + /* Calculate FINGERPRINT if present */ + if (afingerprint != NULL) { + +#if !PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* Update message length */ + PUTVAL16H(start, 2, (pj_uint16_t)(GETVAL16H(start, 2) + 8)); +#endif + + afingerprint->value = pj_crc32_calc(start, buf - start); + afingerprint->value ^= STUN_XOR_FINGERPRINT; + + /* Put this attribute in the message */ + status = encode_uint_attr(afingerprint, buf, (unsigned)buf_size, &msg->hdr, &printed); + if (status != PJ_SUCCESS) + return status; + + buf += printed; + buf_size -= printed; + } + + /* Update message length. */ + msg->hdr.length = (pj_uint16_t)((buf - start) - 20); + + /* Return the length */ + if (p_msg_len) + *p_msg_len = (buf - start); + + return PJ_SUCCESS; +} + +/* + * Find STUN attribute in the STUN message, starting from the specified + * index. + */ +PJ_DEF(pj_stun_attr_hdr *) pj_stun_msg_find_attr(const pj_stun_msg *msg, int attr_type, unsigned index) +{ + PJ_ASSERT_RETURN(msg, NULL); + + for (; index < msg->attr_count; ++index) { + if (msg->attr[index]->type == attr_type) + return (pj_stun_attr_hdr *)msg->attr[index]; + } + + return NULL; +} + +/* + * Clone a STUN attribute. + */ +PJ_DEF(pj_stun_attr_hdr *) pj_stun_attr_clone(pj_pool_t *pool, const pj_stun_attr_hdr *attr) +{ + const struct attr_desc *adesc; + + /* Get the attribute descriptor */ + adesc = find_attr_desc(attr->type); + if (adesc) { + return (pj_stun_attr_hdr *)(*adesc->clone_attr)(pool, attr); + } else { + /* Clone generic attribute */ + const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr *)attr; + PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, NULL); + if (bin_attr->magic == PJ_STUN_MAGIC) { + return (pj_stun_attr_hdr *)clone_binary_attr(pool, attr); + } else { + return NULL; + } + } +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c new file mode 100755 index 000000000..5a097cbfe --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if PJ_LOG_MAX_LEVEL > 0 + +#define APPLY() \ + if (len < 1 || len >= (end - p)) \ + goto on_return; \ + p += len + +static int print_binary(char *buffer, unsigned length, const pj_uint8_t *data, unsigned data_len) +{ + unsigned i; + + if (length < data_len * 2 + 8) + return -1; + + pj_ansi_sprintf(buffer, ", data="); + buffer += 7; + + for (i = 0; i < data_len; ++i) { + pj_ansi_sprintf(buffer, "%02x", (*data) & 0xFF); + buffer += 2; + data++; + } + + pj_ansi_sprintf(buffer, "\n"); + buffer++; + + return data_len * 2 + 8; +} + +static int print_attr(char *buffer, unsigned length, const pj_stun_attr_hdr *ahdr) +{ + char *p = buffer, *end = buffer + length; + const char *attr_name = pj_stun_get_attr_name(ahdr->type); + char attr_buf[32]; + int len; + + if (*attr_name == '?') { + pj_ansi_snprintf(attr_buf, sizeof(attr_buf), "Attr 0x%x", ahdr->type); + attr_name = attr_buf; + } + + len = pj_ansi_snprintf(p, end - p, " %s: length=%d", attr_name, (int)ahdr->length); + APPLY(); + + switch (ahdr->type) { + case PJ_STUN_ATTR_MAPPED_ADDR: + case PJ_STUN_ATTR_RESPONSE_ADDR: + case PJ_STUN_ATTR_SOURCE_ADDR: + case PJ_STUN_ATTR_CHANGED_ADDR: + case PJ_STUN_ATTR_REFLECTED_FROM: + case PJ_STUN_ATTR_XOR_PEER_ADDR: + case PJ_STUN_ATTR_XOR_RELAYED_ADDR: + case PJ_STUN_ATTR_XOR_MAPPED_ADDR: + case PJ_STUN_ATTR_XOR_REFLECTED_FROM: + case PJ_STUN_ATTR_ALTERNATE_SERVER: { + char addr[PJ_INET6_ADDRSTRLEN]; + const pj_stun_sockaddr_attr *attr; + pj_uint16_t af; + + attr = (const pj_stun_sockaddr_attr *)ahdr; + af = attr->sockaddr.addr.sa_family; + + if ((af == pj_AF_INET()) || (af == pj_AF_INET6())) { + len = pj_ansi_snprintf(p, end - p, ", %s addr=%s\n", (af == pj_AF_INET()) ? "IPv4" : "IPv6", + pj_sockaddr_print(&attr->sockaddr, addr, sizeof(addr), 3)); + } else { + len = pj_ansi_snprintf(p, end - p, ", INVALID ADDRESS FAMILY!\n"); + } + APPLY(); + } break; + + case PJ_STUN_ATTR_CHANNEL_NUMBER: { + const pj_stun_uint_attr *attr; + + attr = (const pj_stun_uint_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", chnum=%u (0x%x)\n", (int)PJ_STUN_GET_CH_NB(attr->value), + (int)PJ_STUN_GET_CH_NB(attr->value)); + APPLY(); + } break; + + case PJ_STUN_ATTR_CHANGE_REQUEST: + case PJ_STUN_ATTR_LIFETIME: + case PJ_STUN_ATTR_BANDWIDTH: + case PJ_STUN_ATTR_REQ_ADDR_TYPE: + case PJ_STUN_ATTR_EVEN_PORT: + case PJ_STUN_ATTR_REQ_TRANSPORT: + case PJ_STUN_ATTR_TIMER_VAL: + case PJ_STUN_ATTR_PRIORITY: + case PJ_STUN_ATTR_FINGERPRINT: + case PJ_STUN_ATTR_REFRESH_INTERVAL: + case PJ_STUN_ATTR_ICMP: { + const pj_stun_uint_attr *attr; + + attr = (const pj_stun_uint_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", value=%u (0x%x)\n", (pj_uint32_t)attr->value, (pj_uint32_t)attr->value); + APPLY(); + } break; + + case PJ_STUN_ATTR_USERNAME: + case PJ_STUN_ATTR_PASSWORD: + case PJ_STUN_ATTR_REALM: + case PJ_STUN_ATTR_NONCE: + case PJ_STUN_ATTR_SOFTWARE: { + const pj_stun_string_attr *attr; + + attr = (pj_stun_string_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", value=\"%.*s\"\n", (int)attr->value.slen, attr->value.ptr); + APPLY(); + } break; + + case PJ_STUN_ATTR_ERROR_CODE: { + const pj_stun_errcode_attr *attr; + + attr = (const pj_stun_errcode_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", err_code=%d, reason=\"%.*s\"\n", attr->err_code, (int)attr->reason.slen, + attr->reason.ptr); + APPLY(); + } break; + + case PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES: { + const pj_stun_unknown_attr *attr; + unsigned j; + + attr = (const pj_stun_unknown_attr *)ahdr; + + len = pj_ansi_snprintf(p, end - p, ", unknown list:"); + APPLY(); + + for (j = 0; j < attr->attr_count; ++j) { + len = pj_ansi_snprintf(p, end - p, " %d", (int)attr->attrs[j]); + APPLY(); + } + } break; + + case PJ_STUN_ATTR_MESSAGE_INTEGRITY: { + const pj_stun_msgint_attr *attr; + + attr = (const pj_stun_msgint_attr *)ahdr; + len = print_binary(p, (unsigned)(end - p), attr->hmac, 20); + APPLY(); + } break; + + case PJ_STUN_ATTR_DATA: { + const pj_stun_binary_attr *attr; + + attr = (const pj_stun_binary_attr *)ahdr; + len = print_binary(p, (unsigned)(end - p), attr->data, attr->length); + APPLY(); + } break; + case PJ_STUN_ATTR_ICE_CONTROLLED: + case PJ_STUN_ATTR_ICE_CONTROLLING: + case PJ_STUN_ATTR_RESERVATION_TOKEN: { + const pj_stun_uint64_attr *attr; + pj_uint8_t data[8]; + int i; + + attr = (const pj_stun_uint64_attr *)ahdr; + + for (i = 0; i < 8; ++i) + data[i] = ((const pj_uint8_t *)&attr->value)[7 - i]; + + len = print_binary(p, (unsigned)(end - p), data, 8); + APPLY(); + } break; + case PJ_STUN_ATTR_USE_CANDIDATE: + case PJ_STUN_ATTR_DONT_FRAGMENT: + default: + len = pj_ansi_snprintf(p, end - p, "\n"); + APPLY(); + break; + } + + return (int)(p - buffer); + +on_return: + return len; +} + +/* + * Dump STUN message to a printable string output. + */ +PJ_DEF(char *) pj_stun_msg_dump(const pj_stun_msg *msg, char *buffer, unsigned length, unsigned *printed_len) +{ + char *p, *end; + int len; + unsigned i; + pj_uint32_t tsx_id[3]; + + PJ_ASSERT_RETURN(msg && buffer && length, NULL); + + PJ_CHECK_STACK(); + + p = buffer; + end = buffer + length; + + len = pj_ansi_snprintf(p, end - p, "STUN %s %s\n", pj_stun_get_method_name(msg->hdr.type), + pj_stun_get_class_name(msg->hdr.type)); + APPLY(); + + pj_memcpy(tsx_id, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)); + len = pj_ansi_snprintf(p, end - p, + " Hdr: length=%d, magic=%08x, tsx_id=%08x%08x%08x\n" + " Attributes:\n", + msg->hdr.length, msg->hdr.magic, tsx_id[0], tsx_id[1], tsx_id[2]); + APPLY(); + + for (i = 0; i < msg->attr_count; ++i) { + len = print_attr(p, (unsigned)(end - p), msg->attr[i]); + APPLY(); + } + +on_return: + *p = '\0'; + if (printed_len) + *printed_len = (unsigned)(p - buffer); + return buffer; + +#undef APPLY +} + +#endif /* PJ_LOG_MAX_LEVEL > 0 */ diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c new file mode 100755 index 000000000..98637257f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c @@ -0,0 +1,1325 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +struct pj_stun_session { + pj_stun_config *cfg; + pj_pool_t *pool; + pj_grp_lock_t *grp_lock; + pj_stun_session_cb cb; + void *user_data; + pj_bool_t is_destroying; + + pj_bool_t use_fingerprint; + + pj_pool_t *rx_pool; + +#if PJ_LOG_MAX_LEVEL >= 5 + char dump_buf[1000]; +#endif + unsigned log_flag; + + pj_stun_auth_type auth_type; + pj_stun_auth_cred cred; + int auth_retry; + pj_str_t next_nonce; + pj_str_t server_realm; + + pj_str_t srv_name; + + pj_stun_tx_data pending_request_list; + pj_stun_tx_data cached_response_list; +}; + +#define SNAME(s_) ((s_)->pool->obj_name) +#define THIS_FILE "stun_session.c" + +#if 1 +#define TRACE_(expr) PJ_LOG(5, expr) +#else +#define TRACE_(expr) +#endif + +#define LOG_ERR_(sess, title, rc) PJ_PERROR(3, (sess->pool->obj_name, rc, title)) + +#define TDATA_POOL_SIZE PJNATH_POOL_LEN_STUN_TDATA +#define TDATA_POOL_INC PJNATH_POOL_INC_STUN_TDATA + +static void stun_tsx_on_complete(pj_stun_client_tsx *tsx, pj_status_t status, const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); +static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx, const void *stun_pkt, pj_size_t pkt_size); +static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx); +static void stun_sess_on_destroy(void *comp); +static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force); + +static pj_stun_tsx_cb tsx_cb = {&stun_tsx_on_complete, &stun_tsx_on_send_msg, &stun_tsx_on_destroy}; + +static pj_status_t tsx_add(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + pj_list_push_front(&sess->pending_request_list, tdata); + return PJ_SUCCESS; +} + +static pj_status_t tsx_erase(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + PJ_UNUSED_ARG(sess); + pj_list_erase(tdata); + return PJ_SUCCESS; +} + +static pj_stun_tx_data *tsx_lookup(pj_stun_session *sess, const pj_stun_msg *msg) +{ + pj_stun_tx_data *tdata; + + tdata = sess->pending_request_list.next; + while (tdata != &sess->pending_request_list) { + pj_assert(sizeof(tdata->msg_key) == sizeof(msg->hdr.tsx_id)); + if (tdata->msg_magic == msg->hdr.magic && + pj_memcmp(tdata->msg_key, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)) == 0) { + return tdata; + } + tdata = tdata->next; + } + + return NULL; +} + +static pj_status_t create_tdata(pj_stun_session *sess, pj_stun_tx_data **p_tdata) +{ + pj_pool_t *pool; + pj_stun_tx_data *tdata; + + /* Create pool and initialize basic tdata attributes */ + pool = pj_pool_create(sess->cfg->pf, "tdata%p", TDATA_POOL_SIZE, TDATA_POOL_INC, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + tdata = PJ_POOL_ZALLOC_T(pool, pj_stun_tx_data); + tdata->pool = pool; + tdata->sess = sess; + + pj_list_init(tdata); + + *p_tdata = tdata; + + return PJ_SUCCESS; +} + +static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx) +{ + pj_stun_tx_data *tdata; + + tdata = (pj_stun_tx_data *)pj_stun_client_tsx_get_data(tsx); + pj_stun_client_tsx_stop(tsx); + if (tdata) { + pj_stun_session *sess = tdata->sess; + + pj_grp_lock_acquire(sess->grp_lock); + tsx_erase(sess, tdata); + destroy_tdata(tdata, PJ_TRUE); + pj_grp_lock_release(sess->grp_lock); + } + + pj_stun_client_tsx_destroy(tsx); + + TRACE_((THIS_FILE, "STUN transaction %p destroyed", tsx)); +} + +static void tdata_on_destroy(void *arg) +{ + pj_stun_tx_data *tdata = (pj_stun_tx_data *)arg; + + if (tdata->grp_lock) { + pj_grp_lock_dec_ref(tdata->sess->grp_lock); + } + + pj_pool_safe_release(&tdata->pool); +} + +static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force) +{ + TRACE_((THIS_FILE, "tdata %p destroy request, force=%d, tsx=%p, destroying=%d", tdata, force, tdata->client_tsx, + tdata->is_destroying)); + + /* Just return if destroy has been requested before */ + if (tdata->is_destroying) + return; + + /* STUN session may have been destroyed, except when tdata is cached. */ + + tdata->is_destroying = PJ_TRUE; + + if (tdata->res_timer.id != PJ_FALSE) { + pj_timer_heap_cancel_if_active(tdata->sess->cfg->timer_heap, &tdata->res_timer, PJ_FALSE); + } + + if (force) { + pj_list_erase(tdata); + if (tdata->client_tsx) { + pj_stun_client_tsx_stop(tdata->client_tsx); + pj_stun_client_tsx_set_data(tdata->client_tsx, NULL); + } + if (tdata->grp_lock) { + pj_grp_lock_dec_ref(tdata->grp_lock); + } else { + tdata_on_destroy(tdata); + } + + } else { + if (tdata->client_tsx) { + /* "Probably" this is to absorb retransmission */ + pj_time_val delay = {0, 300}; + pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay); + tdata->is_destroying = PJ_FALSE; + + } else { + pj_list_erase(tdata); + if (tdata->grp_lock) { + pj_grp_lock_dec_ref(tdata->grp_lock); + } else { + tdata_on_destroy(tdata); + } + } + } +} + +/* + * Destroy the transmit data. + */ +PJ_DEF(void) pj_stun_msg_destroy_tdata(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + PJ_UNUSED_ARG(sess); + destroy_tdata(tdata, PJ_FALSE); +} + +/* Timer callback to be called when it's time to destroy response cache */ +static void on_cache_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pj_stun_tx_data *tdata; + pj_stun_session *sess; + + PJ_UNUSED_ARG(timer_heap); + + entry->id = PJ_FALSE; + tdata = (pj_stun_tx_data *)entry->user_data; + sess = tdata->sess; + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying || tdata->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return; + } + + PJ_LOG(5, (SNAME(tdata->sess), "Response cache deleted")); + + destroy_tdata(tdata, PJ_FALSE); + pj_grp_lock_release(sess->grp_lock); +} + +static pj_status_t apply_msg_options(pj_stun_session *sess, pj_pool_t *pool, const pj_stun_req_cred_info *auth_info, + pj_stun_msg *msg) +{ + pj_status_t status = 0; + pj_str_t realm, username, nonce, auth_key; + + /* If the agent is sending a request, it SHOULD add a SOFTWARE attribute + * to the request. The server SHOULD include a SOFTWARE attribute in all + * responses. + * + * If magic value is not PJ_STUN_MAGIC, only apply the attribute for + * responses. + */ + if (sess->srv_name.slen && pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0) == NULL && + (PJ_STUN_IS_RESPONSE(msg->hdr.type) || + (PJ_STUN_IS_REQUEST(msg->hdr.type) && msg->hdr.magic == PJ_STUN_MAGIC))) { + pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE, &sess->srv_name); + } + + if (pj_stun_auth_valid_for_msg(msg) && auth_info) { + realm = auth_info->realm; + username = auth_info->username; + nonce = auth_info->nonce; + auth_key = auth_info->auth_key; + } else { + realm.slen = username.slen = nonce.slen = auth_key.slen = 0; + } + + /* Create and add USERNAME attribute if needed */ + if (username.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) { + status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_USERNAME, &username); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Add REALM only when long term credential is used */ + if (realm.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) { + status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_REALM, &realm); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Add NONCE when desired */ + if (nonce.slen && (PJ_STUN_IS_REQUEST(msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))) { + status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_NONCE, &nonce); + } + + /* Add MESSAGE-INTEGRITY attribute */ + if (username.slen && auth_key.slen) { + status = pj_stun_msg_add_msgint_attr(pool, msg); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Add FINGERPRINT attribute if necessary */ + if (sess->use_fingerprint) { + status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + return PJ_SUCCESS; +} + +static pj_status_t handle_auth_challenge(pj_stun_session *sess, const pj_stun_tx_data *request, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, + unsigned src_addr_len, pj_bool_t *notify_user) +{ + const pj_stun_errcode_attr *ea; + + *notify_user = PJ_TRUE; + + if (response == NULL) + return PJ_SUCCESS; + + if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM) + return PJ_SUCCESS; + + if (!PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { + sess->auth_retry = 0; + return PJ_SUCCESS; + } + + ea = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (!ea) { + PJ_LOG(4, (SNAME(sess), "Invalid error response: no ERROR-CODE" + " attribute")); + *notify_user = PJ_FALSE; + return PJNATH_EINSTUNMSG; + } + + if (ea->err_code == PJ_STUN_SC_UNAUTHORIZED || ea->err_code == PJ_STUN_SC_STALE_NONCE) { + const pj_stun_nonce_attr *anonce; + const pj_stun_realm_attr *arealm; + pj_stun_tx_data *tdata; + unsigned i; + pj_status_t status; + + anonce = (const pj_stun_nonce_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_NONCE, 0); + if (!anonce) { + PJ_LOG(4, (SNAME(sess), "Invalid response: missing NONCE")); + *notify_user = PJ_FALSE; + return PJNATH_EINSTUNMSG; + } + + /* Bail out if we've supplied the correct nonce */ + if (pj_strcmp(&anonce->value, &sess->next_nonce) == 0) { + return PJ_SUCCESS; + } + + /* Bail out if we've tried too many */ + if (++sess->auth_retry > 3) { + PJ_LOG(4, (SNAME(sess), "Error: authentication failed (too " + "many retries)")); + return PJ_STATUS_FROM_STUN_CODE(401); + } + + /* Save next_nonce */ + pj_strdup(sess->pool, &sess->next_nonce, &anonce->value); + + /* Copy the realm from the response */ + arealm = (pj_stun_realm_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_REALM, 0); + if (arealm) { + pj_strdup(sess->pool, &sess->server_realm, &arealm->value); + while (sess->server_realm.slen && !sess->server_realm.ptr[sess->server_realm.slen - 1]) { + --sess->server_realm.slen; + } + } + + /* Create new request */ + status = pj_stun_session_create_req(sess, request->msg->hdr.type, request->msg->hdr.magic, NULL, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Duplicate all the attributes in the old request, except + * USERNAME, REALM, M-I, and NONCE, which will be filled in + * later. + */ + for (i = 0; i < request->msg->attr_count; ++i) { + const pj_stun_attr_hdr *asrc = request->msg->attr[i]; + + if (asrc->type == PJ_STUN_ATTR_USERNAME || asrc->type == PJ_STUN_ATTR_REALM || + asrc->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY || asrc->type == PJ_STUN_ATTR_NONCE) { + continue; + } + + tdata->msg->attr[tdata->msg->attr_count++] = pj_stun_attr_clone(tdata->pool, asrc); + } + + /* Will retry the request with authentication, no need to + * notify user. + */ + *notify_user = PJ_FALSE; + + PJ_LOG(4, (SNAME(sess), "Retrying request with new authentication")); + + /* Retry the request */ + status = + pj_stun_session_send_msg(sess, request->token, PJ_TRUE, request->retransmit, src_addr, src_addr_len, tdata); + + } else { + sess->auth_retry = 0; + } + + return PJ_SUCCESS; +} + +static void stun_tsx_on_complete(pj_stun_client_tsx *tsx, pj_status_t status, const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_session *sess; + pj_bool_t notify_user = PJ_TRUE; + pj_stun_tx_data *tdata; + + tdata = (pj_stun_tx_data *)pj_stun_client_tsx_get_data(tsx); + sess = tdata->sess; + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_stun_msg_destroy_tdata(sess, tdata); + pj_grp_lock_release(sess->grp_lock); + return; + } + + /* Handle authentication challenge */ + handle_auth_challenge(sess, tdata, response, src_addr, src_addr_len, ¬ify_user); + + if (notify_user && sess->cb.on_request_complete) { + (*sess->cb.on_request_complete)(sess, status, tdata->token, tdata, response, src_addr, src_addr_len); + } + + /* Destroy the transmit data. This will remove the transaction + * from the pending list too. + */ + if (status == PJNATH_ESTUNTIMEDOUT) + destroy_tdata(tdata, PJ_TRUE); + else + destroy_tdata(tdata, PJ_FALSE); + tdata = NULL; + + pj_grp_lock_release(sess->grp_lock); +} + +static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx, const void *stun_pkt, pj_size_t pkt_size) +{ + pj_stun_tx_data *tdata; + pj_stun_session *sess; + pj_status_t status; + + tdata = (pj_stun_tx_data *)pj_stun_client_tsx_get_data(tsx); + sess = tdata->sess; + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + + if (sess->is_destroying) { + /* Stray timer */ + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt, pkt_size, tdata->dst_addr, tdata->addr_len); + if (pj_grp_lock_release(sess->grp_lock)) + return PJ_EGONE; + + return status; +} + +/* **************************************************************************/ + +PJ_DEF(pj_status_t) +pj_stun_session_create(pj_stun_config *cfg, const char *name, const pj_stun_session_cb *cb, pj_bool_t fingerprint, + pj_grp_lock_t *grp_lock, pj_stun_session **p_sess) +{ + pj_pool_t *pool; + pj_stun_session *sess; + pj_status_t status; + + PJ_ASSERT_RETURN(cfg && cb && p_sess, PJ_EINVAL); + + if (name == NULL) + name = "stuse%p"; + + pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_STUN_SESS, PJNATH_POOL_INC_STUN_SESS, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + sess = PJ_POOL_ZALLOC_T(pool, pj_stun_session); + sess->cfg = cfg; + sess->pool = pool; + pj_memcpy(&sess->cb, cb, sizeof(*cb)); + sess->use_fingerprint = fingerprint; + sess->log_flag = 0xFFFF; + + if (grp_lock) { + sess->grp_lock = grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(sess->grp_lock); + pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &stun_sess_on_destroy); + + pj_stun_session_set_software_name(sess, &cfg->software_name); + + sess->rx_pool = pj_pool_create(sess->cfg->pf, name, PJNATH_POOL_LEN_STUN_TDATA, PJNATH_POOL_INC_STUN_TDATA, NULL); + + pj_list_init(&sess->pending_request_list); + pj_list_init(&sess->cached_response_list); + + *p_sess = sess; + + return PJ_SUCCESS; +} + +static void stun_sess_on_destroy(void *comp) +{ + pj_stun_session *sess = (pj_stun_session *)comp; + + while (!pj_list_empty(&sess->pending_request_list)) { + pj_stun_tx_data *tdata = sess->pending_request_list.next; + destroy_tdata(tdata, PJ_TRUE); + } + + pj_pool_safe_release(&sess->rx_pool); + pj_pool_safe_release(&sess->pool); + + TRACE_((THIS_FILE, "STUN session %p destroyed", sess)); +} + +PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess) +{ + pj_stun_tx_data *tdata; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + TRACE_((SNAME(sess), "STUN session %p destroy request, ref_cnt=%d", sess, pj_grp_lock_get_ref(sess->grp_lock))); + + pj_grp_lock_acquire(sess->grp_lock); + + if (sess->is_destroying) { + /* Prevent from decrementing the ref counter more than once */ + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + sess->is_destroying = PJ_TRUE; + + /* We need to stop transactions because they are + * holding the group lock's reference counter while retransmitting. + */ + tdata = sess->pending_request_list.next; + while (tdata != &sess->pending_request_list) { + if (tdata->client_tsx) + pj_stun_client_tsx_stop(tdata->client_tsx); + tdata = tdata->next; + } + + /* Destroy cached response within session lock protection to avoid + * race scenario with on_cache_timeout(). + */ + while (!pj_list_empty(&sess->cached_response_list)) { + pj_stun_tx_data *tmp_tdata = sess->cached_response_list.next; + destroy_tdata(tmp_tdata, PJ_TRUE); + } + + pj_grp_lock_dec_ref(sess->grp_lock); + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_session_set_user_data(pj_stun_session *sess, void *user_data) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + pj_grp_lock_acquire(sess->grp_lock); + sess->user_data = user_data; + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +PJ_DEF(void *) pj_stun_session_get_user_data(pj_stun_session *sess) +{ + PJ_ASSERT_RETURN(sess, NULL); + return sess->user_data; +} + +PJ_DEF(pj_grp_lock_t *) pj_stun_session_get_grp_lock(pj_stun_session *sess) +{ + PJ_ASSERT_RETURN(sess, NULL); + return sess->grp_lock; +} + +PJ_DEF(pj_status_t) pj_stun_session_set_software_name(pj_stun_session *sess, const pj_str_t *sw) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + pj_grp_lock_acquire(sess->grp_lock); + if (sw && sw->slen) + pj_strdup(sess->pool, &sess->srv_name, sw); + else + sess->srv_name.slen = 0; + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_session_set_credential(pj_stun_session *sess, pj_stun_auth_type auth_type, const pj_stun_auth_cred *cred) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + sess->auth_type = auth_type; + if (cred) { + pj_stun_auth_cred_dup(sess->pool, &sess->cred, cred); + } else { + sess->auth_type = PJ_STUN_AUTH_NONE; + pj_bzero(&sess->cred, sizeof(sess->cred)); + } + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_stun_session_set_log(pj_stun_session *sess, unsigned flags) +{ + PJ_ASSERT_ON_FAIL(sess, return ); + sess->log_flag = flags; +} + +PJ_DEF(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, pj_bool_t use) +{ + pj_bool_t old_use; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + old_use = sess->use_fingerprint; + sess->use_fingerprint = use; + return old_use; +} + +static pj_status_t get_auth(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + if (sess->cred.type == PJ_STUN_AUTH_CRED_STATIC) { + // tdata->auth_info.realm = sess->cred.data.static_cred.realm; + tdata->auth_info.realm = sess->server_realm; + tdata->auth_info.username = sess->cred.data.static_cred.username; + tdata->auth_info.nonce = sess->cred.data.static_cred.nonce; + + pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key, &tdata->auth_info.realm, &tdata->auth_info.username, + sess->cred.data.static_cred.data_type, &sess->cred.data.static_cred.data); + + } else if (sess->cred.type == PJ_STUN_AUTH_CRED_DYNAMIC) { + pj_str_t password; + void *user_data = sess->cred.data.dyn_cred.user_data; + pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN; + pj_status_t rc; + + rc = (*sess->cred.data.dyn_cred.get_cred)(tdata->msg, user_data, tdata->pool, &tdata->auth_info.realm, + &tdata->auth_info.username, &tdata->auth_info.nonce, &data_type, + &password); + if (rc != PJ_SUCCESS) + return rc; + + pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key, &tdata->auth_info.realm, &tdata->auth_info.username, + data_type, &password); + + } else { + pj_assert(!"Unknown credential type"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_session_create_req(pj_stun_session *sess, int method, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_tx_data **p_tdata) +{ + pj_stun_tx_data *tdata = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = create_tdata(sess, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create STUN message */ + status = pj_stun_msg_create(tdata->pool, method, magic, tsx_id, &tdata->msg); + if (status != PJ_SUCCESS) + goto on_error; + + /* copy the request's transaction ID as the transaction key. */ + pj_assert(sizeof(tdata->msg_key) == sizeof(tdata->msg->hdr.tsx_id)); + tdata->msg_magic = tdata->msg->hdr.magic; + pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id, sizeof(tdata->msg->hdr.tsx_id)); + + /* Get authentication information for the request */ + if (sess->auth_type == PJ_STUN_AUTH_NONE) { + /* No authentication */ + + } else if (sess->auth_type == PJ_STUN_AUTH_SHORT_TERM) { + /* MUST put authentication in request */ + status = get_auth(sess, tdata); + if (status != PJ_SUCCESS) + goto on_error; + + } else if (sess->auth_type == PJ_STUN_AUTH_LONG_TERM) { + /* Only put authentication information if we've received + * response from server. + */ + if (sess->next_nonce.slen != 0) { + status = get_auth(sess, tdata); + if (status != PJ_SUCCESS) + goto on_error; + tdata->auth_info.nonce = sess->next_nonce; + tdata->auth_info.realm = sess->server_realm; + } + + } else { + pj_assert(!"Invalid authentication type"); + status = PJ_EBUG; + goto on_error; + } + + *p_tdata = tdata; + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + +on_error: + if (tdata) + pj_pool_safe_release(&tdata->pool); + pj_grp_lock_release(sess->grp_lock); + return status; +} + +PJ_DEF(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess, int msg_type, pj_stun_tx_data **p_tdata) +{ + pj_stun_tx_data *tdata = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = create_tdata(sess, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* Create STUN message */ + msg_type |= PJ_STUN_INDICATION_BIT; + status = pj_stun_msg_create(tdata->pool, msg_type, PJ_STUN_MAGIC, NULL, &tdata->msg); + if (status != PJ_SUCCESS) { + pj_pool_safe_release(&tdata->pool); + pj_grp_lock_release(sess->grp_lock); + return status; + } + + *p_tdata = tdata; + + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +/* + * Create a STUN response message. + */ +PJ_DEF(pj_status_t) +pj_stun_session_create_res(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned err_code, + const pj_str_t *err_msg, pj_stun_tx_data **p_tdata) +{ + pj_status_t status; + pj_stun_tx_data *tdata = NULL; + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = create_tdata(sess, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* Create STUN response message */ + status = pj_stun_msg_create_response(tdata->pool, rdata->msg, err_code, err_msg, &tdata->msg); + if (status != PJ_SUCCESS) { + pj_pool_safe_release(&tdata->pool); + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* copy the request's transaction ID as the transaction key. */ + pj_assert(sizeof(tdata->msg_key) == sizeof(rdata->msg->hdr.tsx_id)); + tdata->msg_magic = rdata->msg->hdr.magic; + pj_memcpy(tdata->msg_key, rdata->msg->hdr.tsx_id, sizeof(rdata->msg->hdr.tsx_id)); + + /* copy the credential found in the request */ + pj_stun_req_cred_info_dup(tdata->pool, &tdata->auth_info, &rdata->info); + + *p_tdata = tdata; + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/* Print outgoing message to log */ +static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg, unsigned pkt_size, const pj_sockaddr_t *addr) +{ + char dst_name[PJ_INET6_ADDRSTRLEN + 10]; + + if ((PJ_STUN_IS_REQUEST(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_TX_REQ) == 0) || + (PJ_STUN_IS_RESPONSE(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_TX_RES) == 0) || + (PJ_STUN_IS_INDICATION(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_TX_IND) == 0)) { + return; + } + + pj_sockaddr_print(addr, dst_name, sizeof(dst_name), 3); + + PJ_LOG(5, (SNAME(sess), + "TX %d bytes STUN message to %s:\n" + "--- begin STUN message ---\n" + "%s" + "--- end of STUN message ---\n", + pkt_size, dst_name, pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf), NULL))); +} + +PJ_DEF(pj_status_t) +pj_stun_session_send_msg(pj_stun_session *sess, void *token, pj_bool_t cache_res, pj_bool_t retransmit, + const pj_sockaddr_t *server, unsigned addr_len, pj_stun_tx_data *tdata) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + pj_log_push_indent(); + + /* Allocate packet */ + tdata->max_len = PJ_STUN_MAX_PKT_LEN; + tdata->pkt = pj_pool_zalloc(tdata->pool, tdata->max_len); + + tdata->token = token; + tdata->retransmit = retransmit; + + /* Apply options */ + status = apply_msg_options(sess, tdata->pool, &tdata->auth_info, tdata->msg); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error applying options", status); + goto on_return; + } + + /* Encode message */ + status = pj_stun_msg_encode(tdata->msg, (pj_uint8_t *)tdata->pkt, tdata->max_len, 0, &tdata->auth_info.auth_key, + &tdata->pkt_size); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "STUN encode() error", status); + goto on_return; + } + + /* Dump packet */ + dump_tx_msg(sess, tdata->msg, (unsigned)tdata->pkt_size, server); + + /* If this is a STUN request message, then send the request with + * a new STUN client transaction. + */ + if (PJ_STUN_IS_REQUEST(tdata->msg->hdr.type)) { + + /* Create STUN client transaction */ + status = pj_stun_client_tsx_create(sess->cfg, tdata->pool, sess->grp_lock, &tsx_cb, &tdata->client_tsx); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_stun_client_tsx_set_data(tdata->client_tsx, (void *)tdata); + + /* Save the remote address */ + tdata->addr_len = addr_len; + tdata->dst_addr = server; + + /* Send the request! */ + status = pj_stun_client_tsx_send_msg(tdata->client_tsx, retransmit, tdata->pkt, (unsigned)tdata->pkt_size); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error sending STUN request", status); + goto on_return; + } + + /* Add to pending request list */ + tsx_add(sess, tdata); + + } else { + /* Otherwise for non-request message, send directly to transport. */ + if (cache_res && + (PJ_STUN_IS_SUCCESS_RESPONSE(tdata->msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(tdata->msg->hdr.type))) { + /* Requested to keep the response in the cache */ + pj_time_val timeout; + + status = pj_grp_lock_create(tdata->pool, NULL, &tdata->grp_lock); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error creating group lock", status); + goto on_return; + } + pj_grp_lock_add_ref(tdata->grp_lock); + pj_grp_lock_add_handler(tdata->grp_lock, tdata->pool, tdata, &tdata_on_destroy); + + /* Also add ref session group lock to make sure that the session + * is still valid when cache timeout callback is called. + */ + pj_grp_lock_add_ref(sess->grp_lock); + + pj_memset(&tdata->res_timer, 0, sizeof(tdata->res_timer)); + pj_timer_entry_init(&tdata->res_timer, PJ_FALSE, tdata, &on_cache_timeout); + + timeout.sec = sess->cfg->res_cache_msec / 1000; + timeout.msec = sess->cfg->res_cache_msec % 1000; + + status = pj_timer_heap_schedule_w_grp_lock(sess->cfg->timer_heap, &tdata->res_timer, &timeout, PJ_TRUE, + tdata->grp_lock); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error scheduling response timer", status); + goto on_return; + } + + pj_list_push_back(&sess->cached_response_list, tdata); + } + + /* Send to transport directly. */ + status = sess->cb.on_send_msg(sess, token, tdata->pkt, tdata->pkt_size, server, addr_len); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error sending STUN request", status); + goto on_return; + } + + /* Destroy only when response is not cached*/ + if (tdata->res_timer.id == 0) { + pj_stun_msg_destroy_tdata(sess, tdata); + } + } + +on_return: + pj_log_pop_indent(); + + if (pj_grp_lock_release(sess->grp_lock)) + return PJ_EGONE; + + return status; +} + +/* + * Create and send STUN response message. + */ +PJ_DEF(pj_status_t) +pj_stun_session_respond(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned code, const char *errmsg, + void *token, pj_bool_t cache, const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_status_t status; + pj_str_t reason; + pj_stun_tx_data *tdata; + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = pj_stun_session_create_res(sess, rdata, code, (errmsg ? pj_cstr(&reason, errmsg) : NULL), &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + status = pj_stun_session_send_msg(sess, token, cache, PJ_FALSE, dst_addr, addr_len, tdata); + + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * Cancel outgoing STUN transaction. + */ +PJ_DEF(pj_status_t) +pj_stun_session_cancel_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t notify, pj_status_t notify_status) +{ + PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(!notify || notify_status != PJ_SUCCESS, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + if (notify) { + (sess->cb.on_request_complete)(sess, notify_status, tdata->token, tdata, NULL, NULL, 0); + } + + /* Just destroy tdata. This will destroy the transaction as well */ + pj_stun_msg_destroy_tdata(sess, tdata); + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Explicitly request retransmission of the request. + */ +PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t mod_count) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = pj_stun_client_tsx_retransmit(tdata->client_tsx, mod_count); + + pj_grp_lock_release(sess->grp_lock); + + return status; +} + +/* Send response */ +static pj_status_t send_response(pj_stun_session *sess, void *token, pj_pool_t *pool, pj_stun_msg *response, + const pj_stun_req_cred_info *auth_info, pj_bool_t retransmission, + const pj_sockaddr_t *addr, unsigned addr_len) +{ + pj_uint8_t *out_pkt; + pj_size_t out_max_len, out_len; + pj_status_t status; + + /* Apply options */ + if (!retransmission) { + status = apply_msg_options(sess, pool, auth_info, response); + if (status != PJ_SUCCESS) + return status; + } + + /* Alloc packet buffer */ + out_max_len = PJ_STUN_MAX_PKT_LEN; + out_pkt = (pj_uint8_t *)pj_pool_alloc(pool, out_max_len); + + /* Encode */ + status = pj_stun_msg_encode(response, out_pkt, out_max_len, 0, &auth_info->auth_key, &out_len); + if (status != PJ_SUCCESS) { + LOG_ERR_(sess, "Error encoding message", status); + return status; + } + + /* Print log */ + dump_tx_msg(sess, response, (unsigned)out_len, addr); + + /* Send packet */ + status = sess->cb.on_send_msg(sess, token, out_pkt, (unsigned)out_len, addr, addr_len); + + return status; +} + +/* Authenticate incoming message */ +static pj_status_t authenticate_req(pj_stun_session *sess, void *token, const pj_uint8_t *pkt, unsigned pkt_len, + pj_stun_rx_data *rdata, pj_pool_t *tmp_pool, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_msg *response; + pj_status_t status; + + if (PJ_STUN_IS_ERROR_RESPONSE(rdata->msg->hdr.type) || sess->auth_type == PJ_STUN_AUTH_NONE) { + return PJ_SUCCESS; + } + + status = pj_stun_authenticate_request(pkt, pkt_len, rdata->msg, &sess->cred, tmp_pool, &rdata->info, &response); + if (status != PJ_SUCCESS && response != NULL) { + PJ_PERROR(5, (SNAME(sess), status, "Message authentication failed")); + send_response(sess, token, tmp_pool, response, &rdata->info, PJ_FALSE, src_addr, src_addr_len); + } + + return status; +} + +/* Handle incoming response */ +static pj_status_t on_incoming_response(pj_stun_session *sess, unsigned options, const pj_uint8_t *pkt, + unsigned pkt_len, pj_stun_msg *msg, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + /* Lookup pending client transaction */ + tdata = tsx_lookup(sess, msg); + if (tdata == NULL) { + PJ_LOG(5, (SNAME(sess), "Transaction not found, response silently discarded")); + return PJ_SUCCESS; + } + + if (sess->auth_type == PJ_STUN_AUTH_NONE) + options |= PJ_STUN_NO_AUTHENTICATE; + + /* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE + * is specified in the option. + */ + if ((options & PJ_STUN_NO_AUTHENTICATE) == 0 && tdata->auth_info.auth_key.slen != 0 && + pj_stun_auth_valid_for_msg(msg)) { + status = pj_stun_authenticate_response(pkt, pkt_len, msg, &tdata->auth_info.auth_key); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (SNAME(sess), status, "Response authentication failed")); + return status; + } + } + + /* Pass the response to the transaction. + * If the message is accepted, transaction callback will be called, + * and this will call the session callback too. + */ + status = pj_stun_client_tsx_on_rx_msg(tdata->client_tsx, msg, src_addr, src_addr_len); + if (status != PJ_SUCCESS) { + return status; + } + + return PJ_SUCCESS; +} + +/* For requests, check if we cache the response */ +static pj_status_t check_cached_response(pj_stun_session *sess, pj_pool_t *tmp_pool, const pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_tx_data *t; + + /* First lookup response in response cache */ + t = sess->cached_response_list.next; + while (t != &sess->cached_response_list) { + if (t->msg_magic == msg->hdr.magic && t->msg->hdr.type == msg->hdr.type && + pj_memcmp(t->msg_key, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)) == 0) { + break; + } + t = t->next; + } + + if (t != &sess->cached_response_list) { + /* Found response in the cache */ + + PJ_LOG(5, (SNAME(sess), "Request retransmission, sending cached response")); + + send_response(sess, t->token, tmp_pool, t->msg, &t->auth_info, PJ_TRUE, src_addr, src_addr_len); + return PJ_SUCCESS; + } + + return PJ_ENOTFOUND; +} + +/* Handle incoming request */ +static pj_status_t on_incoming_request(pj_stun_session *sess, unsigned options, void *token, pj_pool_t *tmp_pool, + const pj_uint8_t *in_pkt, unsigned in_pkt_len, pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_rx_data rdata; + pj_status_t status; + + /* Init rdata */ + rdata.msg = msg; + pj_bzero(&rdata.info, sizeof(rdata.info)); + + if (sess->auth_type == PJ_STUN_AUTH_NONE) + options |= PJ_STUN_NO_AUTHENTICATE; + + /* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE + * is specified in the option. + */ + if ((options & PJ_STUN_NO_AUTHENTICATE) == 0) { + status = authenticate_req(sess, token, (const pj_uint8_t *)in_pkt, in_pkt_len, &rdata, tmp_pool, src_addr, + src_addr_len); + if (status != PJ_SUCCESS) { + return status; + } + } + + /* Distribute to handler, or respond with Bad Request */ + if (sess->cb.on_rx_request) { + status = (*sess->cb.on_rx_request)(sess, in_pkt, in_pkt_len, &rdata, token, src_addr, src_addr_len); + } else { + pj_str_t err_text; + pj_stun_msg *response; + + err_text = pj_str("Callback is not set to handle request"); + status = pj_stun_msg_create_response(tmp_pool, msg, PJ_STUN_SC_BAD_REQUEST, &err_text, &response); + if (status == PJ_SUCCESS && response) { + status = send_response(sess, token, tmp_pool, response, NULL, PJ_FALSE, src_addr, src_addr_len); + } + } + + return status; +} + +/* Handle incoming indication */ +static pj_status_t on_incoming_indication(pj_stun_session *sess, void *token, pj_pool_t *tmp_pool, + const pj_uint8_t *in_pkt, unsigned in_pkt_len, const pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + PJ_UNUSED_ARG(tmp_pool); + + /* Distribute to handler */ + if (sess->cb.on_rx_indication) { + return (*sess->cb.on_rx_indication)(sess, in_pkt, in_pkt_len, msg, token, src_addr, src_addr_len); + } else { + return PJ_SUCCESS; + } +} + +/* Print outgoing message to log */ +static void dump_rx_msg(pj_stun_session *sess, const pj_stun_msg *msg, unsigned pkt_size, const pj_sockaddr_t *addr) +{ + char src_info[PJ_INET6_ADDRSTRLEN + 10]; + + if ((PJ_STUN_IS_REQUEST(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_RX_REQ) == 0) || + (PJ_STUN_IS_RESPONSE(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_RX_RES) == 0) || + (PJ_STUN_IS_INDICATION(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_RX_IND) == 0)) { + return; + } + + pj_sockaddr_print(addr, src_info, sizeof(src_info), 3); + + PJ_LOG(5, (SNAME(sess), + "RX %d bytes STUN message from %s:\n" + "--- begin STUN message ---\n" + "%s" + "--- end of STUN message ---\n", + pkt_size, src_info, pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf), NULL))); +} + +/* Incoming packet */ +PJ_DEF(pj_status_t) +pj_stun_session_on_rx_pkt(pj_stun_session *sess, const void *packet, pj_size_t pkt_size, unsigned options, void *token, + pj_size_t *parsed_len, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_msg *msg, *response; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + pj_log_push_indent(); + + /* Reset pool */ + pj_pool_reset(sess->rx_pool); + + /* Try to parse the message */ + status = + pj_stun_msg_decode(sess->rx_pool, (const pj_uint8_t *)packet, pkt_size, options, &msg, parsed_len, &response); + if (status != PJ_SUCCESS) { + LOG_ERR_(sess, "STUN msg_decode() error", status); + if (response) { + send_response(sess, token, sess->rx_pool, response, NULL, PJ_FALSE, src_addr, src_addr_len); + } + goto on_return; + } + + dump_rx_msg(sess, msg, (unsigned)pkt_size, src_addr); + + /* For requests, check if we have cached response */ + status = check_cached_response(sess, sess->rx_pool, msg, src_addr, src_addr_len); + if (status == PJ_SUCCESS) { + goto on_return; + } + + /* Handle message */ + if (PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { + status = on_incoming_response(sess, options, (const pj_uint8_t *)packet, (unsigned)pkt_size, msg, src_addr, + src_addr_len); + + } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) { + + status = on_incoming_request(sess, options, token, sess->rx_pool, (const pj_uint8_t *)packet, + (unsigned)pkt_size, msg, src_addr, src_addr_len); + + } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) { + + status = on_incoming_indication(sess, token, sess->rx_pool, (const pj_uint8_t *)packet, (unsigned)pkt_size, msg, + src_addr, src_addr_len); + + } else { + pj_assert(!"Unexpected!"); + status = PJ_EBUG; + } + +on_return: + pj_log_pop_indent(); + + if (pj_grp_lock_release(sess->grp_lock)) + return PJ_EGONE; + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c new file mode 100755 index 000000000..00e29de5f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c @@ -0,0 +1,921 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 1 +#define TRACE_(x) PJ_LOG(5, x) +#else +#define TRACE_(x) +#endif + +enum { MAX_BIND_RETRY = 100 }; + +struct pj_stun_sock { + char *obj_name; /* Log identification */ + pj_pool_t *pool; /* Pool */ + void *user_data; /* Application user data */ + pj_bool_t is_destroying; /* Destroy already called */ + int af; /* Address family */ + pj_stun_config stun_cfg; /* STUN config (ioqueue etc)*/ + pj_stun_sock_cb cb; /* Application callbacks */ + + int ka_interval; /* Keep alive interval */ + pj_timer_entry ka_timer; /* Keep alive timer. */ + + pj_sockaddr srv_addr; /* Resolved server addr */ + pj_sockaddr mapped_addr; /* Our public address */ + + pj_dns_srv_async_query *q; /* Pending DNS query */ + pj_sock_t sock_fd; /* Socket descriptor */ + pj_activesock_t *active_sock; /* Active socket object */ + pj_ioqueue_op_key_t send_key; /* Default send key for app */ + pj_ioqueue_op_key_t int_send_key; /* Send key for internal */ + pj_status_t last_err; /* Last error status */ + + pj_uint16_t tsx_id[6]; /* .. to match STUN msg */ + pj_stun_session *stun_sess; /* STUN session */ + pj_grp_lock_t *grp_lock; /* Session group lock */ +}; + +/* + * Prototypes for static functions + */ + +/* Destructor for group lock */ +static void stun_sock_destructor(void *obj); + +/* This callback is called by the STUN session to send packet */ +static pj_status_t sess_on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + +/* This callback is called by the STUN session when outgoing transaction + * is complete + */ +static void sess_on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +/* DNS resolver callback */ +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec); + +/* Start sending STUN Binding request */ +static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock); + +/* Callback from active socket when incoming packet is received */ +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + +/* Callback from active socket about send status */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + +/* Schedule keep-alive timer */ +static void start_ka_timer(pj_stun_sock *stun_sock); + +/* Keep-alive timer callback */ +static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te); + +#define INTERNAL_MSG_TOKEN (void *)(pj_ssize_t)1 + +/* + * Retrieve the name representing the specified operation. + */ +PJ_DEF(const char *) pj_stun_sock_op_name(pj_stun_sock_op op) +{ + const char *names[] = {"?", "DNS resolution", "STUN Binding request", "Keep-alive", "Mapped addr. changed"}; + + return op < PJ_ARRAY_SIZE(names) ? names[op] : "???"; +} + +/* + * Initialize the STUN transport setting with its default values. + */ +PJ_DEF(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->max_pkt_size = PJ_STUN_SOCK_PKT_LEN; + cfg->async_cnt = 1; + cfg->ka_interval = PJ_STUN_KEEP_ALIVE_SEC; + cfg->qos_type = PJ_QOS_TYPE_BEST_EFFORT; + cfg->qos_ignore_error = PJ_TRUE; +} + +/* Check that configuration setting is valid */ +static pj_bool_t pj_stun_sock_cfg_is_valid(const pj_stun_sock_cfg *cfg) +{ + return cfg->max_pkt_size > 1 && cfg->async_cnt >= 1; +} + +/* + * Create the STUN transport using the specified configuration. + */ +PJ_DEF(pj_status_t) +pj_stun_sock_create(pj_stun_config *stun_cfg, const char *name, int af, const pj_stun_sock_cb *cb, + const pj_stun_sock_cfg *cfg, void *user_data, pj_stun_sock **p_stun_sock) +{ + pj_pool_t *pool; + pj_stun_sock *stun_sock; + pj_stun_sock_cfg default_cfg; + pj_sockaddr bound_addr; + unsigned i; + pj_uint16_t max_bind_retry; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && cb && p_stun_sock, PJ_EINVAL); + PJ_ASSERT_RETURN(af == pj_AF_INET() || af == pj_AF_INET6(), PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(!cfg || pj_stun_sock_cfg_is_valid(cfg), PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_status, PJ_EINVAL); + + status = pj_stun_config_check_valid(stun_cfg); + if (status != PJ_SUCCESS) + return status; + + if (name == NULL) + name = "stuntp%p"; + + if (cfg == NULL) { + pj_stun_sock_cfg_default(&default_cfg); + cfg = &default_cfg; + } + + /* Create structure */ + pool = pj_pool_create(stun_cfg->pf, name, 256, 512, NULL); + stun_sock = PJ_POOL_ZALLOC_T(pool, pj_stun_sock); + stun_sock->pool = pool; + stun_sock->obj_name = pool->obj_name; + stun_sock->user_data = user_data; + stun_sock->af = af; + stun_sock->sock_fd = PJ_INVALID_SOCKET; + pj_memcpy(&stun_sock->stun_cfg, stun_cfg, sizeof(*stun_cfg)); + pj_memcpy(&stun_sock->cb, cb, sizeof(*cb)); + + stun_sock->ka_interval = cfg->ka_interval; + if (stun_sock->ka_interval == 0) + stun_sock->ka_interval = PJ_STUN_KEEP_ALIVE_SEC; + + if (cfg->grp_lock) { + stun_sock->grp_lock = cfg->grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &stun_sock->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(stun_sock->grp_lock); + pj_grp_lock_add_handler(stun_sock->grp_lock, pool, stun_sock, &stun_sock_destructor); + + /* Create socket and bind socket */ + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &stun_sock->sock_fd); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(stun_sock->sock_fd, cfg->qos_type, &cfg->qos_params, 2, stun_sock->obj_name, NULL); + if (status != PJ_SUCCESS && !cfg->qos_ignore_error) + goto on_error; + + /* Apply socket buffer size */ + if (cfg->so_rcvbuf_size > 0) { + unsigned sobuf_size = cfg->so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(stun_sock->sock_fd, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (stun_sock->obj_name, status, "Failed setting SO_RCVBUF")); + } else { + if (sobuf_size < cfg->so_rcvbuf_size) { + PJ_LOG(4, (stun_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured, " + "now=%d, configured=%d", + sobuf_size, cfg->so_rcvbuf_size)); + } else { + PJ_LOG(5, (stun_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (cfg->so_sndbuf_size > 0) { + unsigned sobuf_size = cfg->so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(stun_sock->sock_fd, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (stun_sock->obj_name, status, "Failed setting SO_SNDBUF")); + } else { + if (sobuf_size < cfg->so_sndbuf_size) { + PJ_LOG(4, (stun_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured, " + "now=%d, configured=%d", + sobuf_size, cfg->so_sndbuf_size)); + } else { + PJ_LOG(5, (stun_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Bind socket */ + max_bind_retry = MAX_BIND_RETRY; + if (cfg->port_range && cfg->port_range < max_bind_retry) + max_bind_retry = cfg->port_range; + pj_sockaddr_init(af, &bound_addr, NULL, 0); + if (cfg->bound_addr.addr.sa_family == pj_AF_INET() || cfg->bound_addr.addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, &cfg->bound_addr); + } + status = pj_sock_bind_random(stun_sock->sock_fd, &bound_addr, cfg->port_range, max_bind_retry); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create more useful information string about this transport */ +#if 0 + { + pj_sockaddr bound_addr; + int addr_len = sizeof(bound_addr); + + status = pj_sock_getsockname(stun_sock->sock_fd, &bound_addr, + &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + stun_sock->info = pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+10); + pj_sockaddr_print(&bound_addr, stun_sock->info, + PJ_INET6_ADDRSTRLEN, 3); + } +#endif + + /* Init active socket configuration */ + { + pj_activesock_cfg activesock_cfg; + pj_activesock_cb activesock_cb; + + pj_activesock_cfg_default(&activesock_cfg); + activesock_cfg.grp_lock = stun_sock->grp_lock; + activesock_cfg.async_cnt = cfg->async_cnt; + activesock_cfg.concurrency = 0; + + /* Create the active socket */ + pj_bzero(&activesock_cb, sizeof(activesock_cb)); + activesock_cb.on_data_recvfrom = &on_data_recvfrom; + activesock_cb.on_data_sent = &on_data_sent; + status = pj_activesock_create(pool, stun_sock->sock_fd, pj_SOCK_DGRAM(), &activesock_cfg, stun_cfg->ioqueue, + &activesock_cb, stun_sock, &stun_sock->active_sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start asynchronous read operations */ + status = pj_activesock_start_recvfrom(stun_sock->active_sock, pool, cfg->max_pkt_size, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init send keys */ + pj_ioqueue_op_key_init(&stun_sock->send_key, sizeof(stun_sock->send_key)); + pj_ioqueue_op_key_init(&stun_sock->int_send_key, sizeof(stun_sock->int_send_key)); + } + + /* Create STUN session */ + { + pj_stun_session_cb sess_cb; + + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &sess_on_request_complete; + sess_cb.on_send_msg = &sess_on_send_msg; + status = pj_stun_session_create(&stun_sock->stun_cfg, stun_sock->obj_name, &sess_cb, PJ_FALSE, + stun_sock->grp_lock, &stun_sock->stun_sess); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Associate us with the STUN session */ + pj_stun_session_set_user_data(stun_sock->stun_sess, stun_sock); + + /* Initialize random numbers to be used as STUN transaction ID for + * outgoing Binding request. We use the 80bit number to distinguish + * STUN messages we sent with STUN messages that the application sends. + * The last 16bit value in the array is a counter. + */ + for (i = 0; i < PJ_ARRAY_SIZE(stun_sock->tsx_id); ++i) { + stun_sock->tsx_id[i] = (pj_uint16_t)pj_rand(); + } + stun_sock->tsx_id[5] = 0; + + /* Init timer entry */ + stun_sock->ka_timer.cb = &ka_timer_cb; + stun_sock->ka_timer.user_data = stun_sock; + + /* Done */ + *p_stun_sock = stun_sock; + return PJ_SUCCESS; + +on_error: + pj_stun_sock_destroy(stun_sock); + return status; +} + +/* Start socket. */ +PJ_DEF(pj_status_t) +pj_stun_sock_start(pj_stun_sock *stun_sock, const pj_str_t *domain, pj_uint16_t default_port, pj_dns_resolver *resolver) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && domain && default_port, PJ_EINVAL); + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Check whether the domain contains IP address */ + stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)stun_sock->af; + status = pj_inet_pton(stun_sock->af, domain, pj_sockaddr_get_addr(&stun_sock->srv_addr)); + if (status != PJ_SUCCESS) { + stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)0; + } + + /* If resolver is set, try to resolve with DNS SRV first. It + * will fallback to DNS A/AAAA when no SRV record is found. + */ + if (status != PJ_SUCCESS && resolver) { + const pj_str_t res_name = pj_str("_stun._udp."); + unsigned opt; + + pj_assert(stun_sock->q == NULL); + + /* Init DNS resolution option */ + if (stun_sock->af == pj_AF_INET6()) + opt = (PJ_DNS_SRV_RESOLVE_AAAA_ONLY | PJ_DNS_SRV_FALLBACK_AAAA); + else + opt = PJ_DNS_SRV_FALLBACK_A; + + stun_sock->last_err = PJ_SUCCESS; + status = pj_dns_srv_resolve(domain, &res_name, default_port, stun_sock->pool, resolver, opt, stun_sock, + &dns_srv_resolver_cb, &stun_sock->q); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in pj_dns_srv_resolve()")); + } else { + /* DNS SRV callback may have been called here, such as when + * the result is cached, so we need to check the last error + * status. If the callback hasn't been called, processing + * will resume later. + */ + status = stun_sock->last_err; + if (stun_sock->last_err != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in sending Binding request (2)")); + } + } + + } else { + + if (status != PJ_SUCCESS) { + pj_addrinfo ai; + unsigned cnt = 1; + + status = pj_getaddrinfo(stun_sock->af, domain, &cnt, &ai); + if (cnt == 0) + status = PJ_EAFNOTSUP; + + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in pj_getaddrinfo()")); + pj_grp_lock_release(stun_sock->grp_lock); + return status; + } + + pj_sockaddr_cp(&stun_sock->srv_addr, &ai.ai_addr); + } + + pj_sockaddr_set_port(&stun_sock->srv_addr, (pj_uint16_t)default_port); + + /* Start sending Binding request */ + status = get_mapped_addr(stun_sock); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in sending Binding request")); + } + } + + pj_grp_lock_release(stun_sock->grp_lock); + return status; +} + +/* Destructor */ +static void stun_sock_destructor(void *obj) +{ + pj_stun_sock *stun_sock = (pj_stun_sock *)obj; + + if (stun_sock->q) { + pj_dns_srv_cancel_query(stun_sock->q, PJ_FALSE); + stun_sock->q = NULL; + } + + /* + if (stun_sock->stun_sess) { + pj_stun_session_destroy(stun_sock->stun_sess); + stun_sock->stun_sess = NULL; + } + */ + + pj_pool_safe_release(&stun_sock->pool); + + TRACE_(("", "STUN sock %p destroyed", stun_sock)); +} + +/* Destroy */ +PJ_DEF(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *stun_sock) +{ + TRACE_( + (stun_sock->obj_name, "STUN sock %p request, ref_cnt=%d", stun_sock, pj_grp_lock_get_ref(stun_sock->grp_lock))); + + pj_grp_lock_acquire(stun_sock->grp_lock); + if (stun_sock->is_destroying) { + /* Destroy already called */ + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_EINVALIDOP; + } + + stun_sock->is_destroying = PJ_TRUE; + pj_timer_heap_cancel_if_active(stun_sock->stun_cfg.timer_heap, &stun_sock->ka_timer, 0); + + if (stun_sock->active_sock != NULL) { + stun_sock->sock_fd = PJ_INVALID_SOCKET; + pj_activesock_close(stun_sock->active_sock); + } else if (stun_sock->sock_fd != PJ_INVALID_SOCKET) { + pj_sock_close(stun_sock->sock_fd); + stun_sock->sock_fd = PJ_INVALID_SOCKET; + } + + if (stun_sock->stun_sess) { + pj_stun_session_destroy(stun_sock->stun_sess); + } + pj_grp_lock_dec_ref(stun_sock->grp_lock); + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_SUCCESS; +} + +/* Associate user data */ +PJ_DEF(pj_status_t) pj_stun_sock_set_user_data(pj_stun_sock *stun_sock, void *user_data) +{ + PJ_ASSERT_RETURN(stun_sock, PJ_EINVAL); + stun_sock->user_data = user_data; + return PJ_SUCCESS; +} + +/* Get user data */ +PJ_DEF(void *) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock) +{ + PJ_ASSERT_RETURN(stun_sock, NULL); + return stun_sock->user_data; +} + +/* Get group lock */ +PJ_DECL(pj_grp_lock_t *) pj_stun_sock_get_grp_lock(pj_stun_sock *stun_sock) +{ + PJ_ASSERT_RETURN(stun_sock, NULL); + return stun_sock->grp_lock; +} + +/* Notify application that session has failed */ +static pj_bool_t sess_fail(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status) +{ + pj_bool_t ret; + + PJ_PERROR(4, (stun_sock->obj_name, status, "Session failed because %s failed", pj_stun_sock_op_name(op))); + + ret = (*stun_sock->cb.on_status)(stun_sock, op, status); + + return ret; +} + +/* DNS resolver callback */ +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec) +{ + pj_stun_sock *stun_sock = (pj_stun_sock *)user_data; + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Clear query */ + stun_sock->q = NULL; + + /* Handle error */ + if (status != PJ_SUCCESS) { + stun_sock->last_err = status; + sess_fail(stun_sock, PJ_STUN_SOCK_DNS_OP, status); + pj_grp_lock_release(stun_sock->grp_lock); + return; + } + + pj_assert(rec->count); + pj_assert(rec->entry[0].server.addr_count); + pj_assert(rec->entry[0].server.addr[0].af == stun_sock->af); + + /* Set the address */ + pj_sockaddr_init(stun_sock->af, &stun_sock->srv_addr, NULL, rec->entry[0].port); + if (stun_sock->af == pj_AF_INET6()) { + stun_sock->srv_addr.ipv6.sin6_addr = rec->entry[0].server.addr[0].ip.v6; + } else { + stun_sock->srv_addr.ipv4.sin_addr = rec->entry[0].server.addr[0].ip.v4; + } + + /* Start sending Binding request */ + stun_sock->last_err = get_mapped_addr(stun_sock); + + pj_grp_lock_release(stun_sock->grp_lock); +} + +/* Start sending STUN Binding request */ +static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + /* Increment request counter and create STUN Binding request */ + ++stun_sock->tsx_id[5]; + status = pj_stun_session_create_req(stun_sock->stun_sess, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, + (const pj_uint8_t *)stun_sock->tsx_id, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Send request */ + status = pj_stun_session_send_msg(stun_sock->stun_sess, INTERNAL_MSG_TOKEN, PJ_FALSE, PJ_TRUE, &stun_sock->srv_addr, + pj_sockaddr_get_len(&stun_sock->srv_addr), tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + sess_fail(stun_sock, PJ_STUN_SOCK_BINDING_OP, status); + return status; +} + +/* Get info */ +PJ_DEF(pj_status_t) pj_stun_sock_get_info(pj_stun_sock *stun_sock, pj_stun_sock_info *info) +{ + int addr_len; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && info, PJ_EINVAL); + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Copy STUN server address and mapped address */ + pj_memcpy(&info->srv_addr, &stun_sock->srv_addr, sizeof(pj_sockaddr)); + pj_memcpy(&info->mapped_addr, &stun_sock->mapped_addr, sizeof(pj_sockaddr)); + + /* Retrieve bound address */ + addr_len = sizeof(info->bound_addr); + status = pj_sock_getsockname(stun_sock->sock_fd, &info->bound_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(stun_sock->grp_lock); + return status; + } + + /* If socket is bound to a specific interface, then only put that + * interface in the alias list. Otherwise query all the interfaces + * in the host. + */ + if (pj_sockaddr_has_addr(&info->bound_addr)) { + info->alias_cnt = 1; + pj_sockaddr_cp(&info->aliases[0], &info->bound_addr); + } else { + pj_sockaddr def_addr; + pj_uint16_t port = pj_sockaddr_get_port(&info->bound_addr); + pj_enum_ip_option enum_opt; + unsigned i; + + /* Get the default address */ + status = pj_gethostip(stun_sock->af, &def_addr); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in getting default address for STUN info")); + pj_grp_lock_release(stun_sock->grp_lock); + return status; + } + + pj_sockaddr_set_port(&def_addr, port); + + /* Enum all IP interfaces in the host */ + pj_enum_ip_option_default(&enum_opt); + enum_opt.af = stun_sock->af; + enum_opt.omit_deprecated_ipv6 = PJ_TRUE; + info->alias_cnt = PJ_ARRAY_SIZE(info->aliases); + status = pj_enum_ip_interface2(&enum_opt, &info->alias_cnt, info->aliases); + if (status == PJ_ENOTSUP) { + /* Try again without omitting deprecated IPv6 addresses */ + enum_opt.omit_deprecated_ipv6 = PJ_FALSE; + status = pj_enum_ip_interface2(&enum_opt, &info->alias_cnt, info->aliases); + } + + if (status != PJ_SUCCESS) { + /* If enumeration fails, just return the default address */ + PJ_PERROR(4, (stun_sock->obj_name, status, + "Failed in enumerating interfaces for STUN info, " + "returning default address only")); + info->alias_cnt = 1; + pj_sockaddr_cp(&info->aliases[0], &def_addr); + } + + /* Set the port number for each address. + */ + for (i = 0; i < info->alias_cnt; ++i) { + pj_sockaddr_set_port(&info->aliases[i], port); + } + + /* Put the default IP in the first slot */ + for (i = 0; i < info->alias_cnt; ++i) { + if (pj_sockaddr_cmp(&info->aliases[i], &def_addr) == 0) { + if (i != 0) { + pj_sockaddr_cp(&info->aliases[i], &info->aliases[0]); + pj_sockaddr_cp(&info->aliases[0], &def_addr); + } + break; + } + } + } + + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_SUCCESS; +} + +/* Send application data */ +PJ_DEF(pj_status_t) +pj_stun_sock_sendto(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, const void *pkt, unsigned pkt_len, + unsigned flag, const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_ssize_t size; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && pkt && dst_addr && addr_len, PJ_EINVAL); + + pj_grp_lock_acquire(stun_sock->grp_lock); + + if (!stun_sock->active_sock) { + /* We have been shutdown, but this callback may still get called + * by retransmit timer. + */ + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_EINVALIDOP; + } + + if (send_key == NULL) + send_key = &stun_sock->send_key; + + size = pkt_len; + status = pj_activesock_sendto(stun_sock->active_sock, send_key, pkt, &size, flag, dst_addr, addr_len); + + pj_grp_lock_release(stun_sock->grp_lock); + return status; +} + +/* This callback is called by the STUN session to send packet */ +static pj_status_t sess_on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_stun_sock *stun_sock; + pj_ssize_t size; + + stun_sock = (pj_stun_sock *)pj_stun_session_get_user_data(sess); + if (!stun_sock || !stun_sock->active_sock) { + /* We have been shutdown, but this callback may still get called + * by retransmit timer. + */ + return PJ_EINVALIDOP; + } + + pj_assert(token == INTERNAL_MSG_TOKEN); + PJ_UNUSED_ARG(token); + + size = pkt_size; + return pj_activesock_sendto(stun_sock->active_sock, &stun_sock->int_send_key, pkt, &size, 0, dst_addr, addr_len); +} + +/* This callback is called by the STUN session when outgoing transaction + * is complete + */ +static void sess_on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_sock *stun_sock; + const pj_stun_sockaddr_attr *mapped_attr; + pj_stun_sock_op op; + pj_bool_t mapped_changed; + pj_bool_t resched = PJ_TRUE; + + stun_sock = (pj_stun_sock *)pj_stun_session_get_user_data(sess); + if (!stun_sock) + return; + + PJ_UNUSED_ARG(tdata); + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + /* Check if this is a keep-alive or the first Binding request */ + if (pj_sockaddr_has_addr(&stun_sock->mapped_addr)) + op = PJ_STUN_SOCK_KEEP_ALIVE_OP; + else + op = PJ_STUN_SOCK_BINDING_OP; + + /* Handle failure */ + if (status != PJ_SUCCESS) { + resched = sess_fail(stun_sock, op, status); + goto on_return; + } + + /* Get XOR-MAPPED-ADDRESS, or MAPPED-ADDRESS when XOR-MAPPED-ADDRESS + * doesn't exist. + */ + mapped_attr = (const pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mapped_attr == NULL) { + mapped_attr = (const pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); + } + + if (mapped_attr == NULL) { + resched = sess_fail(stun_sock, op, PJNATH_ESTUNNOMAPPEDADDR); + goto on_return; + } + + /* Determine if mapped address has changed, and save the new mapped + * address and call callback if so + */ + mapped_changed = !pj_sockaddr_has_addr(&stun_sock->mapped_addr) || + pj_sockaddr_cmp(&stun_sock->mapped_addr, &mapped_attr->sockaddr) != 0; + if (mapped_changed) { + /* Print mapped adress */ + { + char addrinfo[PJ_INET6_ADDRSTRLEN + 10]; + PJ_LOG(4, (stun_sock->obj_name, "STUN mapped address found/changed: %s", + pj_sockaddr_print(&mapped_attr->sockaddr, addrinfo, sizeof(addrinfo), 3))); + } + + pj_sockaddr_cp(&stun_sock->mapped_addr, &mapped_attr->sockaddr); + + if (op == PJ_STUN_SOCK_KEEP_ALIVE_OP) + op = PJ_STUN_SOCK_MAPPED_ADDR_CHANGE; + } + + /* Notify user */ + resched = (*stun_sock->cb.on_status)(stun_sock, op, PJ_SUCCESS); + +on_return: + /* Start/restart keep-alive timer */ + if (resched) + start_ka_timer(stun_sock); +} + +/* Schedule keep-alive timer */ +static void start_ka_timer(pj_stun_sock *stun_sock) +{ + pj_timer_heap_cancel_if_active(stun_sock->stun_cfg.timer_heap, &stun_sock->ka_timer, 0); + + pj_assert(stun_sock->ka_interval != 0); + if (stun_sock->ka_interval > 0 && !stun_sock->is_destroying) { + pj_time_val delay; + + delay.sec = stun_sock->ka_interval; + delay.msec = 0; + + pj_timer_heap_schedule_w_grp_lock(stun_sock->stun_cfg.timer_heap, &stun_sock->ka_timer, &delay, PJ_TRUE, + stun_sock->grp_lock); + } +} + +/* Keep-alive timer callback */ +static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_stun_sock *stun_sock; + + stun_sock = (pj_stun_sock *)te->user_data; + + PJ_UNUSED_ARG(th); + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Time to send STUN Binding request */ + if (get_mapped_addr(stun_sock) != PJ_SUCCESS) { + pj_grp_lock_release(stun_sock->grp_lock); + return; + } + + /* Next keep-alive timer will be scheduled once the request + * is complete. + */ + pj_grp_lock_release(stun_sock->grp_lock); +} + +/* Callback from active socket when incoming packet is received */ +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status) +{ + pj_stun_sock *stun_sock; + pj_stun_msg_hdr *hdr; + pj_uint16_t type; + + stun_sock = (pj_stun_sock *)pj_activesock_get_user_data(asock); + if (!stun_sock) + return PJ_FALSE; + + /* Log socket error */ + if (status != PJ_SUCCESS) { + PJ_PERROR(2, (stun_sock->obj_name, status, "recvfrom() error")); + return PJ_TRUE; + } + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Check that this is STUN message */ + status = pj_stun_msg_check((const pj_uint8_t *)data, size, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET); + if (status != PJ_SUCCESS) { + /* Not STUN -- give it to application */ + goto process_app_data; + } + + /* Treat packet as STUN header and copy the STUN message type. + * We don't want to access the type directly from the header + * since it may not be properly aligned. + */ + hdr = (pj_stun_msg_hdr *)data; + pj_memcpy(&type, &hdr->type, 2); + type = pj_ntohs(type); + + /* If the packet is a STUN Binding response and part of the + * transaction ID matches our internal ID, then this is + * our internal STUN message (Binding request or keep alive). + * Give it to our STUN session. + */ + if (!PJ_STUN_IS_RESPONSE(type) || PJ_STUN_GET_METHOD(type) != PJ_STUN_BINDING_METHOD || + pj_memcmp(hdr->tsx_id, stun_sock->tsx_id, 10) != 0) { + /* Not STUN Binding response, or STUN transaction ID mismatch. + * This is not our message too -- give it to application. + */ + goto process_app_data; + } + + /* This is our STUN Binding response. Give it to the STUN session */ + status = pj_stun_session_on_rx_pkt(stun_sock->stun_sess, data, size, PJ_STUN_IS_DATAGRAM, NULL, NULL, src_addr, + addr_len); + + status = pj_grp_lock_release(stun_sock->grp_lock); + + return status != PJ_EGONE ? PJ_TRUE : PJ_FALSE; + +process_app_data: + if (stun_sock->cb.on_rx_data) { + (*stun_sock->cb.on_rx_data)(stun_sock, data, (unsigned)size, src_addr, addr_len); + status = pj_grp_lock_release(stun_sock->grp_lock); + return status != PJ_EGONE ? PJ_TRUE : PJ_FALSE; + } + + status = pj_grp_lock_release(stun_sock->grp_lock); + return status != PJ_EGONE ? PJ_TRUE : PJ_FALSE; +} + +/* Callback from active socket about send status */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + pj_stun_sock *stun_sock; + + stun_sock = (pj_stun_sock *)pj_activesock_get_user_data(asock); + if (!stun_sock) + return PJ_FALSE; + + /* Don't report to callback if this is internal message */ + if (send_key == &stun_sock->int_send_key) { + return PJ_TRUE; + } + + /* Report to callback */ + if (stun_sock->cb.on_data_sent) { + pj_bool_t ret; + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* If app gives NULL send_key in sendto() function, then give + * NULL in the callback too + */ + if (send_key == &stun_sock->send_key) + send_key = NULL; + + /* Call callback */ + ret = (*stun_sock->cb.on_data_sent)(stun_sock, send_key, sent); + + pj_grp_lock_release(stun_sock->grp_lock); + return ret; + } + + return PJ_TRUE; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c new file mode 100755 index 000000000..67279bf3b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_transaction.c" +#define TIMER_INACTIVE 0 +#define TIMER_ACTIVE 1 + +struct pj_stun_client_tsx { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_stun_tsx_cb cb; + void *user_data; + pj_grp_lock_t *grp_lock; + + pj_bool_t complete; + + pj_bool_t require_retransmit; + unsigned rto_msec; + pj_timer_entry retransmit_timer; + unsigned transmit_count; + pj_time_val retransmit_time; + pj_timer_heap_t *timer_heap; + + pj_timer_entry destroy_timer; + + void *last_pkt; + unsigned last_pkt_size; +}; + +#if 1 +#define TRACE_(expr) PJ_LOG(5, expr) +#else +#define TRACE_(expr) +#endif + +static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer); +static void destroy_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer); + +/* + * Create a STUN client transaction. + */ +PJ_DEF(pj_status_t) +pj_stun_client_tsx_create(pj_stun_config *cfg, pj_pool_t *pool, pj_grp_lock_t *grp_lock, const pj_stun_tsx_cb *cb, + pj_stun_client_tsx **p_tsx) +{ + pj_stun_client_tsx *tsx; + + PJ_ASSERT_RETURN(cfg && cb && p_tsx, PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL); + + tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx); + tsx->rto_msec = cfg->rto_msec; + tsx->timer_heap = cfg->timer_heap; + tsx->grp_lock = grp_lock; + pj_memcpy(&tsx->cb, cb, sizeof(*cb)); + + tsx->retransmit_timer.cb = &retransmit_timer_callback; + tsx->retransmit_timer.user_data = tsx; + + tsx->destroy_timer.cb = &destroy_timer_callback; + tsx->destroy_timer.user_data = tsx; + + pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), "utsx%p", tsx); + + *p_tsx = tsx; + + PJ_LOG(5, (tsx->obj_name, "STUN client transaction created")); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy(pj_stun_client_tsx *tsx, const pj_time_val *delay) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx && delay, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->cb.on_destroy, PJ_EINVAL); + + pj_grp_lock_acquire(tsx->grp_lock); + + /* Cancel previously registered timer */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer, TIMER_INACTIVE); + + /* Stop retransmission, just in case */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + + status = + pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->destroy_timer, delay, TIMER_ACTIVE, tsx->grp_lock); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(tsx->grp_lock); + return status; + } + + tsx->cb.on_complete = NULL; + + pj_grp_lock_release(tsx->grp_lock); + + TRACE_((tsx->obj_name, "STUN transaction %p schedule destroy", tsx)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx) +{ + /* + * Currently tsx has no objects to destroy so we don't need to do anything + * here. + */ + /* pj_stun_client_tsx_stop(tsx); */ + PJ_UNUSED_ARG(tsx); + return PJ_SUCCESS; +} + +/* + * Destroy transaction immediately. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_stop(pj_stun_client_tsx *tsx) +{ + PJ_ASSERT_RETURN(tsx, PJ_EINVAL); + + /* Don't call grp_lock_acquire() because we might be called on + * group lock's destructor. + */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer, TIMER_INACTIVE); + + PJ_LOG(5, + (tsx->obj_name, "STUN client transaction %p stopped, ref_cnt=%d", tsx, pj_grp_lock_get_ref(tsx->grp_lock))); + + return PJ_SUCCESS; +} + +/* + * Check if transaction has completed. + */ +PJ_DEF(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx) +{ + PJ_ASSERT_RETURN(tsx, PJ_FALSE); + return tsx->complete; +} + +/* + * Set user data. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx, void *data) +{ + PJ_ASSERT_RETURN(tsx, PJ_EINVAL); + tsx->user_data = data; + return PJ_SUCCESS; +} + +/* + * Get the user data + */ +PJ_DEF(void *) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx) +{ + PJ_ASSERT_RETURN(tsx, NULL); + return tsx->user_data; +} + +/* + * Transmit message. + */ +static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx, pj_bool_t mod_count) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx->retransmit_timer.id == TIMER_INACTIVE || !tsx->require_retransmit || !mod_count, PJ_EBUSY); + + if (tsx->require_retransmit && mod_count) { + /* Calculate retransmit/timeout delay */ + if (tsx->transmit_count == 0) { + tsx->retransmit_time.sec = 0; + tsx->retransmit_time.msec = tsx->rto_msec; + + } else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT - 1) { + unsigned msec; + + msec = PJ_TIME_VAL_MSEC(tsx->retransmit_time); + msec <<= 1; + tsx->retransmit_time.sec = msec / 1000; + tsx->retransmit_time.msec = msec % 1000; + + } else { + tsx->retransmit_time.sec = PJ_STUN_TIMEOUT_VALUE / 1000; + tsx->retransmit_time.msec = PJ_STUN_TIMEOUT_VALUE % 1000; + } + + /* Schedule timer first because when send_msg() failed we can + * cancel it (as opposed to when schedule_timer() failed we cannot + * cancel transmission). + */ + ; + status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->retransmit_timer, &tsx->retransmit_time, + TIMER_ACTIVE, tsx->grp_lock); + if (status != PJ_SUCCESS) { + tsx->retransmit_timer.id = TIMER_INACTIVE; + return status; + } + } + + if (mod_count) + tsx->transmit_count++; + + PJ_LOG(5, (tsx->obj_name, "STUN sending message (transmit count=%d)", tsx->transmit_count)); + pj_log_push_indent(); + + /* Send message */ + status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size); + if (status == PJ_EPENDING || status == PJ_EBUSY) + status = PJ_SUCCESS; + + if (status == PJNATH_ESTUNDESTROYED) { + /* We've been destroyed, don't access the object. */ + } else if (status != PJ_SUCCESS) { + if (mod_count || status == PJ_EINVALIDOP) { + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + } + PJ_PERROR(4, (tsx->obj_name, status, "STUN error sending message")); + } + + pj_log_pop_indent(); + return status; +} + +/* + * Send outgoing message and start STUN transaction. + */ +PJ_DEF(pj_status_t) +pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx, pj_bool_t retransmit, void *pkt, unsigned pkt_len) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY); + + pj_grp_lock_acquire(tsx->grp_lock); + + /* Encode message */ + tsx->last_pkt = pkt; + tsx->last_pkt_size = pkt_len; + + /* Update STUN retransmit flag */ + tsx->require_retransmit = retransmit; + + /* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE. + * Since we don't have timeout timer, simulate this by using + * retransmit timer. + */ + if (!retransmit) { + unsigned timeout; + + pj_assert(tsx->retransmit_timer.id == 0); + tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT; + + timeout = tsx->rto_msec * 16; + tsx->retransmit_time.sec = timeout / 1000; + tsx->retransmit_time.msec = timeout % 1000; + + /* Schedule timer first because when send_msg() failed we can + * cancel it (as opposed to when schedule_timer() failed we cannot + * cancel transmission). + */ + ; + status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->retransmit_timer, &tsx->retransmit_time, + TIMER_ACTIVE, tsx->grp_lock); + if (status != PJ_SUCCESS) { + tsx->retransmit_timer.id = TIMER_INACTIVE; + pj_grp_lock_release(tsx->grp_lock); + return status; + } + } + + /* Send the message */ + status = tsx_transmit_msg(tsx, PJ_TRUE); + if (status != PJ_SUCCESS) { + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + pj_grp_lock_release(tsx->grp_lock); + return status; + } + + pj_grp_lock_release(tsx->grp_lock); + return PJ_SUCCESS; +} + +/* Retransmit timer callback */ +static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer) +{ + pj_stun_client_tsx *tsx = (pj_stun_client_tsx *)timer->user_data; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + pj_grp_lock_acquire(tsx->grp_lock); + + if (tsx->transmit_count >= PJ_STUN_MAX_TRANSMIT_COUNT) { + /* tsx may be destroyed when calling the callback below */ + pj_grp_lock_t *grp_lock = tsx->grp_lock; + + /* Retransmission count exceeded. Transaction has failed */ + tsx->retransmit_timer.id = 0; + PJ_LOG(4, (tsx->obj_name, "STUN timeout waiting for response")); + pj_log_push_indent(); + if (!tsx->complete) { + tsx->complete = PJ_TRUE; + if (tsx->cb.on_complete) { + tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0); + } + } + pj_grp_lock_release(grp_lock); + /* We might have been destroyed, don't try to access the object */ + pj_log_pop_indent(); + return; + } + + tsx->retransmit_timer.id = 0; + status = tsx_transmit_msg(tsx, PJ_TRUE); + if (status != PJ_SUCCESS) { + tsx->retransmit_timer.id = 0; + if (!tsx->complete) { + tsx->complete = PJ_TRUE; + if (tsx->cb.on_complete) { + tsx->cb.on_complete(tsx, status, NULL, NULL, 0); + } + } + } + + pj_grp_lock_release(tsx->grp_lock); + /* We might have been destroyed, don't try to access the object */ +} + +/* + * Request to retransmit the request. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx, pj_bool_t mod_count) +{ + if (tsx->destroy_timer.id != 0) { + return PJ_SUCCESS; + } + + if (mod_count) { + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + } + + return tsx_transmit_msg(tsx, mod_count); +} + +/* Timer callback to destroy transaction */ +static void destroy_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer) +{ + pj_stun_client_tsx *tsx = (pj_stun_client_tsx *)timer->user_data; + + PJ_UNUSED_ARG(timer_heap); + + tsx->destroy_timer.id = PJ_FALSE; + + tsx->cb.on_destroy(tsx); + /* Don't access transaction after this */ +} + +/* + * Notify the STUN transaction about the arrival of STUN response. + */ +PJ_DEF(pj_status_t) +pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, const pj_stun_msg *msg, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_errcode_attr *err_attr; + pj_status_t status; + + /* Must be STUN response message */ + if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) && !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { + PJ_LOG(4, (tsx->obj_name, "STUN rx_msg() error: not response message")); + return PJNATH_EINSTUNMSGTYPE; + } + + /* We have a response with matching transaction ID. + * We can cancel retransmit timer now. + */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + + /* Find STUN error code attribute */ + err_attr = (pj_stun_errcode_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0); + + if (err_attr && err_attr->err_code <= 200) { + /* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2: + * Any response between 100 and 299 MUST result in the cessation + * of request retransmissions, but otherwise is discarded. + */ + PJ_LOG(4, (tsx->obj_name, "STUN rx_msg() error: received provisional %d code (%.*s)", err_attr->err_code, + (int)err_attr->reason.slen, err_attr->reason.ptr)); + return PJ_SUCCESS; + } + + if (err_attr == NULL) { + status = PJ_SUCCESS; + } else { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + } + + /* Call callback */ + if (!tsx->complete) { + tsx->complete = PJ_TRUE; + if (tsx->cb.on_complete) { + tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len); + } + /* We might have been destroyed, don't try to access the object */ + } + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c new file mode 100755 index 000000000..f8036940f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c @@ -0,0 +1,2002 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PJ_TURN_CHANNEL_MIN 0x4000 +#define PJ_TURN_CHANNEL_MAX 0x7FFF /* inclusive */ +#define PJ_TURN_CHANNEL_HTABLE_SIZE 8 +#define PJ_TURN_PERM_HTABLE_SIZE 8 + +static const char *state_names[] = {"Null", "Resolving", "Resolved", "Allocating", + "Ready", "Deallocating", "Deallocated", "Destroying"}; + +enum timer_id_t { TIMER_NONE, TIMER_KEEP_ALIVE, TIMER_DESTROY }; + +/* This structure describes a channel binding. A channel binding is index by + * the channel number or IP address and port number of the peer. + */ +struct ch_t { + /* The channel number */ + pj_uint16_t num; + + /* PJ_TRUE if we've received successful response to ChannelBind request + * for this channel. + */ + pj_bool_t bound; + + /* The peer IP address and port */ + pj_sockaddr addr; + + /* The channel binding expiration */ + pj_time_val expiry; +}; + +/* This structure describes a permission. A permission is identified by the + * IP address only. + */ +struct perm_t { + /* Cache of hash value to speed-up lookup */ + pj_uint32_t hval; + + /* The permission IP address. The port number MUST be zero */ + pj_sockaddr addr; + + /* Number of peers that uses this permission. */ + unsigned peer_cnt; + + /* Automatically renew this permission once it expires? */ + pj_bool_t renew; + + /* The permission expiration */ + pj_time_val expiry; + + /* Arbitrary/random pointer value (token) to map this perm with the + * request to create it. It is used to invalidate this perm when the + * request fails. + */ + void *req_token; +}; + +struct conn_bind_t { + pj_uint32_t id; /* Connection ID. */ + pj_sockaddr peer_addr; /* Peer address. */ + unsigned peer_addr_len; +}; + +/* The TURN client session structure */ +struct pj_turn_session { + pj_pool_t *pool; + const char *obj_name; + pj_turn_session_cb cb; + void *user_data; + pj_stun_config stun_cfg; + pj_bool_t is_destroying; + + pj_grp_lock_t *grp_lock; + int busy; + + pj_turn_state_t state; + pj_status_t last_status; + pj_bool_t pending_destroy; + + pj_stun_session *stun; + + unsigned lifetime; + int ka_interval; + pj_time_val expiry; + + pj_timer_heap_t *timer_heap; + pj_timer_entry timer; + + pj_uint16_t default_port; + + pj_uint16_t af; + pj_turn_tp_type conn_type; + pj_uint16_t srv_addr_cnt; + pj_sockaddr *srv_addr_list; + pj_sockaddr *srv_addr; + + pj_bool_t pending_alloc; + pj_turn_alloc_param alloc_param; + + pj_sockaddr mapped_addr; + pj_sockaddr relay_addr; + + pj_hash_table_t *ch_table; + pj_hash_table_t *perm_table; + + pj_uint32_t send_ind_tsx_id[3]; + /* tx_pkt must be 16bit aligned */ + pj_uint8_t tx_pkt[PJ_TURN_MAX_PKT_LEN]; + + pj_uint16_t next_ch; +}; + +/* + * Prototypes. + */ +static void sess_shutdown(pj_turn_session *sess, pj_status_t status); +static void turn_sess_on_destroy(void *comp); +static void do_destroy(pj_turn_session *sess); +static void send_refresh(pj_turn_session *sess, int lifetime); +static pj_status_t stun_on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); +static void stun_on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +static pj_status_t stun_on_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec); +static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, + pj_bool_t update, pj_bool_t bind_channel); +static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess, pj_uint16_t chnum); +static struct perm_t *lookup_perm(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, + pj_bool_t update); +static void invalidate_perm(pj_turn_session *sess, struct perm_t *perm); +static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e); + +/* + * Create default pj_turn_alloc_param. + */ +PJ_DEF(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm) +{ + pj_bzero(prm, sizeof(*prm)); + prm->peer_conn_type = PJ_TURN_TP_UDP; +} + +/* + * Duplicate pj_turn_alloc_param. + */ +PJ_DEF(void) pj_turn_alloc_param_copy(pj_pool_t *pool, pj_turn_alloc_param *dst, const pj_turn_alloc_param *src) +{ + PJ_UNUSED_ARG(pool); + pj_memcpy(dst, src, sizeof(*dst)); +} + +/* + * Get TURN state name. + */ +PJ_DEF(const char *) pj_turn_state_name(pj_turn_state_t state) +{ + return state_names[state]; +} + +/* + * Create TURN client session. + */ +PJ_DEF(pj_status_t) +pj_turn_session_create(const pj_stun_config *cfg, const char *name, int af, pj_turn_tp_type conn_type, + pj_grp_lock_t *grp_lock, const pj_turn_session_cb *cb, unsigned options, void *user_data, + pj_turn_session **p_sess) +{ + pj_pool_t *pool; + pj_turn_session *sess; + pj_stun_session_cb stun_cb; + pj_status_t status; + + PJ_ASSERT_RETURN(cfg && cfg->pf && cb && p_sess, PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_send_pkt, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + if (name == NULL) + name = "turn%p"; + + /* Allocate and create TURN session */ + pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_TURN_SESS, PJNATH_POOL_INC_TURN_SESS, NULL); + sess = PJ_POOL_ZALLOC_T(pool, pj_turn_session); + sess->pool = pool; + sess->obj_name = pool->obj_name; + sess->timer_heap = cfg->timer_heap; + sess->af = (pj_uint16_t)af; + sess->conn_type = conn_type; + sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; + sess->user_data = user_data; + sess->next_ch = PJ_TURN_CHANNEL_MIN; + + /* Copy STUN session */ + pj_memcpy(&sess->stun_cfg, cfg, sizeof(pj_stun_config)); + + /* Copy callback */ + pj_memcpy(&sess->cb, cb, sizeof(*cb)); + + /* Peer hash table */ + sess->ch_table = pj_hash_create(pool, PJ_TURN_CHANNEL_HTABLE_SIZE); + + /* Permission hash table */ + sess->perm_table = pj_hash_create(pool, PJ_TURN_PERM_HTABLE_SIZE); + + /* Session lock */ + if (grp_lock) { + sess->grp_lock = grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(sess->grp_lock); + pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &turn_sess_on_destroy); + + /* Timer */ + pj_timer_entry_init(&sess->timer, TIMER_NONE, sess, &on_timer_event); + + /* Create STUN session */ + pj_bzero(&stun_cb, sizeof(stun_cb)); + stun_cb.on_send_msg = &stun_on_send_msg; + stun_cb.on_request_complete = &stun_on_request_complete; + stun_cb.on_rx_indication = &stun_on_rx_indication; + status = pj_stun_session_create(&sess->stun_cfg, sess->obj_name, &stun_cb, PJ_FALSE, sess->grp_lock, &sess->stun); + if (status != PJ_SUCCESS) { + do_destroy(sess); + return status; + } + + /* Attach ourself to STUN session */ + pj_stun_session_set_user_data(sess->stun, sess); + + /* Done */ + + PJ_LOG(4, (sess->obj_name, "TURN client session created")); + + *p_sess = sess; + return PJ_SUCCESS; +} + +static void turn_sess_on_destroy(void *comp) +{ + pj_turn_session *sess = (pj_turn_session *)comp; + + /* Destroy pool */ + if (sess->pool) { + PJ_LOG(4, (sess->obj_name, "TURN client session destroyed")); + pj_pool_safe_release(&sess->pool); + } +} + +/* Destroy */ +static void do_destroy(pj_turn_session *sess) +{ + PJ_LOG(4, (sess->obj_name, "TURN session destroy request, ref_cnt=%d", pj_grp_lock_get_ref(sess->grp_lock))); + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return; + } + + sess->is_destroying = PJ_TRUE; + pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); + pj_stun_session_destroy(sess->stun); + + pj_grp_lock_dec_ref(sess->grp_lock); + pj_grp_lock_release(sess->grp_lock); +} + +/* Set session state */ +static void set_state(pj_turn_session *sess, enum pj_turn_state_t state) +{ + pj_turn_state_t old_state = sess->state; + + if (state == sess->state) + return; + + PJ_LOG(4, (sess->obj_name, "State changed %s --> %s", state_names[old_state], state_names[state])); + sess->state = state; + + if (sess->cb.on_state) { + (*sess->cb.on_state)(sess, old_state, state); + } +} + +/* + * Notify application and shutdown the TURN session. + */ +static void sess_shutdown(pj_turn_session *sess, pj_status_t status) +{ + pj_bool_t can_destroy = PJ_TRUE; + + PJ_LOG(4, (sess->obj_name, "Request to shutdown in state %s, cause:%d", state_names[sess->state], status)); + + if (sess->last_status == PJ_SUCCESS && status != PJ_SUCCESS) + sess->last_status = status; + + switch (sess->state) { + case PJ_TURN_STATE_NULL: + break; + case PJ_TURN_STATE_RESOLVING: + /* Wait for DNS callback invoked, it will call the this function + * again. If the callback happens to get pending_destroy==FALSE, + * the TURN allocation will call this function again. + */ + sess->pending_destroy = PJ_TRUE; + can_destroy = PJ_FALSE; + break; + case PJ_TURN_STATE_RESOLVED: + break; + case PJ_TURN_STATE_ALLOCATING: + /* We need to wait until allocation complete */ + sess->pending_destroy = PJ_TRUE; + can_destroy = PJ_FALSE; + break; + case PJ_TURN_STATE_READY: + /* Send REFRESH with LIFETIME=0 */ + can_destroy = PJ_FALSE; + send_refresh(sess, 0); + break; + case PJ_TURN_STATE_DEALLOCATING: + can_destroy = PJ_FALSE; + /* This may recursively call this function again with + * state==PJ_TURN_STATE_DEALLOCATED. + */ + /* No need to deallocate as we're already deallocating! + * See https://github.com/pjsip/pjproject/issues/1551 + send_refresh(sess, 0); + */ + break; + case PJ_TURN_STATE_DEALLOCATED: + case PJ_TURN_STATE_DESTROYING: + break; + } + + if (can_destroy) { + /* Schedule destroy */ + pj_time_val delay = {0, 0}; + + set_state(sess, PJ_TURN_STATE_DESTROYING); + + pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); + pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, &delay, TIMER_DESTROY, sess->grp_lock); + } +} + +/* + * Public API to destroy TURN client session. + */ +PJ_DEF(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + + sess_shutdown(sess, PJ_SUCCESS); + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/** + * Forcefully destroy the TURN session. + */ +PJ_DEF(pj_status_t) pj_turn_session_destroy(pj_turn_session *sess, pj_status_t last_err) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + if (last_err != PJ_SUCCESS && sess->last_status == PJ_SUCCESS) + sess->last_status = last_err; + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, PJ_SUCCESS); + return PJ_SUCCESS; +} + +/* + * Get TURN session info. + */ +PJ_DEF(pj_status_t) pj_turn_session_get_info(pj_turn_session *sess, pj_turn_session_info *info) +{ + pj_time_val now; + + PJ_ASSERT_RETURN(sess && info, PJ_EINVAL); + + pj_gettimeofday(&now); + + info->state = sess->state; + info->conn_type = sess->conn_type; + info->lifetime = sess->expiry.sec - now.sec; + info->last_status = sess->last_status; + + if (sess->srv_addr) + pj_memcpy(&info->server, sess->srv_addr, sizeof(info->server)); + else + pj_bzero(&info->server, sizeof(info->server)); + + pj_memcpy(&info->mapped_addr, &sess->mapped_addr, sizeof(sess->mapped_addr)); + pj_memcpy(&info->relay_addr, &sess->relay_addr, sizeof(sess->relay_addr)); + + return PJ_SUCCESS; +} + +/* + * Re-assign user data. + */ +PJ_DEF(pj_status_t) pj_turn_session_set_user_data(pj_turn_session *sess, void *user_data) +{ + sess->user_data = user_data; + return PJ_SUCCESS; +} + +/** + * Retrieve user data. + */ +PJ_DEF(void *) pj_turn_session_get_user_data(pj_turn_session *sess) +{ + return sess->user_data; +} + +/** + * Get group lock. + */ +PJ_DEF(pj_grp_lock_t *) pj_turn_session_get_grp_lock(pj_turn_session *sess) +{ + PJ_ASSERT_RETURN(sess, NULL); + return sess->grp_lock; +} + +/* + * Configure message logging. By default all flags are enabled. + * + * @param sess The TURN client session. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DEF(void) pj_turn_session_set_log(pj_turn_session *sess, unsigned flags) +{ + pj_stun_session_set_log(sess->stun, flags); +} + +/* + * Set software name + */ +PJ_DEF(pj_status_t) pj_turn_session_set_software_name(pj_turn_session *sess, const pj_str_t *sw) +{ + pj_status_t status; + + pj_grp_lock_acquire(sess->grp_lock); + status = pj_stun_session_set_software_name(sess->stun, sw); + pj_grp_lock_release(sess->grp_lock); + + return status; +} + +/** + * Set the server or domain name of the server. + */ +PJ_DEF(pj_status_t) +pj_turn_session_set_server(pj_turn_session *sess, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver) +{ + pj_sockaddr tmp_addr; + pj_bool_t is_ip_addr; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && domain, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_NULL, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* See if "domain" contains just IP address */ + tmp_addr.addr.sa_family = sess->af; + status = pj_inet_pton(sess->af, domain, pj_sockaddr_get_addr(&tmp_addr)); + is_ip_addr = (status == PJ_SUCCESS); + + if (!is_ip_addr && resolver) { + /* Resolve with DNS SRV resolution, and fallback to DNS A resolution + * if default_port is specified. + */ + unsigned opt = 0; + pj_str_t res_name; + + switch (sess->conn_type) { + case PJ_TURN_TP_UDP: + res_name = pj_str("_turn._udp."); + break; + case PJ_TURN_TP_TCP: + res_name = pj_str("_turn._tcp."); + break; + case PJ_TURN_TP_TLS: + res_name = pj_str("_turns._tcp."); + break; + default: + status = PJNATH_ETURNINTP; + goto on_return; + } + + /* Init DNS resolution option for IPv6 */ + if (sess->af == pj_AF_INET6()) + opt |= PJ_DNS_SRV_RESOLVE_AAAA_ONLY; + + /* Fallback to DNS A only if default port is specified */ + if (default_port > 0 && default_port < 65536) { + if (sess->af == pj_AF_INET6()) + opt |= PJ_DNS_SRV_FALLBACK_AAAA; + else + opt |= PJ_DNS_SRV_FALLBACK_A; + sess->default_port = (pj_uint16_t)default_port; + } + + PJ_LOG(5, (sess->obj_name, "Resolving %.*s%.*s with DNS SRV", (int)res_name.slen, res_name.ptr, + (int)domain->slen, domain->ptr)); + set_state(sess, PJ_TURN_STATE_RESOLVING); + + /* User may have destroyed us in the callback */ + if (sess->state != PJ_TURN_STATE_RESOLVING) { + status = PJ_ECANCELLED; + goto on_return; + } + + /* Add reference before async DNS resolution */ + pj_grp_lock_add_ref(sess->grp_lock); + + status = pj_dns_srv_resolve(domain, &res_name, default_port, sess->pool, resolver, opt, sess, + &dns_srv_resolver_cb, NULL); + if (status != PJ_SUCCESS) { + set_state(sess, PJ_TURN_STATE_NULL); + pj_grp_lock_dec_ref(sess->grp_lock); + goto on_return; + } + + } else { + /* Resolver is not specified, resolve with standard gethostbyname(). + * The default_port MUST be specified in this case. + */ + pj_addrinfo *ai; + unsigned i, cnt; + + /* Default port must be specified */ + PJ_ASSERT_RETURN(default_port > 0 && default_port < 65536, PJ_EINVAL); + sess->default_port = (pj_uint16_t)default_port; + + cnt = PJ_TURN_MAX_DNS_SRV_CNT; + ai = (pj_addrinfo *)pj_pool_calloc(sess->pool, cnt, sizeof(pj_addrinfo)); + + PJ_LOG(5, (sess->obj_name, "Resolving %.*s with DNS A", (int)domain->slen, domain->ptr)); + set_state(sess, PJ_TURN_STATE_RESOLVING); + + /* User may have destroyed us in the callback */ + if (sess->state != PJ_TURN_STATE_RESOLVING) { + status = PJ_ECANCELLED; + goto on_return; + } + + status = pj_getaddrinfo(sess->af, domain, &cnt, ai); + if (status != PJ_SUCCESS) + goto on_return; + + sess->srv_addr_cnt = (pj_uint16_t)cnt; + sess->srv_addr_list = (pj_sockaddr *)pj_pool_calloc(sess->pool, cnt, sizeof(pj_sockaddr)); + for (i = 0; i < cnt; ++i) { + pj_sockaddr *addr = &sess->srv_addr_list[i]; + pj_memcpy(addr, &ai[i].ai_addr, sizeof(pj_sockaddr)); + addr->addr.sa_family = sess->af; + pj_sockaddr_set_port(addr, sess->default_port); + } + + sess->srv_addr = &sess->srv_addr_list[0]; + set_state(sess, PJ_TURN_STATE_RESOLVED); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Set credential to be used by the session. + */ +PJ_DEF(pj_status_t) pj_turn_session_set_credential(pj_turn_session *sess, const pj_stun_auth_cred *cred) +{ + PJ_ASSERT_RETURN(sess && cred, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->stun, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + pj_stun_session_set_credential(sess->stun, PJ_STUN_AUTH_LONG_TERM, cred); + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/** + * Create TURN allocation. + */ +PJ_DEF(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess, const pj_turn_alloc_param *param) +{ + pj_stun_tx_data *tdata; + pj_bool_t retransmit; + pj_status_t status; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state > PJ_TURN_STATE_NULL && sess->state <= PJ_TURN_STATE_RESOLVED, PJ_EINVALIDOP); + PJ_ASSERT_RETURN(param->peer_conn_type == PJ_TURN_TP_UDP || param->peer_conn_type == PJ_TURN_TP_TCP, PJ_EINVAL); + + /* Verify address family in allocation param */ + if (param && param->af) { + PJ_ASSERT_RETURN(param->af == pj_AF_INET() || param->af == pj_AF_INET6(), PJ_EINVAL); + } + + pj_grp_lock_acquire(sess->grp_lock); + + if (param && param != &sess->alloc_param) + pj_turn_alloc_param_copy(sess->pool, &sess->alloc_param, param); + + if (sess->state < PJ_TURN_STATE_RESOLVED) { + sess->pending_alloc = PJ_TRUE; + + PJ_LOG(4, (sess->obj_name, "Pending ALLOCATE in state %s", state_names[sess->state])); + + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + } + + /* Ready to allocate */ + pj_assert(sess->state == PJ_TURN_STATE_RESOLVED); + + /* Create a bare request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_ALLOCATE_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* MUST include REQUESTED-TRANSPORT attribute */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REQ_TRANSPORT, + PJ_STUN_SET_RT_PROTO(param->peer_conn_type)); + + /* Include BANDWIDTH if requested */ + if (sess->alloc_param.bandwidth > 0) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_BANDWIDTH, sess->alloc_param.bandwidth); + } + + /* Include LIFETIME if requested */ + if (sess->alloc_param.lifetime > 0) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_LIFETIME, sess->alloc_param.lifetime); + } + + /* Include ADDRESS-FAMILY if requested */ + if (sess->alloc_param.af || sess->af == pj_AF_INET6()) { + enum { IPV4_AF_TYPE = 0x01 << 24, IPV6_AF_TYPE = 0x02 << 24 }; + + if (sess->alloc_param.af == pj_AF_INET6() || (sess->alloc_param.af == 0 && sess->af == pj_AF_INET6())) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REQ_ADDR_TYPE, IPV6_AF_TYPE); + } else if (sess->alloc_param.af == pj_AF_INET()) { + /* For IPv4, only add the attribute when explicitly requested */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REQ_ADDR_TYPE, IPV4_AF_TYPE); + } + } + + /* Server address must be set */ + pj_assert(sess->srv_addr != NULL); + + /* Send request */ + set_state(sess, PJ_TURN_STATE_ALLOCATING); + retransmit = (sess->conn_type == PJ_TURN_TP_UDP); + status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, retransmit, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) { + /* Set state back to RESOLVED. We don't want to destroy session now, + * let the application do it if it wants to. + */ + /* Set state back to RESOLVED may cause infinite loop (see #1942). */ + // set_state(sess, PJ_TURN_STATE_RESOLVED); + } + + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * Install or renew permissions + */ +PJ_DEF(pj_status_t) +pj_turn_session_set_perm(pj_turn_session *sess, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options) +{ + pj_stun_tx_data *tdata; + pj_hash_iterator_t it_buf, *it; + void *req_token; + unsigned i, attr_added = 0; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && addr_cnt && addr, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create a bare CreatePermission request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CREATE_PERM_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* Create request token to map the request to the perm structures + * which the request belongs. + */ + req_token = (void *)(pj_ssize_t)pj_rand(); + + /* Process the addresses */ + for (i = 0; i < addr_cnt; ++i) { + struct perm_t *perm; + + /* Lookup the perm structure and create if it doesn't exist */ + perm = lookup_perm(sess, &addr[i], pj_sockaddr_get_len(&addr[i]), PJ_TRUE); + perm->renew = (options & 0x01); + + /* Only add to the request if the request doesn't contain this + * address yet. + */ + if (perm->req_token != req_token) { + perm->req_token = req_token; + + /* Add XOR-PEER-ADDRESS */ + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, + &addr[i], sizeof(addr[i])); + if (status != PJ_SUCCESS) + goto on_error; + + ++attr_added; + } + } + + /* No address to set */ + if (attr_added == 0) { + pj_stun_msg_destroy_tdata(sess->stun, tdata); + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + } + + /* Send the request */ + status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), + sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) { + /* tdata is already destroyed */ + tdata = NULL; + goto on_error; + } + + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + +on_error: + /* destroy tdata */ + if (tdata) { + pj_stun_msg_destroy_tdata(sess->stun, tdata); + } + /* invalidate perm structures associated with this request */ + it = pj_hash_first(sess->perm_table, &it_buf); + while (it) { + struct perm_t *perm = (struct perm_t *)pj_hash_this(sess->perm_table, it); + it = pj_hash_next(sess->perm_table, it); + if (perm->req_token == req_token) + invalidate_perm(sess, perm); + } + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * Send REFRESH + */ +static void send_refresh(pj_turn_session *sess, int lifetime) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_ON_FAIL(sess->state == PJ_TURN_STATE_READY, return ); + + /* Create a bare REFRESH request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_REFRESH_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add LIFETIME */ + if (lifetime >= 0) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_LIFETIME, lifetime); + } + + /* Send request */ + if (lifetime == 0) { + set_state(sess, PJ_TURN_STATE_DEALLOCATING); + } + + status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return; + +on_error: + if (lifetime == 0) { + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, status); + } +} + +/** + * Relay data to the specified peer through the session. + */ +PJ_DEF(pj_status_t) +pj_turn_session_sendto(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *addr, + unsigned addr_len) +{ + struct ch_t *ch; + struct perm_t *perm; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && pkt && pkt_len && addr && addr_len, PJ_EINVAL); + + /* Return error if we're not ready */ + if (sess->state != PJ_TURN_STATE_READY) { + return PJ_EIGNORED; + } + + /* Lock session now */ + pj_grp_lock_acquire(sess->grp_lock); + + /* Lookup permission first */ + perm = lookup_perm(sess, addr, pj_sockaddr_get_len(addr), PJ_FALSE); + if (perm == NULL) { + /* Permission doesn't exist, install it first */ + char ipstr[PJ_INET6_ADDRSTRLEN + 2]; + + PJ_LOG(4, (sess->obj_name, "sendto(): IP %s has no permission, requesting it first..", + pj_sockaddr_print(addr, ipstr, sizeof(ipstr), 2))); + + status = pj_turn_session_set_perm(sess, 1, (const pj_sockaddr *)addr, 0); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + } + + /* If peer connection is TCP (RFC 6062), send it directly */ + if (sess->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) { + status = sess->cb.on_send_pkt(sess, pkt, pkt_len, addr, addr_len); + goto on_return; + } + + /* See if the peer is bound to a channel number */ + ch = lookup_ch_by_addr(sess, addr, pj_sockaddr_get_len(addr), PJ_FALSE, PJ_FALSE); + if (ch && ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound) { + unsigned total_len; + + /* Peer is assigned a channel number, we can use ChannelData */ + pj_turn_channel_data *cd = (pj_turn_channel_data *)sess->tx_pkt; + + pj_assert(sizeof(*cd) == 4); + + /* Calculate total length, including paddings */ + total_len = (pkt_len + sizeof(*cd) + 3) & (~3); + if (total_len > sizeof(sess->tx_pkt)) { + status = PJ_ETOOBIG; + goto on_return; + } + + cd->ch_number = pj_htons((pj_uint16_t)ch->num); + cd->length = pj_htons((pj_uint16_t)pkt_len); + pj_memcpy(cd + 1, pkt, pkt_len); + + pj_assert(sess->srv_addr != NULL); + + status = + sess->cb.on_send_pkt(sess, sess->tx_pkt, total_len, sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr)); + + } else { + /* Use Send Indication. */ + pj_stun_sockaddr_attr peer_attr; + pj_stun_binary_attr data_attr; + pj_stun_msg send_ind; + pj_size_t send_ind_len; + + /* Increment counter */ + ++sess->send_ind_tsx_id[2]; + + /* Create blank SEND-INDICATION */ + status = pj_stun_msg_init(&send_ind, PJ_STUN_SEND_INDICATION, PJ_STUN_MAGIC, + (const pj_uint8_t *)sess->send_ind_tsx_id); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add XOR-PEER-ADDRESS */ + pj_stun_sockaddr_attr_init(&peer_attr, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, addr, addr_len); + pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr *)&peer_attr); + + /* Add DATA attribute */ + pj_stun_binary_attr_init(&data_attr, NULL, PJ_STUN_ATTR_DATA, NULL, 0); + data_attr.data = (pj_uint8_t *)pkt; + data_attr.length = pkt_len; + pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr *)&data_attr); + + /* Encode the message */ + status = pj_stun_msg_encode(&send_ind, sess->tx_pkt, sizeof(sess->tx_pkt), 0, NULL, &send_ind_len); + if (status != PJ_SUCCESS) + goto on_return; + + /* Send the Send Indication */ + status = sess->cb.on_send_pkt(sess, sess->tx_pkt, (unsigned)send_ind_len, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr)); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Bind a peer address to a channel number. + */ +PJ_DEF(pj_status_t) +pj_turn_session_bind_channel(pj_turn_session *sess, const pj_sockaddr_t *peer_adr, unsigned addr_len) +{ + struct ch_t *ch; + pj_stun_tx_data *tdata; + pj_uint16_t ch_num; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && peer_adr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create blank ChannelBind request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CHANNEL_BIND_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Lookup if this peer has already been assigned a number */ + ch = lookup_ch_by_addr(sess, peer_adr, pj_sockaddr_get_len(peer_adr), PJ_TRUE, PJ_FALSE); + pj_assert(ch); + + if (ch->num != PJ_TURN_INVALID_CHANNEL) { + /* Channel is already bound. This is a refresh request. */ + ch_num = ch->num; + } else { + PJ_ASSERT_ON_FAIL(sess->next_ch <= PJ_TURN_CHANNEL_MAX, { + status = PJ_ETOOMANY; + goto on_return; + }); + ch->num = ch_num = sess->next_ch++; + } + + /* Add CHANNEL-NUMBER attribute */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_CHANNEL_NUMBER, PJ_STUN_SET_CH_NB(ch_num)); + + /* Add XOR-PEER-ADDRESS attribute */ + pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, peer_adr, addr_len); + + /* Send the request, associate peer data structure with tdata + * for future reference when we receive the ChannelBind response. + */ + status = pj_stun_session_send_msg(sess->stun, ch, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Send ConnectionBind request. + */ +PJ_DEF(pj_status_t) +pj_turn_session_connection_bind(pj_turn_session *sess, pj_pool_t *pool, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_stun_tx_data *tdata; + struct conn_bind_t *conn_bind; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && pool && conn_id && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create blank ConnectionBind request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CONNECTION_BIND_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add CONNECTION_ID attribute */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_CONNECTION_ID, conn_id); + + conn_bind = PJ_POOL_ZALLOC_T(pool, struct conn_bind_t); + conn_bind->id = conn_id; + pj_sockaddr_cp(&conn_bind->peer_addr, peer_addr); + conn_bind->peer_addr_len = addr_len; + + /* Send the request, associate connection data structure with tdata + * for future reference when we receive the ConnectionBind response. + */ + status = pj_stun_session_send_msg(sess->stun, conn_bind, PJ_FALSE, PJ_FALSE, peer_addr, addr_len, tdata); + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Send Connect request. + */ +PJ_DEF(pj_status_t) pj_turn_session_connect(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create blank Connect request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CONNECT_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, peer_addr, + addr_len); + if (status != PJ_SUCCESS) + goto on_return; + status = pj_stun_session_send_msg(sess->stun, (void *)peer_addr, PJ_FALSE, PJ_FALSE, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +PJ_DEF(pj_status_t) +pj_turn_session_on_rx_pkt(pj_turn_session *sess, void *pkt, pj_size_t pkt_len, pj_size_t *parsed_len) +{ + pj_turn_session_on_rx_pkt_param prm; + pj_status_t status; + + pj_bzero(&prm, sizeof(prm)); + prm.pkt = pkt; + prm.pkt_len = pkt_len; + status = pj_turn_session_on_rx_pkt2(sess, &prm); + if (status == PJ_SUCCESS && parsed_len) + *parsed_len = prm.parsed_len; + return status; +} + +/** + * Notify TURN client session upon receiving a packet from server. + * The packet maybe a STUN packet or ChannelData packet. + */ +PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt2(pj_turn_session *sess, pj_turn_session_on_rx_pkt_param *prm) +{ + pj_bool_t is_stun; + pj_status_t status; + pj_bool_t is_datagram; + + /* Packet could be ChannelData or STUN message (response or + * indication). + */ + + /* Start locking the session */ + pj_grp_lock_acquire(sess->grp_lock); + + is_datagram = (sess->conn_type == PJ_TURN_TP_UDP); + + /* Quickly check if this is STUN message */ + is_stun = ((((pj_uint8_t *)prm->pkt)[0] & 0xC0) == 0); + + if (is_stun) { + /* This looks like STUN, give it to the STUN session */ + unsigned options; + const pj_sockaddr_t *src_addr = prm->src_addr ? prm->src_addr : sess->srv_addr; + unsigned src_addr_len = prm->src_addr_len ? prm->src_addr_len : pj_sockaddr_get_len(sess->srv_addr); + + options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK; + if (is_datagram) + options |= PJ_STUN_IS_DATAGRAM; + status = pj_stun_session_on_rx_pkt(sess->stun, prm->pkt, prm->pkt_len, options, NULL, &prm->parsed_len, + src_addr, src_addr_len); + + } else { + /* This must be ChannelData. */ + pj_turn_channel_data cd; + struct ch_t *ch; + + if (prm->pkt_len < 4) { + prm->parsed_len = 0; + status = PJ_ETOOSMALL; + goto on_return; + } + + /* Decode ChannelData packet */ + pj_memcpy(&cd, prm->pkt, sizeof(pj_turn_channel_data)); + cd.ch_number = pj_ntohs(cd.ch_number); + cd.length = pj_ntohs(cd.length); + + /* Check that size is sane */ + if (prm->pkt_len < cd.length + sizeof(cd)) { + if (is_datagram) { + /* Discard the datagram */ + prm->parsed_len = prm->pkt_len; + } else { + /* Insufficient fragment */ + prm->parsed_len = 0; + } + status = PJ_ETOOSMALL; + goto on_return; + } else { + /* Apply padding too */ + prm->parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd); + } + + /* Lookup channel */ + ch = lookup_ch_by_chnum(sess, cd.ch_number); + if (!ch || !ch->bound) { + status = PJ_ENOTFOUND; + goto on_return; + } + + /* Notify application */ + if (sess->cb.on_rx_data) { + (*sess->cb.on_rx_data)(sess, ((pj_uint8_t *)prm->pkt) + sizeof(cd), cd.length, &ch->addr, + pj_sockaddr_get_len(&ch->addr)); + } + + status = PJ_SUCCESS; + } + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * This is a callback from STUN session to send outgoing packet. + */ +static pj_status_t stun_on_send_msg(pj_stun_session *stun, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_turn_session *sess; + + PJ_UNUSED_ARG(token); + + sess = (pj_turn_session *)pj_stun_session_get_user_data(stun); + if (sess->cb.on_stun_send_pkt) { + return (*sess->cb.on_stun_send_pkt)(sess, (const pj_uint8_t *)pkt, (unsigned)pkt_size, dst_addr, addr_len); + } else { + return (*sess->cb.on_send_pkt)(sess, (const pj_uint8_t *)pkt, (unsigned)pkt_size, dst_addr, addr_len); + } +} + +/* + * Handle failed ALLOCATE or REFRESH request. This may switch to alternate + * server if we have one. + */ +static void on_session_fail(pj_turn_session *sess, enum pj_stun_method_e method, pj_status_t status, + const pj_str_t *reason) +{ + sess->last_status = status; + + do { + pj_str_t reason1; + char err_msg[PJ_ERR_MSG_SIZE]; + + if (reason == NULL) { + pj_strerror(status, err_msg, sizeof(err_msg)); + reason1 = pj_str(err_msg); + reason = &reason1; + } + + PJ_LOG(4, (sess->obj_name, "%s error: %.*s", pj_stun_get_method_name(method), (int)reason->slen, reason->ptr)); + + /* If this is ALLOCATE response and we don't have more server + * addresses to try, notify application and destroy the TURN + * session. + */ + if (method == PJ_STUN_ALLOCATE_METHOD && sess->srv_addr == &sess->srv_addr_list[sess->srv_addr_cnt - 1]) { + + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, status); + return; + } + + /* Otherwise if this is not ALLOCATE response, notify application + * that session has been TERMINATED. + */ + if (method != PJ_STUN_ALLOCATE_METHOD) { + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, status); + return; + } + + /* Try next server */ + ++sess->srv_addr; + reason = NULL; + + PJ_LOG(4, (sess->obj_name, "Trying next server")); + set_state(sess, PJ_TURN_STATE_RESOLVED); + + } while (0); +} + +/* + * Handle successful response to ALLOCATE or REFRESH request. + */ +static void on_allocate_success(pj_turn_session *sess, enum pj_stun_method_e method, const pj_stun_msg *msg) +{ + const pj_stun_lifetime_attr *lf_attr; + const pj_stun_xor_relayed_addr_attr *raddr_attr; + const pj_stun_sockaddr_attr *mapped_attr; + pj_str_t s; + + /* Must have LIFETIME attribute */ + lf_attr = (const pj_stun_lifetime_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0); + if (lf_attr == NULL) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, pj_cstr(&s, "Error: Missing LIFETIME attribute")); + return; + } + + /* If LIFETIME is zero, this is a deallocation */ + if (lf_attr->value == 0) { + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, PJ_SUCCESS); + return; + } + + /* Update lifetime and keep-alive interval */ + sess->lifetime = lf_attr->value; + pj_gettimeofday(&sess->expiry); + + if (sess->lifetime < PJ_TURN_KEEP_ALIVE_SEC) { + if (sess->lifetime <= 2) { + on_session_fail(sess, method, PJ_ETOOSMALL, pj_cstr(&s, "Error: LIFETIME too small")); + return; + } + sess->ka_interval = sess->lifetime - 2; + sess->expiry.sec += (sess->ka_interval - 1); + } else { + int timeout; + + sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; + + timeout = sess->lifetime - PJ_TURN_REFRESH_SEC_BEFORE; + if (timeout < sess->ka_interval) + timeout = sess->ka_interval - 1; + + sess->expiry.sec += timeout; + } + + /* Check that relayed transport address contains correct + * address family. + */ + raddr_attr = (const pj_stun_xor_relayed_addr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_RELAYED_ADDR, 0); + if (raddr_attr == NULL && method == PJ_STUN_ALLOCATE_METHOD) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Received ALLOCATE without " + "RELAY-ADDRESS attribute")); + return; + } + if (raddr_attr && ((sess->alloc_param.af != 0 && raddr_attr->sockaddr.addr.sa_family != sess->alloc_param.af) || + (sess->alloc_param.af == 0 && raddr_attr->sockaddr.addr.sa_family != sess->af))) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Mismatched RELAY-ADDRESS " + "address family")); + return; + } + if (raddr_attr && !pj_sockaddr_has_addr(&raddr_attr->sockaddr)) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Invalid IP address in " + "RELAY-ADDRESS attribute")); + return; + } + + /* Save relayed address */ + if (raddr_attr) { + /* If we already have relay address, check if the relay address + * in the response matches our relay address. + */ + if (pj_sockaddr_has_addr(&sess->relay_addr)) { + if (pj_sockaddr_cmp(&sess->relay_addr, &raddr_attr->sockaddr)) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: different RELAY-ADDRESS is" + "returned by server")); + return; + } + } else { + /* Otherwise save the relayed address */ + pj_memcpy(&sess->relay_addr, &raddr_attr->sockaddr, sizeof(pj_sockaddr)); + } + } + + /* Get mapped address */ + mapped_attr = (const pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mapped_attr) { + pj_memcpy(&sess->mapped_addr, &mapped_attr->sockaddr, sizeof(mapped_attr->sockaddr)); + } + + /* Success */ + + /* Cancel existing keep-alive timer, if any */ + pj_assert(sess->timer.id != TIMER_DESTROY); + if (sess->timer.id == TIMER_KEEP_ALIVE) { + pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); + } + + /* Start keep-alive timer once allocation succeeds */ + if (sess->state < PJ_TURN_STATE_DEALLOCATING) { + pj_time_val timeout; + timeout.sec = sess->ka_interval; + timeout.msec = 0; + + pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, &timeout, TIMER_KEEP_ALIVE, sess->grp_lock); + + set_state(sess, PJ_TURN_STATE_READY); + } +} + +/* + * Notification from STUN session on request completion. + */ +static void stun_on_request_complete(pj_stun_session *stun, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_turn_session *sess; + enum pj_stun_method_e method = (enum pj_stun_method_e)PJ_STUN_GET_METHOD(tdata->msg->hdr.type); + + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (pj_turn_session *)pj_stun_session_get_user_data(stun); + + if (method == PJ_STUN_ALLOCATE_METHOD) { + + /* Destroy if we have pending destroy request */ + if (sess->pending_destroy) { + if (status == PJ_SUCCESS) + sess->state = PJ_TURN_STATE_READY; + else + sess->state = PJ_TURN_STATE_DEALLOCATED; + sess_shutdown(sess, PJ_SUCCESS); + return; + } + + /* Handle ALLOCATE response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + + /* Successful Allocate response */ + on_allocate_success(sess, method, response); + + } else { + /* Failed Allocate request */ + const pj_str_t *err_msg = NULL; + + if (status == PJ_SUCCESS) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + err_msg = &err_attr->reason; + } else { + status = PJNATH_EINSTUNMSG; + } + } + + on_session_fail(sess, method, status, err_msg); + } + + } else if (method == PJ_STUN_REFRESH_METHOD) { + /* Handle Refresh response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + /* Success, schedule next refresh. */ + on_allocate_success(sess, method, response); + + } else { + /* Failed Refresh request */ + const pj_str_t *err_msg = NULL; + + pj_assert(status != PJ_SUCCESS); + + if (response) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + err_msg = &err_attr->reason; + } + } + + /* Notify and destroy */ + on_session_fail(sess, method, status, err_msg); + } + + } else if (method == PJ_STUN_CHANNEL_BIND_METHOD) { + /* Handle ChannelBind response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + /* Successful ChannelBind response */ + struct ch_t *ch = (struct ch_t *)token; + + pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL); + ch->bound = PJ_TRUE; + + /* Update hash table */ + lookup_ch_by_addr(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr), PJ_TRUE, PJ_TRUE); + + } else { + /* Failed ChannelBind response */ + pj_str_t reason = {"", 0}; + int err_code = 0; + char errbuf[PJ_ERR_MSG_SIZE]; + + pj_assert(status != PJ_SUCCESS); + + if (response) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + err_code = err_attr->err_code; + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + reason = err_attr->reason; + } + } else { + err_code = status; + reason = pj_strerror(status, errbuf, sizeof(errbuf)); + } + + PJ_LOG(1, (sess->obj_name, "ChannelBind failed: %d/%.*s", err_code, (int)reason.slen, reason.ptr)); + + if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) { + /* Allocation mismatch means allocation no longer exists */ + on_session_fail(sess, PJ_STUN_CHANNEL_BIND_METHOD, status, &reason); + return; + } + } + + } else if (method == PJ_STUN_CREATE_PERM_METHOD) { + /* Handle CreatePermission response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + /* No special handling when the request is successful. */ + } else { + /* Iterate the permission table and invalidate all permissions + * that are related to this request. + */ + pj_hash_iterator_t it_buf, *it; + char ipstr[PJ_INET6_ADDRSTRLEN + 10]; + int err_code; + char errbuf[PJ_ERR_MSG_SIZE]; + pj_str_t reason; + + pj_assert(status != PJ_SUCCESS); + + if (response) { + const pj_stun_errcode_attr *eattr; + + eattr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (eattr) { + err_code = eattr->err_code; + reason = eattr->reason; + } else { + err_code = -1; + reason = pj_str("?"); + } + } else { + err_code = status; + reason = pj_strerror(status, errbuf, sizeof(errbuf)); + } + + it = pj_hash_first(sess->perm_table, &it_buf); + while (it) { + struct perm_t *perm = (struct perm_t *)pj_hash_this(sess->perm_table, it); + it = pj_hash_next(sess->perm_table, it); + + if (perm->req_token == token) { + PJ_LOG(1, (sess->obj_name, "CreatePermission failed for IP %s: %d/%.*s", + pj_sockaddr_print(&perm->addr, ipstr, sizeof(ipstr), 2), err_code, (int)reason.slen, + reason.ptr)); + + invalidate_perm(sess, perm); + } + } + + if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) { + /* Allocation mismatch means allocation no longer exists */ + on_session_fail(sess, PJ_STUN_CREATE_PERM_METHOD, status, &reason); + return; + } + } + + } else if (method == PJ_STUN_CONNECTION_BIND_METHOD) { + /* Handle ConnectionBind response */ + struct conn_bind_t *conn_bind = (struct conn_bind_t *)token; + + if (status != PJ_SUCCESS || !PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + pj_str_t reason = {0}; + if (status == PJ_SUCCESS) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + reason = err_attr->reason; + } else { + status = PJNATH_EINSTUNMSG; + } + } + pj_perror(1, sess->obj_name, status, "ConnectionBind failed: %.*s", (int)reason.slen, reason.ptr); + } + + /* Notify app */ + if (sess->cb.on_connection_bind_status) { + (*sess->cb.on_connection_bind_status)(sess, status, conn_bind->id, &conn_bind->peer_addr, + conn_bind->peer_addr_len); + } + } else if (method == PJ_STUN_CONNECT_METHOD) { + /* Handle Connct response */ + struct pj_sockaddr_t *peer_addr = (struct pj_sockaddr_t *)token; + pj_uint32_t conn_id = 0; + + if (status != PJ_SUCCESS || !PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + pj_str_t reason = {0}; + if (status == PJ_SUCCESS) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + reason = err_attr->reason; + } else { + status = PJNATH_EINSTUNMSG; + } + } + pj_perror(1, sess->obj_name, status, "Connect failed: %.*s", (int)reason.slen, reason.ptr); + } else { + const pj_stun_uint_attr *conn_id_attr; + conn_id_attr = (const pj_stun_uint_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CONNECTION_ID, 0); + if (conn_id_attr == NULL) { + status = PJNATH_EINSTUNMSG; + pj_perror(1, sess->obj_name, status, "Error: Missing CONNECTION-ID attribute"); + } else { + conn_id = conn_id_attr->value; + } + } + + /* Notify app */ + if (sess->cb.on_connect_complete) { + (*sess->cb.on_connect_complete)(sess, status, conn_id, peer_addr, pj_sockaddr_get_len(peer_addr)); + } + } else { + PJ_LOG(4, (sess->obj_name, "Unexpected STUN %s response", pj_stun_get_method_name(response->hdr.type))); + } +} + +/* + * Notification from STUN session on incoming STUN Indication + * message. + */ +static pj_status_t stun_on_rx_indication(pj_stun_session *stun, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_turn_session *sess; + pj_stun_xor_peer_addr_attr *peer_attr; + pj_stun_icmp_attr *icmp; + pj_stun_data_attr *data_attr; + + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (pj_turn_session *)pj_stun_session_get_user_data(stun); + + /* ConnectionAttempt Indication */ + if (msg->hdr.type == PJ_STUN_CONNECTION_ATTEMPT_INDICATION) { + pj_stun_uint_attr *connection_id_attr; + + /* Get CONNECTION-ID attribute */ + connection_id_attr = (pj_stun_uint_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_CONNECTION_ID, 0); + + /* Get XOR-PEER-ADDRESS attribute */ + peer_attr = (pj_stun_xor_peer_addr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0); + + /* Must have both XOR-PEER-ADDRESS and CONNECTION-ID attributes */ + if (!peer_attr || !connection_id_attr) { + PJ_LOG(4, (sess->obj_name, "Received ConnectionAttempt indication with missing " + "attributes")); + return PJ_EINVALIDOP; + } + + /* Notify application */ + if (sess->cb.on_connection_attempt) { + (*sess->cb.on_connection_attempt)(sess, connection_id_attr->value, &peer_attr->sockaddr, + pj_sockaddr_get_len(&peer_attr->sockaddr)); + } + return PJ_SUCCESS; + } + + /* Next, expecting Data Indication only */ + if (msg->hdr.type != PJ_STUN_DATA_INDICATION) { + PJ_LOG(4, (sess->obj_name, "Unexpected STUN %s indication", pj_stun_get_method_name(msg->hdr.type))); + return PJ_EINVALIDOP; + } + + /* Check if there is ICMP attribute in the message */ + icmp = (pj_stun_icmp_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICMP, 0); + if (icmp != NULL) { + /* This is a forwarded ICMP packet. Ignore it for now */ + return PJ_SUCCESS; + } + + /* Get XOR-PEER-ADDRESS attribute */ + peer_attr = (pj_stun_xor_peer_addr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0); + + /* Get DATA attribute */ + data_attr = (pj_stun_data_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_DATA, 0); + + /* Must have both XOR-PEER-ADDRESS and DATA attributes */ + if (!peer_attr || !data_attr) { + PJ_LOG(4, (sess->obj_name, "Received Data indication with missing attributes")); + return PJ_EINVALIDOP; + } + + /* Notify application */ + if (sess->cb.on_rx_data) { + (*sess->cb.on_rx_data)(sess, data_attr->data, data_attr->length, &peer_attr->sockaddr, + pj_sockaddr_get_len(&peer_attr->sockaddr)); + } + + return PJ_SUCCESS; +} + +/* + * Notification on completion of DNS SRV resolution. + */ +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec) +{ + pj_turn_session *sess = (pj_turn_session *)user_data; + unsigned i, cnt, tot_cnt; + + /* Check failure */ + if (status != PJ_SUCCESS || sess->pending_destroy) { + set_state(sess, PJ_TURN_STATE_DESTROYING); + sess_shutdown(sess, status); + pj_grp_lock_dec_ref(sess->grp_lock); + return; + } + + /* Calculate total number of server entries in the response */ + tot_cnt = 0; + for (i = 0; i < rec->count; ++i) { + tot_cnt += rec->entry[i].server.addr_count; + } + + if (tot_cnt > PJ_TURN_MAX_DNS_SRV_CNT) + tot_cnt = PJ_TURN_MAX_DNS_SRV_CNT; + + /* Allocate server entries */ + sess->srv_addr_list = (pj_sockaddr *)pj_pool_calloc(sess->pool, tot_cnt, sizeof(pj_sockaddr)); + + /* Copy results to server entries */ + for (i = 0, cnt = 0; i < rec->count && cnt < PJ_TURN_MAX_DNS_SRV_CNT; ++i) { + unsigned j; + + for (j = 0; j < rec->entry[i].server.addr_count && cnt < PJ_TURN_MAX_DNS_SRV_CNT; ++j) { + if (rec->entry[i].server.addr[j].af == sess->af) { + pj_sockaddr *addr = &sess->srv_addr_list[cnt]; + + addr->addr.sa_family = sess->af; + pj_sockaddr_set_port(addr, rec->entry[i].port); + if (sess->af == pj_AF_INET6()) + addr->ipv6.sin6_addr = rec->entry[i].server.addr[j].ip.v6; + else + addr->ipv4.sin_addr = rec->entry[i].server.addr[j].ip.v4; + + ++cnt; + } + } + } + sess->srv_addr_cnt = (pj_uint16_t)cnt; + + /* Set current server */ + sess->srv_addr = &sess->srv_addr_list[0]; + + /* Set state to PJ_TURN_STATE_RESOLVED */ + set_state(sess, PJ_TURN_STATE_RESOLVED); + + /* Run pending allocation */ + if (sess->pending_alloc) { + pj_status_t status2; + status2 = pj_turn_session_alloc(sess, NULL); + if (status2 != PJ_SUCCESS) + on_session_fail(sess, PJ_STUN_ALLOCATE_METHOD, status2, NULL); + } + + pj_grp_lock_dec_ref(sess->grp_lock); +} + +/* + * Lookup peer descriptor from its address. + */ +static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, + pj_bool_t update, pj_bool_t bind_channel) +{ + pj_uint32_t hval = 0; + struct ch_t *ch; + + ch = (struct ch_t *)pj_hash_get(sess->ch_table, addr, addr_len, &hval); + if (ch == NULL && update) { + ch = PJ_POOL_ZALLOC_T(sess->pool, struct ch_t); + ch->num = PJ_TURN_INVALID_CHANNEL; + pj_memcpy(&ch->addr, addr, addr_len); + + /* Register by peer address */ + pj_hash_set(sess->pool, sess->ch_table, &ch->addr, addr_len, hval, ch); + } + + if (ch && update) { + pj_gettimeofday(&ch->expiry); + ch->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; + + if (bind_channel) { + pj_uint32_t hval2 = 0; + /* Register by channel number */ + pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound); + + if (pj_hash_get(sess->ch_table, &ch->num, sizeof(ch->num), &hval2) == 0) { + pj_hash_set(sess->pool, sess->ch_table, &ch->num, sizeof(ch->num), hval2, ch); + } + } + } + + /* Also create/update permission for this destination. Ideally we + * should update this when we receive the successful response, + * but that would cause duplicate CreatePermission to be sent + * during refreshing. + */ + if (ch && update) { + lookup_perm(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr), PJ_TRUE); + } + + return ch; +} + +/* + * Lookup channel descriptor from its channel number. + */ +static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess, pj_uint16_t chnum) +{ + return (struct ch_t *)pj_hash_get(sess->ch_table, &chnum, sizeof(chnum), NULL); +} + +/* + * Lookup permission and optionally create if it doesn't exist. + */ +static struct perm_t *lookup_perm(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, pj_bool_t update) +{ + pj_uint32_t hval = 0; + pj_sockaddr perm_addr; + struct perm_t *perm; + + /* make sure port number if zero */ + if (pj_sockaddr_get_port(addr) != 0) { + pj_memcpy(&perm_addr, addr, addr_len); + pj_sockaddr_set_port(&perm_addr, 0); + addr = &perm_addr; + } + + /* lookup and create if it doesn't exist and wanted */ + perm = (struct perm_t *)pj_hash_get(sess->perm_table, addr, addr_len, &hval); + if (perm == NULL && update) { + perm = PJ_POOL_ZALLOC_T(sess->pool, struct perm_t); + pj_memcpy(&perm->addr, addr, addr_len); + perm->hval = hval; + + pj_hash_set(sess->pool, sess->perm_table, &perm->addr, addr_len, perm->hval, perm); + } + + if (perm && update) { + pj_gettimeofday(&perm->expiry); + perm->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; + } + + return perm; +} + +/* + * Delete permission + */ +static void invalidate_perm(pj_turn_session *sess, struct perm_t *perm) +{ + pj_hash_set(NULL, sess->perm_table, &perm->addr, pj_sockaddr_get_len(&perm->addr), perm->hval, NULL); +} + +/* + * Scan permission's hash table to refresh the permission. + */ +static unsigned refresh_permissions(pj_turn_session *sess, const pj_time_val *now) +{ + pj_stun_tx_data *tdata = NULL; + unsigned count = 0; + void *req_token = NULL; + pj_hash_iterator_t *it, itbuf; + pj_status_t status; + + it = pj_hash_first(sess->perm_table, &itbuf); + while (it) { + struct perm_t *perm = (struct perm_t *)pj_hash_this(sess->perm_table, it); + + it = pj_hash_next(sess->perm_table, it); + + if (perm->expiry.sec - 1 <= now->sec) { + if (perm->renew) { + /* Renew this permission */ + if (tdata == NULL) { + /* Create a bare CreatePermission request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CREATE_PERM_REQUEST, PJ_STUN_MAGIC, NULL, + &tdata); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (sess->obj_name, status, "Error creating CreatePermission request")); + return 0; + } + + /* Create request token to map the request to the perm + * structures which the request belongs. + */ + req_token = (void *)(pj_ssize_t)pj_rand(); + } + + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, + &perm->addr, sizeof(perm->addr)); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess->stun, tdata); + return 0; + } + + perm->expiry = *now; + perm->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; + perm->req_token = req_token; + ++count; + + } else { + /* This permission has expired and app doesn't want + * us to renew, so delete it from the hash table. + */ + invalidate_perm(sess, perm); + } + } + } + + if (tdata) { + status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), + sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (sess->obj_name, status, "Error sending CreatePermission request")); + count = 0; + } + } + + return count; +} + +/* + * Timer event. + */ +static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e) +{ + pj_turn_session *sess = (pj_turn_session *)e->user_data; + enum timer_id_t eid; + + PJ_UNUSED_ARG(th); + + pj_grp_lock_acquire(sess->grp_lock); + + eid = (enum timer_id_t)e->id; + e->id = TIMER_NONE; + + if (eid == TIMER_KEEP_ALIVE) { + pj_time_val now; + pj_hash_iterator_t itbuf, *it; + pj_bool_t resched = PJ_TRUE; + pj_bool_t pkt_sent = PJ_FALSE; + + if (sess->state >= PJ_TURN_STATE_DEALLOCATING) { + /* Ignore if we're deallocating */ + goto on_return; + } + + pj_gettimeofday(&now); + + /* Refresh allocation if it's time to do so */ + if (PJ_TIME_VAL_LTE(sess->expiry, now)) { + int lifetime = sess->alloc_param.lifetime; + + if (lifetime == 0) + lifetime = -1; + + send_refresh(sess, lifetime); + resched = PJ_FALSE; + pkt_sent = PJ_TRUE; + } + + /* Scan hash table to refresh bound channels */ + it = pj_hash_first(sess->ch_table, &itbuf); + while (it) { + struct ch_t *ch = (struct ch_t *)pj_hash_this(sess->ch_table, it); + if (ch->bound && PJ_TIME_VAL_LTE(ch->expiry, now)) { + + /* Send ChannelBind to refresh channel binding and + * permission. + */ + pj_turn_session_bind_channel(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr)); + pkt_sent = PJ_TRUE; + } + + it = pj_hash_next(sess->ch_table, it); + } + + /* Scan permission table to refresh permissions */ + if (refresh_permissions(sess, &now)) + pkt_sent = PJ_TRUE; + + /* If no packet is sent, send a blank Send indication to + * refresh local NAT. + */ + if (!pkt_sent && sess->alloc_param.ka_interval > 0) { + pj_stun_tx_data *tdata; + pj_status_t rc; + + /* Create blank SEND-INDICATION */ + rc = pj_stun_session_create_ind(sess->stun, PJ_STUN_SEND_INDICATION, &tdata); + if (rc == PJ_SUCCESS) { + /* Add DATA attribute with zero length */ + pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_DATA, NULL, 0); + + /* Send the indication */ + pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, PJ_FALSE, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + } + } + + /* Reshcedule timer */ + if (resched) { + pj_time_val delay; + + delay.sec = sess->ka_interval; + delay.msec = 0; + + pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, &delay, TIMER_KEEP_ALIVE, sess->grp_lock); + } + + } else if (eid == TIMER_DESTROY) { + /* Time to destroy */ + do_destroy(sess); + } else { + pj_assert(!"Unknown timer event"); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c new file mode 100755 index 000000000..164072bb5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c @@ -0,0 +1,1733 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { TIMER_NONE, TIMER_DESTROY }; + +enum { MAX_BIND_RETRY = 100 }; + +#define INIT 0x1FFFFFFF + +enum { + DATACONN_STATE_NULL, + DATACONN_STATE_INITSOCK, + DATACONN_STATE_CONN_BINDING, + DATACONN_STATE_READY, +}; + +/* This structure describe data connection of TURN TCP allocations + * (RFC 6062). + */ +typedef struct tcp_data_conn_t { + pj_pool_t *pool; + + pj_uint32_t id; /* Connection ID. */ + int state; /* Connection state. */ + pj_sockaddr peer_addr; /* Peer address (mapped). */ + unsigned peer_addr_len; + + pj_activesock_t *asock; /* Active socket. */ + pj_ioqueue_op_key_t send_key; + + pj_turn_sock *turn_sock; /* TURN socket parent. */ +} tcp_data_conn_t; + +struct pj_turn_sock { + pj_pool_t *pool; + const char *obj_name; + pj_turn_session *sess; + pj_turn_sock_cb cb; + void *user_data; + + pj_bool_t is_destroying; + pj_grp_lock_t *grp_lock; + + pj_turn_alloc_param alloc_param; + pj_stun_config cfg; + pj_turn_sock_cfg setting; + + pj_timer_entry timer; + + int af; + pj_turn_tp_type conn_type; + pj_activesock_t *active_sock; +#if PJ_HAS_SSL_SOCK + pj_ssl_sock_t *ssl_sock; + pj_ssl_cert_t *cert; + pj_str_t server_name; +#endif + + pj_ioqueue_op_key_t send_key; + pj_ioqueue_op_key_t int_send_key; + unsigned pkt_len; + unsigned body_len; + + /* Data connection, when peer_conn_type==PJ_TURN_TP_TCP (RFC 6062) */ + unsigned data_conn_cnt; + tcp_data_conn_t data_conn[PJ_TURN_MAX_TCP_CONN_CNT]; +}; + +/* + * Callback prototypes. + */ +static pj_status_t turn_on_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); +static pj_status_t turn_on_stun_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); +static void turn_on_channel_bound(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len, + unsigned ch_num); +static void turn_on_rx_data(pj_turn_session *sess, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_state(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state); +static void turn_on_connection_attempt(pj_turn_session *sess, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_connection_bind_status(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); +static void turn_on_connect_complete(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + +static pj_bool_t on_data_read(pj_turn_sock *turn_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +static pj_bool_t on_data_sent(pj_turn_sock *turn_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +static pj_bool_t on_connect_complete(pj_turn_sock *turn_sock, pj_status_t status); + +/* + * Activesock callback + */ +static pj_bool_t on_connect_complete_asock(pj_activesock_t *asock, pj_status_t status); +static pj_bool_t on_data_read_asock(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +static pj_bool_t on_data_sent_asock(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + +/* + * SSL sock callback + */ +#if PJ_HAS_SSL_SOCK +static pj_bool_t on_connect_complete_ssl_sock(pj_ssl_sock_t *ssl_sock, pj_status_t status); +static pj_bool_t on_data_read_ssl_sock(pj_ssl_sock_t *ssl_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +#endif + +static pj_bool_t dataconn_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +static pj_bool_t dataconn_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +static pj_bool_t dataconn_on_connect_complete(pj_activesock_t *asock, pj_status_t status); +static void dataconn_cleanup(tcp_data_conn_t *conn); + +static void turn_sock_on_destroy(void *comp); +static void destroy(pj_turn_sock *turn_sock); +static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e); + +/* Init config */ +PJ_DEF(void) pj_turn_sock_cfg_default(pj_turn_sock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->max_pkt_size = PJ_TURN_MAX_PKT_LEN; + cfg->qos_type = PJ_QOS_TYPE_BEST_EFFORT; + cfg->qos_ignore_error = PJ_TRUE; + +#if PJ_HAS_SSL_SOCK + pj_turn_sock_tls_cfg_default(&cfg->tls_cfg); +#endif +} + +#if PJ_HAS_SSL_SOCK + +PJ_DEF(void) pj_turn_sock_tls_cfg_default(pj_turn_sock_tls_cfg *tls_cfg) +{ + pj_bzero(tls_cfg, sizeof(*tls_cfg)); + pj_ssl_sock_param_default(&tls_cfg->ssock_param); + tls_cfg->ssock_param.proto = PJ_TURN_TLS_DEFAULT_PROTO; +} + +PJ_DEF(void) pj_turn_sock_tls_cfg_dup(pj_pool_t *pool, pj_turn_sock_tls_cfg *dst, const pj_turn_sock_tls_cfg *src) +{ + pj_memcpy(dst, src, sizeof(*dst)); + pj_strdup_with_null(pool, &dst->ca_list_file, &src->ca_list_file); + pj_strdup_with_null(pool, &dst->ca_list_path, &src->ca_list_path); + pj_strdup_with_null(pool, &dst->cert_file, &src->cert_file); + pj_strdup_with_null(pool, &dst->privkey_file, &src->privkey_file); + pj_strdup_with_null(pool, &dst->password, &src->password); + pj_strdup(pool, &dst->ca_buf, &src->ca_buf); + pj_strdup(pool, &dst->cert_buf, &src->cert_buf); + pj_strdup(pool, &dst->privkey_buf, &src->privkey_buf); + pj_ssl_sock_param_copy(pool, &dst->ssock_param, &src->ssock_param); +} + +static void wipe_buf(pj_str_t *buf) +{ + volatile char *p = buf->ptr; + pj_ssize_t len = buf->slen; + while (len--) + *p++ = 0; + buf->slen = 0; +} + +PJ_DEF(void) pj_turn_sock_tls_cfg_wipe_keys(pj_turn_sock_tls_cfg *tls_cfg) +{ + wipe_buf(&tls_cfg->ca_list_file); + wipe_buf(&tls_cfg->ca_list_path); + wipe_buf(&tls_cfg->cert_file); + wipe_buf(&tls_cfg->privkey_file); + wipe_buf(&tls_cfg->password); + wipe_buf(&tls_cfg->ca_buf); + wipe_buf(&tls_cfg->cert_buf); + wipe_buf(&tls_cfg->privkey_buf); +} +#endif + +/* + * Create. + */ +PJ_DEF(pj_status_t) +pj_turn_sock_create(pj_stun_config *cfg, int af, pj_turn_tp_type conn_type, const pj_turn_sock_cb *cb, + const pj_turn_sock_cfg *setting, void *user_data, pj_turn_sock **p_turn_sock) +{ + pj_turn_sock *turn_sock; + pj_turn_session_cb sess_cb; + pj_turn_sock_cfg default_setting; + pj_pool_t *pool; + const char *name_tmpl; + pj_status_t status; + + PJ_ASSERT_RETURN(cfg && p_turn_sock, PJ_EINVAL); + PJ_ASSERT_RETURN(af == pj_AF_INET() || af == pj_AF_INET6(), PJ_EINVAL); + PJ_ASSERT_RETURN(conn_type != PJ_TURN_TP_TCP || PJ_HAS_TCP, PJ_EINVAL); + PJ_ASSERT_RETURN(conn_type != PJ_TURN_TP_TLS || PJ_HAS_SSL_SOCK, PJ_EINVAL); + + if (!setting) { + pj_turn_sock_cfg_default(&default_setting); + setting = &default_setting; + } + + switch (conn_type) { + case PJ_TURN_TP_UDP: + name_tmpl = "udprel%p"; + break; + case PJ_TURN_TP_TCP: + name_tmpl = "tcprel%p"; + break; +#if PJ_HAS_SSL_SOCK + case PJ_TURN_TP_TLS: + name_tmpl = "tlsrel%p"; + break; +#endif + default: + PJ_ASSERT_RETURN(!"Invalid TURN conn_type", PJ_EINVAL); + name_tmpl = "tcprel%p"; + break; + } + + /* Create and init basic data structure */ + pool = pj_pool_create(cfg->pf, name_tmpl, PJNATH_POOL_LEN_TURN_SOCK, PJNATH_POOL_INC_TURN_SOCK, NULL); + turn_sock = PJ_POOL_ZALLOC_T(pool, pj_turn_sock); + turn_sock->pool = pool; + turn_sock->obj_name = pool->obj_name; + turn_sock->user_data = user_data; + turn_sock->af = af; + turn_sock->conn_type = conn_type; + + /* Copy STUN config (this contains ioqueue, timer heap, etc.) */ + pj_memcpy(&turn_sock->cfg, cfg, sizeof(*cfg)); + + /* Copy setting (QoS parameters etc */ + pj_memcpy(&turn_sock->setting, setting, sizeof(*setting)); +#if PJ_HAS_SSL_SOCK + pj_turn_sock_tls_cfg_dup(turn_sock->pool, &turn_sock->setting.tls_cfg, &setting->tls_cfg); +#endif + + /* Set callback */ + if (cb) { + pj_memcpy(&turn_sock->cb, cb, sizeof(*cb)); + } + + /* Session lock */ + if (setting && setting->grp_lock) { + turn_sock->grp_lock = setting->grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &turn_sock->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(turn_sock->grp_lock); + pj_grp_lock_add_handler(turn_sock->grp_lock, pool, turn_sock, &turn_sock_on_destroy); + + /* Init timer */ + pj_timer_entry_init(&turn_sock->timer, TIMER_NONE, turn_sock, &timer_cb); + + /* Init TURN session */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_send_pkt = &turn_on_send_pkt; + sess_cb.on_stun_send_pkt = &turn_on_stun_send_pkt; + sess_cb.on_channel_bound = &turn_on_channel_bound; + sess_cb.on_rx_data = &turn_on_rx_data; + sess_cb.on_state = &turn_on_state; + sess_cb.on_connect_complete = &turn_on_connect_complete; + sess_cb.on_connection_attempt = &turn_on_connection_attempt; + sess_cb.on_connection_bind_status = &turn_on_connection_bind_status; + status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type, turn_sock->grp_lock, &sess_cb, 0, turn_sock, + &turn_sock->sess); + if (status != PJ_SUCCESS) { + destroy(turn_sock); + return status; + } + + /* Note: socket and ioqueue will be created later once the TURN server + * has been resolved. + */ + + *p_turn_sock = turn_sock; + return PJ_SUCCESS; +} + +/* + * Destroy. + */ +static void turn_sock_on_destroy(void *comp) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)comp; + + if (turn_sock->pool) { + PJ_LOG(4, (turn_sock->obj_name, "TURN socket destroyed")); + pj_pool_safe_release(&turn_sock->pool); + } +} + +static void destroy(pj_turn_sock *turn_sock) +{ + unsigned i; + + PJ_LOG(4, + (turn_sock->obj_name, "TURN socket destroy request, ref_cnt=%d", pj_grp_lock_get_ref(turn_sock->grp_lock))); + + pj_grp_lock_acquire(turn_sock->grp_lock); + if (turn_sock->is_destroying) { + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + turn_sock->is_destroying = PJ_TRUE; + if (turn_sock->sess) + pj_turn_session_shutdown(turn_sock->sess); + if (turn_sock->active_sock) + pj_activesock_close(turn_sock->active_sock); +#if PJ_HAS_SSL_SOCK + if (turn_sock->ssl_sock) + pj_ssl_sock_close(turn_sock->ssl_sock); +#endif + + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + dataconn_cleanup(&turn_sock->data_conn[i]); + } + turn_sock->data_conn_cnt = 0; + + pj_grp_lock_dec_ref(turn_sock->grp_lock); + pj_grp_lock_release(turn_sock->grp_lock); +} + +PJ_DEF(void) pj_turn_sock_destroy(pj_turn_sock *turn_sock) +{ + pj_grp_lock_acquire(turn_sock->grp_lock); + if (turn_sock->is_destroying) { + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + if (turn_sock->sess) { + pj_turn_session_shutdown(turn_sock->sess); + /* This will ultimately call our state callback, and when + * session state is DESTROYING we will schedule a timer to + * destroy ourselves. + */ + } else { + destroy(turn_sock); + } + + pj_grp_lock_release(turn_sock->grp_lock); +} + +/* Timer callback */ +static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)e->user_data; + int eid = e->id; + + PJ_UNUSED_ARG(th); + + e->id = TIMER_NONE; + + switch (eid) { + case TIMER_DESTROY: + destroy(turn_sock); + break; + default: + pj_assert(!"Invalid timer id"); + break; + } +} + +/* Display error */ +static void show_err(pj_turn_sock *turn_sock, const char *title, pj_status_t status) +{ + PJ_PERROR(4, (turn_sock->obj_name, status, title)); +} + +/* On error, terminate session */ +static void sess_fail(pj_turn_sock *turn_sock, const char *title, pj_status_t status) +{ + show_err(turn_sock, title, status); + if (turn_sock->sess) { + pj_turn_session_destroy(turn_sock->sess, status); + } +} + +/* + * Set user data. + */ +PJ_DEF(pj_status_t) pj_turn_sock_set_user_data(pj_turn_sock *turn_sock, void *user_data) +{ + PJ_ASSERT_RETURN(turn_sock, PJ_EINVAL); + turn_sock->user_data = user_data; + return PJ_SUCCESS; +} + +/* + * Get user data. + */ +PJ_DEF(void *) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock) +{ + PJ_ASSERT_RETURN(turn_sock, NULL); + return turn_sock->user_data; +} + +/* + * Get group lock. + */ +PJ_DEF(pj_grp_lock_t *) pj_turn_sock_get_grp_lock(pj_turn_sock *turn_sock) +{ + PJ_ASSERT_RETURN(turn_sock, NULL); + return turn_sock->grp_lock; +} + +/** + * Get info. + */ +PJ_DEF(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock, pj_turn_session_info *info) +{ + PJ_ASSERT_RETURN(turn_sock && info, PJ_EINVAL); + + if (turn_sock->sess) { + return pj_turn_session_get_info(turn_sock->sess, info); + } else { + pj_bzero(info, sizeof(*info)); + info->state = PJ_TURN_STATE_NULL; + return PJ_SUCCESS; + } +} + +/** + * Lock the TURN socket. Application may need to call this function to + * synchronize access to other objects to avoid deadlock. + */ +PJ_DEF(pj_status_t) pj_turn_sock_lock(pj_turn_sock *turn_sock) +{ + return pj_grp_lock_acquire(turn_sock->grp_lock); +} + +/** + * Unlock the TURN socket. + */ +PJ_DEF(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock) +{ + return pj_grp_lock_release(turn_sock->grp_lock); +} + +/* + * Set STUN message logging for this TURN session. + */ +PJ_DEF(void) pj_turn_sock_set_log(pj_turn_sock *turn_sock, unsigned flags) +{ + pj_turn_session_set_log(turn_sock->sess, flags); +} + +/* + * Set software name + */ +PJ_DEF(pj_status_t) pj_turn_sock_set_software_name(pj_turn_sock *turn_sock, const pj_str_t *sw) +{ + return pj_turn_session_set_software_name(turn_sock->sess, sw); +} + +/* + * Initialize. + */ +PJ_DEF(pj_status_t) +pj_turn_sock_alloc(pj_turn_sock *turn_sock, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver, + const pj_stun_auth_cred *cred, const pj_turn_alloc_param *param) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(turn_sock && domain, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess, PJ_EINVALIDOP); + + pj_grp_lock_acquire(turn_sock->grp_lock); + + /* Copy alloc param. We will call session_alloc() only after the + * server address has been resolved. + */ + if (param) { + pj_turn_alloc_param_copy(turn_sock->pool, &turn_sock->alloc_param, param); + } else { + pj_turn_alloc_param_default(&turn_sock->alloc_param); + } + + /* Set credental */ + if (cred) { + status = pj_turn_session_set_credential(turn_sock->sess, cred); + if (status != PJ_SUCCESS) { + sess_fail(turn_sock, "Error setting credential", status); + pj_grp_lock_release(turn_sock->grp_lock); + return status; + } + } +#if PJ_HAS_SSL_SOCK + if (turn_sock->conn_type == PJ_TURN_TP_TLS) { + pj_strdup_with_null(turn_sock->pool, &turn_sock->server_name, domain); + } +#endif + + /* Resolve server */ + status = pj_turn_session_set_server(turn_sock->sess, domain, default_port, resolver); + if (status != PJ_SUCCESS) { + sess_fail(turn_sock, "Error setting TURN server", status); + pj_grp_lock_release(turn_sock->grp_lock); + return status; + } else if (!turn_sock->sess) { + /* TURN session may have been destroyed here, i.e: when DNS resolution + * completed synchronously and TURN allocation failed. + */ + PJ_LOG(4, (turn_sock->obj_name, "TURN session destroyed in setting " + "TURN server")); + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_EGONE; + } + + /* Done for now. The next work will be done when session state moved + * to RESOLVED state. + */ + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_SUCCESS; +} + +/* + * Install permission + */ +PJ_DEF(pj_status_t) +pj_turn_sock_set_perm(pj_turn_sock *turn_sock, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options) +{ + if (turn_sock->sess == NULL) + return PJ_EINVALIDOP; + + return pj_turn_session_set_perm(turn_sock->sess, addr_cnt, addr, options); +} + +/* + * Send packet. + */ +PJ_DEF(pj_status_t) +pj_turn_sock_sendto(pj_turn_sock *turn_sock, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *addr, + unsigned addr_len) +{ + PJ_ASSERT_RETURN(turn_sock && addr && addr_len, PJ_EINVAL); + + if (turn_sock->sess == NULL) + return PJ_EINVALIDOP; + + /* TURN session may add some headers to the packet, so we need + * to store our actual data length to be sent here. + */ + turn_sock->body_len = pkt_len; + return pj_turn_session_sendto(turn_sock->sess, pkt, pkt_len, addr, addr_len); +} + +/* + * Bind a peer address to a channel number. + */ +PJ_DEF(pj_status_t) pj_turn_sock_bind_channel(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len) +{ + PJ_ASSERT_RETURN(turn_sock && peer && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess != NULL, PJ_EINVALIDOP); + + return pj_turn_session_bind_channel(turn_sock->sess, peer, addr_len); +} + +/** + * Send Connect request for the specified a peer address. + */ +PJ_DEF(pj_status_t) pj_turn_sock_connect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + PJ_ASSERT_RETURN(turn_sock && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess != NULL, PJ_EINVALIDOP); + + return pj_turn_session_connect(turn_sock->sess, peer_addr, addr_len); +} + +/** + * Close existing connection to the peer address. + */ +PJ_DEF(pj_status_t) pj_turn_sock_disconnect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer_addr, unsigned addr_len) + +{ + unsigned i; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + + PJ_ASSERT_RETURN(turn_sock && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess != NULL, PJ_EINVALIDOP); + + pj_grp_lock_acquire(turn_sock->grp_lock); + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + tcp_data_conn_t *conn = &turn_sock->data_conn[i]; + if (conn->state < DATACONN_STATE_CONN_BINDING) + continue; + if (pj_sockaddr_cmp(&conn->peer_addr, peer_addr) == 0) { + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_SUCCESS; + } + } + + PJ_LOG(4, (turn_sock->obj_name, "Connection for peer %s is not exist", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_ENOTFOUND; +} + +/* + * Notification when outgoing TCP socket has been connected. + */ +static pj_bool_t on_connect_complete(pj_turn_sock *turn_sock, pj_status_t status) +{ + pj_grp_lock_acquire(turn_sock->grp_lock); + + /* TURN session may have already been destroyed here. + * See ticket #1557 (https://github.com/pjsip/pjproject/issues/1557). + */ + if (!turn_sock->sess) { + sess_fail(turn_sock, "TURN session already destroyed", status); + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + if (status != PJ_SUCCESS) { + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + sess_fail(turn_sock, "UDP connect() error", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TCP) + sess_fail(turn_sock, "TCP connect() error", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) + sess_fail(turn_sock, "TLS connect() error", status); + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + if (turn_sock->conn_type != PJ_TURN_TP_UDP) { + PJ_LOG(5, (turn_sock->obj_name, "%s connected", turn_sock->conn_type == PJ_TURN_TP_TCP ? "TCP" : "TLS")); + } + + /* Kick start pending read operation */ + if (turn_sock->conn_type != PJ_TURN_TP_TLS) + status = pj_activesock_start_read(turn_sock->active_sock, turn_sock->pool, turn_sock->setting.max_pkt_size, 0); +#if PJ_HAS_SSL_SOCK + else + status = pj_ssl_sock_start_read(turn_sock->ssl_sock, turn_sock->pool, turn_sock->setting.max_pkt_size, 0); +#endif + + /* Init send_key */ + pj_ioqueue_op_key_init(&turn_sock->send_key, sizeof(turn_sock->send_key)); + pj_ioqueue_op_key_init(&turn_sock->int_send_key, sizeof(turn_sock->int_send_key)); + + /* Send Allocate request */ + status = pj_turn_session_alloc(turn_sock->sess, &turn_sock->alloc_param); + if (status != PJ_SUCCESS) { + sess_fail(turn_sock, "Error sending ALLOCATE", status); + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_TRUE; +} + +static pj_bool_t on_connect_complete_asock(pj_activesock_t *asock, pj_status_t status) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_activesock_get_user_data(asock); + if (!turn_sock) + return PJ_FALSE; + + return on_connect_complete(turn_sock, status); +} + +#if PJ_HAS_SSL_SOCK +static pj_bool_t on_connect_complete_ssl_sock(pj_ssl_sock_t *ssl_sock, pj_status_t status) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_ssl_sock_get_user_data(ssl_sock); + if (!turn_sock) + return PJ_FALSE; + + return on_connect_complete(turn_sock, status); +} +#endif + +static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos) +{ + return (pj_uint16_t)((buf[pos + 0] << 8) | (buf[pos + 1] << 0)); +} + +/* Quick check to determine if there is enough packet to process in the + * incoming buffer. Return the packet length, or zero if there's no packet. + */ +static unsigned has_packet(pj_turn_sock *turn_sock, const void *buf, pj_size_t bufsize) +{ + pj_bool_t is_stun; + + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + return (unsigned)bufsize; + + /* Quickly check if this is STUN message, by checking the first two bits and + * size field which must be multiple of 4 bytes + */ + is_stun = ((((pj_uint8_t *)buf)[0] & 0xC0) == 0) && ((GETVAL16H((const pj_uint8_t *)buf, 2) & 0x03) == 0); + + if (is_stun) { + pj_size_t msg_len = GETVAL16H((const pj_uint8_t *)buf, 2); + return (unsigned)((msg_len + 20 <= bufsize) ? msg_len + 20 : 0); + } else { + /* This must be ChannelData. */ + pj_turn_channel_data cd; + + if (bufsize < 4) + return 0; + + /* Decode ChannelData packet */ + pj_memcpy(&cd, buf, sizeof(pj_turn_channel_data)); + cd.length = pj_ntohs(cd.length); + + if (bufsize >= cd.length + sizeof(cd)) + return (cd.length + sizeof(cd) + 3) & (~3); + else + return 0; + } +} + +/* + * Notification from ioqueue when incoming UDP packet is received. + */ +static pj_bool_t on_data_read(pj_turn_sock *turn_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_bool_t ret = PJ_TRUE; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (status == PJ_SUCCESS && turn_sock->sess && !turn_sock->is_destroying) { + /* Report incoming packet to TURN session, repeat while we have + * "packet" in the buffer (required for stream-oriented transports) + */ + unsigned pkt_len; + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Incoming data, %lu bytes total buffer", size)); + + while ((pkt_len = has_packet(turn_sock, data, size)) != 0) { + pj_size_t parsed_len; + // const pj_uint8_t *pkt = (const pj_uint8_t*)data; + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Packet start: %02X %02X %02X %02X", + // pkt[0], pkt[1], pkt[2], pkt[3])); + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Processing %lu bytes packet of %lu bytes total buffer", + // pkt_len, size)); + + parsed_len = (unsigned)size; + pj_turn_session_on_rx_pkt(turn_sock->sess, data, size, &parsed_len); + + /* parsed_len may be zero if we have parsing error, so use our + * previous calculation to exhaust the bad packet. + */ + if (parsed_len == 0) + parsed_len = pkt_len; + + if (parsed_len < (unsigned)size) { + *remainder = size - parsed_len; + pj_memmove(data, ((char *)data) + parsed_len, *remainder); + } else { + *remainder = 0; + } + size = *remainder; + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Buffer size now %lu bytes", size)); + } + } else if (status != PJ_SUCCESS) { + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + sess_fail(turn_sock, "UDP connection closed", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TCP) + sess_fail(turn_sock, "TCP connection closed", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) + sess_fail(turn_sock, "TLS connection closed", status); + + ret = PJ_FALSE; + goto on_return; + } + +on_return: + pj_grp_lock_release(turn_sock->grp_lock); + + return ret; +} + +static pj_bool_t on_data_read_asock(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_activesock_get_user_data(asock); + + return on_data_read(turn_sock, data, size, status, remainder); +} + +static pj_bool_t on_data_sent(pj_turn_sock *turn_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + /* Don't report to callback if this is internal message. */ + if (send_key == &turn_sock->int_send_key) { + return PJ_TRUE; + } + + if (turn_sock->cb.on_data_sent) { + pj_ssize_t header_len, sent_size; + + /* Remove the length of packet header from sent size. */ + header_len = turn_sock->pkt_len - turn_sock->body_len; + sent_size = (sent > header_len) ? (sent - header_len) : 0; + (*turn_sock->cb.on_data_sent)(turn_sock, sent_size); + } + + return PJ_TRUE; +} + +static pj_bool_t on_data_sent_asock(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_activesock_get_user_data(asock); + + return on_data_sent(turn_sock, send_key, sent); +} + +#if PJ_HAS_SSL_SOCK +static pj_bool_t on_data_read_ssl_sock(pj_ssl_sock_t *ssl_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_ssl_sock_get_user_data(ssl_sock); + + return on_data_read(turn_sock, data, size, status, remainder); +} + +static pj_bool_t on_data_sent_ssl_sock(pj_ssl_sock_t *ssl_sock, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent) +{ + pj_turn_sock *turn_sock; + + PJ_UNUSED_ARG(op_key); + + turn_sock = (pj_turn_sock *)pj_ssl_sock_get_user_data(ssl_sock); + + /* Check for error/closure */ + if (bytes_sent <= 0) { + pj_status_t status; + + status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : (pj_status_t)-bytes_sent; + + sess_fail(turn_sock, "TLS send() error", status); + + return PJ_FALSE; + } + + return on_data_sent(turn_sock, op_key, bytes_sent); +} +#endif + +static pj_status_t send_pkt(pj_turn_session *sess, pj_bool_t internal, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_ssize_t len = pkt_len; + pj_status_t status = PJ_SUCCESS; + pj_ioqueue_op_key_t *send_key = &turn_sock->send_key; + + if (turn_sock == NULL || turn_sock->is_destroying) { + /* We've been destroyed */ + // https://github.com/pjsip/pjproject/issues/1316 + // pj_assert(!"We should shutdown gracefully"); + return PJ_EINVALIDOP; + } + + if (internal) + send_key = &turn_sock->int_send_key; + turn_sock->pkt_len = pkt_len; + + if (turn_sock->conn_type == PJ_TURN_TP_UDP) { + status = pj_activesock_sendto(turn_sock->active_sock, send_key, pkt, &len, 0, dst_addr, dst_addr_len); + } else if (turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) { + pj_turn_session_info info; + pj_turn_session_get_info(turn_sock->sess, &info); + if (pj_sockaddr_cmp(&info.server, dst_addr) == 0) { + /* Destination address is TURN server */ + status = pj_activesock_send(turn_sock->active_sock, send_key, pkt, &len, 0); + } else { + /* Destination address is peer, lookup data connection */ + unsigned i; + + status = PJ_ENOTFOUND; + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + tcp_data_conn_t *conn = &turn_sock->data_conn[i]; + if (conn->state < DATACONN_STATE_CONN_BINDING) + continue; + if (pj_sockaddr_cmp(&conn->peer_addr, dst_addr) == 0) { + status = pj_activesock_send(conn->asock, &conn->send_key, pkt, &len, 0); + break; + } + } + } + } else if (turn_sock->conn_type == PJ_TURN_TP_TCP) { + status = pj_activesock_send(turn_sock->active_sock, send_key, pkt, &len, 0); + } +#if PJ_HAS_SSL_SOCK + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) { + status = pj_ssl_sock_send(turn_sock->ssl_sock, send_key, pkt, &len, 0); + } +#endif + else { + PJ_ASSERT_RETURN(!"Invalid TURN conn_type", PJ_EINVAL); + } + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + show_err(turn_sock, "socket send()", status); + } + + return status; +} + +/* + * Callback from TURN session to send outgoing packet. + */ +static pj_status_t turn_on_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + return send_pkt(sess, PJ_FALSE, pkt, pkt_len, dst_addr, dst_addr_len); +} + +static pj_status_t turn_on_stun_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + return send_pkt(sess, PJ_TRUE, pkt, pkt_len, dst_addr, dst_addr_len); +} + +/* + * Callback from TURN session when a channel is successfully bound. + */ +static void turn_on_channel_bound(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len, + unsigned ch_num) +{ + PJ_UNUSED_ARG(sess); + PJ_UNUSED_ARG(peer_addr); + PJ_UNUSED_ARG(addr_len); + PJ_UNUSED_ARG(ch_num); +} + +/* + * Callback from TURN session upon incoming data. + */ +static void turn_on_rx_data(pj_turn_session *sess, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + if (turn_sock == NULL || turn_sock->is_destroying) { + /* We've been destroyed */ + return; + } + + if (turn_sock->alloc_param.peer_conn_type != PJ_TURN_TP_UDP) { + /* Data traffic for RFC 6062 is not via TURN session */ + return; + } + + if (turn_sock->cb.on_rx_data) { + (*turn_sock->cb.on_rx_data)(turn_sock, pkt, pkt_len, peer_addr, addr_len); + } +} + +/* + * Callback from TURN session when state has changed + */ +static void turn_on_state(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_status_t status = PJ_SUCCESS; + + if (turn_sock == NULL) { + /* We've been destroyed */ + return; + } + + /* Notify app first */ + if (turn_sock->cb.on_state) { + (*turn_sock->cb.on_state)(turn_sock, old_state, new_state); + } + + /* Make sure user hasn't destroyed us in the callback */ + if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) { + pj_turn_session_info info; + pj_turn_session_get_info(turn_sock->sess, &info); + new_state = info.state; + } + + if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) { + /* + * Once server has been resolved, initiate outgoing TCP + * connection to the server. + */ + pj_turn_session_info info; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + int sock_type; + pj_sock_t sock; + pj_activesock_cfg asock_cfg; + pj_activesock_cb asock_cb; + pj_sockaddr bound_addr, *cfg_bind_addr; + pj_uint16_t max_bind_retry; + + /* Close existing connection, if any. This happens when + * we're switching to alternate TURN server when either TCP + * connection or ALLOCATE request failed. + */ + if ((turn_sock->conn_type != PJ_TURN_TP_TLS) && (turn_sock->active_sock)) { + pj_activesock_close(turn_sock->active_sock); + turn_sock->active_sock = NULL; + } +#if PJ_HAS_SSL_SOCK + else if ((turn_sock->conn_type == PJ_TURN_TP_TLS) && (turn_sock->ssl_sock)) { + pj_ssl_sock_close(turn_sock->ssl_sock); + turn_sock->ssl_sock = NULL; + } +#endif + /* Get server address from session info */ + pj_turn_session_get_info(sess, &info); + + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + sock_type = pj_SOCK_DGRAM(); + else + sock_type = pj_SOCK_STREAM(); + + cfg_bind_addr = &turn_sock->setting.bound_addr; + max_bind_retry = MAX_BIND_RETRY; + if (turn_sock->setting.port_range && turn_sock->setting.port_range < max_bind_retry) { + max_bind_retry = turn_sock->setting.port_range; + } + pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0); + if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || cfg_bind_addr->addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, cfg_bind_addr); + } + + if (turn_sock->conn_type != PJ_TURN_TP_TLS) { + /* Init socket */ + status = pj_sock_socket(turn_sock->af, sock_type, 0, &sock); + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + + /* Bind socket */ + status = pj_sock_bind_random(sock, &bound_addr, turn_sock->setting.port_range, max_bind_retry); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + pj_turn_sock_destroy(turn_sock); + return; + } + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(sock, turn_sock->setting.qos_type, &turn_sock->setting.qos_params, + (turn_sock->setting.qos_ignore_error ? 2 : 1), turn_sock->pool->obj_name, NULL); + if (status != PJ_SUCCESS && !turn_sock->setting.qos_ignore_error) { + pj_sock_close(sock); + pj_turn_sock_destroy(turn_sock); + return; + } + + /* Apply socket buffer size */ + if (turn_sock->setting.so_rcvbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_RCVBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_rcvbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_rcvbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (turn_sock->setting.so_sndbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_SNDBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_sndbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_sndbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = turn_sock->grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &on_data_read_asock; + asock_cb.on_data_sent = &on_data_sent_asock; + asock_cb.on_connect_complete = &on_connect_complete_asock; + status = pj_activesock_create(turn_sock->pool, sock, sock_type, &asock_cfg, turn_sock->cfg.ioqueue, + &asock_cb, turn_sock, &turn_sock->active_sock); + if (status != PJ_SUCCESS) + pj_sock_close(sock); + } +#if PJ_HAS_SSL_SOCK + else { + // TURN TLS + pj_ssl_sock_param param, *ssock_param; + + ssock_param = &turn_sock->setting.tls_cfg.ssock_param; + pj_ssl_sock_param_default(¶m); + + pj_ssl_sock_param_copy(turn_sock->pool, ¶m, ssock_param); + param.cb.on_connect_complete = &on_connect_complete_ssl_sock; + param.cb.on_data_read = &on_data_read_ssl_sock; + param.cb.on_data_sent = &on_data_sent_ssl_sock; + param.ioqueue = turn_sock->cfg.ioqueue; + param.timer_heap = turn_sock->cfg.timer_heap; + param.grp_lock = turn_sock->grp_lock; + param.server_name = turn_sock->server_name; + param.user_data = turn_sock; + param.sock_type = sock_type; + param.sock_af = turn_sock->af; + if (param.send_buffer_size < PJ_TURN_MAX_PKT_LEN) + param.send_buffer_size = PJ_TURN_MAX_PKT_LEN; + if (param.read_buffer_size < PJ_TURN_MAX_PKT_LEN) + param.read_buffer_size = PJ_TURN_MAX_PKT_LEN; + + param.qos_type = turn_sock->setting.qos_type; + param.qos_ignore_error = turn_sock->setting.qos_ignore_error; + pj_memcpy(¶m.qos_params, &turn_sock->setting.qos_params, sizeof(param.qos_params)); + + if (turn_sock->setting.tls_cfg.cert_file.slen || turn_sock->setting.tls_cfg.ca_list_file.slen || + turn_sock->setting.tls_cfg.ca_list_path.slen || turn_sock->setting.tls_cfg.privkey_file.slen) { + status = pj_ssl_cert_load_from_files2( + turn_sock->pool, &turn_sock->setting.tls_cfg.ca_list_file, &turn_sock->setting.tls_cfg.ca_list_path, + &turn_sock->setting.tls_cfg.cert_file, &turn_sock->setting.tls_cfg.privkey_file, + &turn_sock->setting.tls_cfg.password, &turn_sock->cert); + + } else if (turn_sock->setting.tls_cfg.ca_buf.slen || turn_sock->setting.tls_cfg.cert_buf.slen || + turn_sock->setting.tls_cfg.privkey_buf.slen) { + status = pj_ssl_cert_load_from_buffer( + turn_sock->pool, &turn_sock->setting.tls_cfg.ca_buf, &turn_sock->setting.tls_cfg.cert_buf, + &turn_sock->setting.tls_cfg.privkey_buf, &turn_sock->setting.tls_cfg.password, &turn_sock->cert); + } + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + if (turn_sock->cert) { + pj_turn_sock_tls_cfg_wipe_keys(&turn_sock->setting.tls_cfg); + } + + status = pj_ssl_sock_create(turn_sock->pool, ¶m, &turn_sock->ssl_sock); + + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + + if (turn_sock->cert) { + status = pj_ssl_sock_set_certificate(turn_sock->ssl_sock, turn_sock->pool, turn_sock->cert); + + pj_ssl_cert_wipe_keys(turn_sock->cert); + turn_sock->cert = NULL; + } + } +#endif + + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + + PJ_LOG(5, (turn_sock->pool->obj_name, "Connecting to %s", + pj_sockaddr_print(&info.server, addrtxt, sizeof(addrtxt), 3))); + + /* Initiate non-blocking connect */ + if (turn_sock->conn_type == PJ_TURN_TP_UDP) { + status = PJ_SUCCESS; + } +#if PJ_HAS_TCP + else if (turn_sock->conn_type == PJ_TURN_TP_TCP) { + status = pj_activesock_start_connect(turn_sock->active_sock, turn_sock->pool, &info.server, + pj_sockaddr_get_len(&info.server)); + } +#endif +#if PJ_HAS_SSL_SOCK + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) { + pj_ssl_start_connect_param connect_param; + connect_param.pool = turn_sock->pool; + connect_param.localaddr = &bound_addr; + connect_param.local_port_range = turn_sock->setting.port_range; + connect_param.remaddr = &info.server; + connect_param.addr_len = pj_sockaddr_get_len(&info.server); + + status = pj_ssl_sock_start_connect2(turn_sock->ssl_sock, &connect_param); + } +#endif + if (status == PJ_SUCCESS) { + on_connect_complete(turn_sock, PJ_SUCCESS); + } else if (status != PJ_EPENDING) { + PJ_PERROR(3, (turn_sock->pool->obj_name, status, "Failed to connect to %s", + pj_sockaddr_print(&info.server, addrtxt, sizeof(addrtxt), 3))); + pj_turn_sock_destroy(turn_sock); + return; + } + + /* Done for now. Subsequent work will be done in + * on_connect_complete() callback. + */ + } + + if (new_state >= PJ_TURN_STATE_DESTROYING && turn_sock->sess) { + pj_time_val delay = {0, 0}; + + turn_sock->sess = NULL; + pj_turn_session_set_user_data(sess, NULL); + + pj_timer_heap_cancel_if_active(turn_sock->cfg.timer_heap, &turn_sock->timer, 0); + pj_timer_heap_schedule_w_grp_lock(turn_sock->cfg.timer_heap, &turn_sock->timer, &delay, TIMER_DESTROY, + turn_sock->grp_lock); + } +} + +static void dataconn_cleanup(tcp_data_conn_t *conn) +{ + if (conn->asock) + pj_activesock_close(conn->asock); + + pj_pool_safe_release(&conn->pool); + + pj_bzero(conn, sizeof(*conn)); +} + +static pj_bool_t dataconn_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + tcp_data_conn_t *conn = (tcp_data_conn_t *)pj_activesock_get_user_data(asock); + pj_turn_sock *turn_sock = conn->turn_sock; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (size == 0 && status != PJ_SUCCESS) { + /* Connection gone, release data connection */ + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + *remainder = size; + while (*remainder > 0) { + if (conn->state == DATACONN_STATE_READY) { + /* Application data */ + if (turn_sock->cb.on_rx_data) { + (*turn_sock->cb.on_rx_data)(turn_sock, data, (unsigned)*remainder, &conn->peer_addr, + conn->peer_addr_len); + } + *remainder = 0; + } else if (conn->state == DATACONN_STATE_CONN_BINDING) { + /* Waiting for ConnectionBind response */ + pj_bool_t is_stun; + pj_turn_session_on_rx_pkt_param prm; + + /* Ignore if this is not a STUN message */ + is_stun = ((((pj_uint8_t *)data)[0] & 0xC0) == 0); + if (!is_stun) + goto on_return; + + pj_bzero(&prm, sizeof(prm)); + prm.pkt = data; + prm.pkt_len = *remainder; + prm.src_addr = &conn->peer_addr; + prm.src_addr_len = conn->peer_addr_len; + pj_turn_session_on_rx_pkt2(conn->turn_sock->sess, &prm); + /* Got remainder? */ + if (prm.parsed_len < *remainder && prm.parsed_len > 0) { + pj_memmove(data, (pj_uint8_t *)data + prm.parsed_len, *remainder); + } + *remainder -= prm.parsed_len; + } else + goto on_return; + } + +on_return: + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_TRUE; +} + +static pj_bool_t dataconn_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + tcp_data_conn_t *conn = (tcp_data_conn_t *)pj_activesock_get_user_data(asock); + pj_turn_sock *turn_sock = conn->turn_sock; + + return on_data_sent(turn_sock, send_key, sent); +} + +static pj_bool_t dataconn_on_connect_complete(pj_activesock_t *asock, pj_status_t status) +{ + tcp_data_conn_t *conn = (tcp_data_conn_t *)pj_activesock_get_user_data(asock); + pj_turn_sock *turn_sock = conn->turn_sock; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (status == PJ_SUCCESS) { + status = pj_activesock_start_read(asock, turn_sock->pool, turn_sock->setting.max_pkt_size, 0); + } + if (status == PJ_SUCCESS) { + conn->state = DATACONN_STATE_CONN_BINDING; + status = pj_turn_session_connection_bind(turn_sock->sess, conn->pool, conn->id, &conn->peer_addr, + conn->peer_addr_len); + } + if (status != PJ_SUCCESS) { + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_TRUE; +} + +static void turn_on_connection_attempt(pj_turn_session *sess, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_pool_t *pool; + tcp_data_conn_t *new_conn; + pj_turn_session_info info; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_activesock_cfg asock_cfg; + pj_activesock_cb asock_cb; + pj_sockaddr bound_addr, *cfg_bind_addr; + pj_uint16_t max_bind_retry; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + pj_status_t status; + unsigned i; + + PJ_ASSERT_ON_FAIL(turn_sock->conn_type == PJ_TURN_TP_TCP && turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP, + return ); + + PJ_LOG(5, (turn_sock->pool->obj_name, "Connection attempt from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + + if (turn_sock == NULL) { + /* We've been destroyed */ + return; + } + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (turn_sock->data_conn_cnt == PJ_TURN_MAX_TCP_CONN_CNT) { + /* Data connection has reached limit */ + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + /* Check if app wants to accept this connection */ + status = PJ_SUCCESS; + if (turn_sock->cb.on_connection_attempt) { + status = (*turn_sock->cb.on_connection_attempt)(turn_sock, conn_id, peer_addr, addr_len); + } + /* App rejects it */ + if (status != PJ_SUCCESS) { + pj_perror(4, turn_sock->pool->obj_name, status, "Rejected connection attempt from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + /* Find free data connection slot */ + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + if (turn_sock->data_conn[i].state == DATACONN_STATE_NULL) + break; + } + + /* Verify that a free slot is found */ + pj_assert(i < PJ_TURN_MAX_TCP_CONN_CNT); + ++turn_sock->data_conn_cnt; + + /* Init new data connection */ + new_conn = &turn_sock->data_conn[i]; + pj_bzero(new_conn, sizeof(*new_conn)); + pool = pj_pool_create(turn_sock->cfg.pf, "dataconn", 128, 128, NULL); + new_conn->pool = pool; + new_conn->id = conn_id; + new_conn->turn_sock = turn_sock; + pj_sockaddr_cp(&new_conn->peer_addr, peer_addr); + new_conn->peer_addr_len = addr_len; + pj_ioqueue_op_key_init(&new_conn->send_key, sizeof(new_conn->send_key)); + new_conn->state = DATACONN_STATE_INITSOCK; + + /* Init socket */ + status = pj_sock_socket(turn_sock->af, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Bind socket */ + cfg_bind_addr = &turn_sock->setting.bound_addr; + max_bind_retry = MAX_BIND_RETRY; + if (turn_sock->setting.port_range && turn_sock->setting.port_range < max_bind_retry) { + max_bind_retry = turn_sock->setting.port_range; + } + pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0); + if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || cfg_bind_addr->addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, cfg_bind_addr); + } + status = pj_sock_bind_random(sock, &bound_addr, turn_sock->setting.port_range, max_bind_retry); + if (status != PJ_SUCCESS) + goto on_return; + + /* Apply socket buffer size */ + if (turn_sock->setting.so_rcvbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_RCVBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_rcvbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_rcvbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (turn_sock->setting.so_sndbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_SNDBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_sndbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_sndbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = turn_sock->grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &dataconn_on_data_read; + asock_cb.on_data_sent = &dataconn_on_data_sent; + asock_cb.on_connect_complete = &dataconn_on_connect_complete; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg, turn_sock->cfg.ioqueue, &asock_cb, new_conn, + &new_conn->asock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Connect to TURN server for data connection */ + pj_turn_session_get_info(turn_sock->sess, &info); + status = pj_activesock_start_connect(new_conn->asock, pool, &info.server, pj_sockaddr_get_len(&info.server)); + if (status == PJ_SUCCESS) { + dataconn_on_connect_complete(new_conn->asock, PJ_SUCCESS); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + +on_return: + if (status == PJ_EPENDING) { + PJ_LOG(5, (pool->obj_name, "Accepting connection from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + } else { + /* not PJ_SUCCESS */ + pj_perror(4, pool->obj_name, status, "Failed in accepting connection from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + + if (!new_conn->asock && sock != PJ_INVALID_SOCKET) + pj_sock_close(sock); + + dataconn_cleanup(new_conn); + --turn_sock->data_conn_cnt; + + /* Notify app for failure */ + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } + } + pj_grp_lock_release(turn_sock->grp_lock); +} + +static void turn_on_connection_bind_status(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + tcp_data_conn_t *conn = NULL; + unsigned i; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + tcp_data_conn_t *c = &turn_sock->data_conn[i]; + if (c->id == conn_id && pj_sockaddr_cmp(peer_addr, &c->peer_addr) == 0) { + conn = c; + break; + } + } + if (!conn) { + PJ_LOG(5, (turn_sock->pool->obj_name, "Warning: stray connection bind event")); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + if (status == PJ_SUCCESS) { + conn->state = DATACONN_STATE_READY; + } else { + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + } + + pj_grp_lock_release(turn_sock->grp_lock); + + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } +} + +static void turn_on_connect_complete(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_pool_t *pool; + tcp_data_conn_t *new_conn; + pj_turn_session_info info; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_activesock_cfg asock_cfg; + pj_activesock_cb asock_cb; + pj_sockaddr bound_addr, *cfg_bind_addr; + pj_uint16_t max_bind_retry; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + unsigned i; + + if (turn_sock == NULL) { + /* We've been destroyed */ + return; + } + + PJ_ASSERT_ON_FAIL(turn_sock->conn_type == PJ_TURN_TP_TCP && turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP, + return ); + PJ_LOG(5, (turn_sock->pool->obj_name, "Trying to connect to peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (turn_sock->data_conn_cnt == PJ_TURN_MAX_TCP_CONN_CNT) { + /* Data connection has reached limit */ + + status = PJ_ETOOMANY; + pj_perror(4, turn_sock->pool->obj_name, status, "Failed in connect to peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + + /* Notify app for failure */ + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } + + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + /* Find free data connection slot */ + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + if (turn_sock->data_conn[i].state == DATACONN_STATE_NULL) + break; + } + + /* Verify that a free slot is found */ + pj_assert(i < PJ_TURN_MAX_TCP_CONN_CNT); + ++turn_sock->data_conn_cnt; + + /* Init new data connection */ + new_conn = &turn_sock->data_conn[i]; + pj_bzero(new_conn, sizeof(*new_conn)); + pool = pj_pool_create(turn_sock->cfg.pf, "dataconn", 128, 128, NULL); + new_conn->pool = pool; + new_conn->id = conn_id; + new_conn->turn_sock = turn_sock; + pj_sockaddr_cp(&new_conn->peer_addr, peer_addr); + new_conn->peer_addr_len = addr_len; + pj_ioqueue_op_key_init(&new_conn->send_key, sizeof(new_conn->send_key)); + new_conn->state = DATACONN_STATE_INITSOCK; + + /* Init socket */ + status = pj_sock_socket(turn_sock->af, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Bind socket */ + cfg_bind_addr = &turn_sock->setting.bound_addr; + max_bind_retry = MAX_BIND_RETRY; + if (turn_sock->setting.port_range && turn_sock->setting.port_range < max_bind_retry) { + max_bind_retry = turn_sock->setting.port_range; + } + pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0); + if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || cfg_bind_addr->addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, cfg_bind_addr); + } + status = pj_sock_bind_random(sock, &bound_addr, turn_sock->setting.port_range, max_bind_retry); + if (status != PJ_SUCCESS) + goto on_return; + + /* Apply socket buffer size */ + if (turn_sock->setting.so_rcvbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_RCVBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_rcvbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_rcvbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (turn_sock->setting.so_sndbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_SNDBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_sndbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_sndbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = turn_sock->grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &dataconn_on_data_read; + asock_cb.on_data_sent = &dataconn_on_data_sent; + asock_cb.on_connect_complete = &dataconn_on_connect_complete; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg, turn_sock->cfg.ioqueue, &asock_cb, new_conn, + &new_conn->asock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Connect to TURN server for data connection */ + pj_turn_session_get_info(turn_sock->sess, &info); + status = pj_activesock_start_connect(new_conn->asock, pool, &info.server, pj_sockaddr_get_len(&info.server)); + if (status == PJ_SUCCESS) { + dataconn_on_connect_complete(new_conn->asock, PJ_SUCCESS); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + +on_return: + if (status == PJ_EPENDING) { + PJ_LOG(5, (pool->obj_name, "Connecting to peer %s", pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + } else { + /* not PJ_SUCCESS */ + pj_perror(4, pool->obj_name, status, "Failed in connect to peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + + if (!new_conn->asock && sock != PJ_INVALID_SOCKET) + pj_sock_close(sock); + + dataconn_cleanup(new_conn); + --turn_sock->data_conn_cnt; + + /* Notify app for failure */ + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } + } + pj_grp_lock_release(turn_sock->grp_lock); +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c new file mode 100755 index 000000000..3b20a0ea1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c @@ -0,0 +1,849 @@ +/* + * Copyright (C) 2022 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJNATH_HAS_UPNP) && (PJNATH_HAS_UPNP != 0) + +#include +#include + +#define THIS_FILE "upnp.c" + +#define TRACE_(...) // PJ_LOG(6, (THIS_FILE, ##__VA_ARGS__)) + +/* Set to 1 to enable UPnP native logging */ +#define ENABLE_LOG 0 + +/* Maximum number of devices. */ +#define MAX_DEVS 16 + +#if ENABLE_LOG +#include +#endif + +/* UPnP device descriptions. */ +static const char *UPNP_ROOT_DEVICE = "upnp:rootdevice"; +static const char *UPNP_IGD_DEVICE = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"; +static const char *UPNP_WANIP_SERVICE = "urn:schemas-upnp-org:service:WANIPConnection:1"; +static const char *UPNP_WANPPP_SERVICE = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + +/* Structure for IGD device. */ +struct igd { + pj_str_t dev_id; + pj_str_t url; + pj_str_t service_type; + pj_str_t control_url; + pj_str_t public_ip; + pj_sockaddr public_ip_addr; + + pj_bool_t valid; + pj_bool_t alive; +}; + +/* UPnP manager. */ +static struct upnp { + unsigned initialized; + pj_pool_t *pool; + pj_thread_desc thread_desc; + pj_thread_t *thread; + pj_mutex_t *mutex; + int search_cnt; + pj_status_t status; + + unsigned igd_cnt; + struct igd igd_devs[20]; + int primary_igd_idx; + + UpnpClient_Handle client_hnd; + void (*upnp_cb)(pj_status_t status); +} upnp_mgr; + +/* Get the value of the node. */ +static const char *get_node_value(IXML_Node *node) +{ + const char *ret = NULL; + if (node) { + IXML_Node *child = ixmlNode_getFirstChild(node); + if (child) + ret = ixmlNode_getNodeValue(child); + } + return ret; +} + +/* Get the value of the first element in the doc with the specified name. */ +static const char *doc_get_elmt_value(IXML_Document *doc, const char *name) +{ + const char *ret = NULL; + IXML_NodeList *node_list = ixmlDocument_getElementsByTagName(doc, name); + if (node_list) { + ret = get_node_value(ixmlNodeList_item(node_list, 0)); + ixmlNodeList_free(node_list); + } + return ret; +} + +/* Get the value of the first element with the specified name. */ +static const char *elmt_get_elmt_value(IXML_Element *elmt, const char *name) +{ + const char *ret = NULL; + IXML_NodeList *node_list = ixmlElement_getElementsByTagName(elmt, name); + if (node_list) { + ret = get_node_value(ixmlNodeList_item(node_list, 0)); + ixmlNodeList_free(node_list); + } + return ret; +} + +/* Check if response contains errorCode. */ +static const char *check_error_response(IXML_Document *doc) +{ + const char *error_code = doc_get_elmt_value(doc, "errorCode"); + + if (error_code) { + const char *error_desc = doc_get_elmt_value(doc, "errorDescription"); + + PJ_LOG(3, (THIS_FILE, "Response error code: %s (%s)", error_code, error_desc)); + } + + return error_code; +} + +/* Query the external IP of the IGD. */ +static const char *action_get_external_ip(struct igd *igd) +{ + static const char *action_name = "GetExternalIPAddress"; + IXML_Document *action = NULL; + IXML_Document *response = NULL; + const char *public_ip = NULL; + int upnp_err; + + /* Create action XML. */ + action = UpnpMakeAction(action_name, igd->service_type.ptr, 0, NULL); + if (!action) { + PJ_LOG(3, (THIS_FILE, "Failed to make GetExternalIPAddress action")); + return NULL; + } + + /* Send the action XML. */ + upnp_err = + UpnpSendAction(upnp_mgr.client_hnd, igd->control_url.ptr, igd->service_type.ptr, NULL, action, &response); + if (upnp_err != UPNP_E_SUCCESS || !response) { + PJ_LOG(3, (THIS_FILE, "Failed to send GetExternalIPAddress action: %s", UpnpGetErrorMessage(upnp_err))); + goto on_error; + } + + if (check_error_response(response)) + goto on_error; + + /* Get the external IP address from the response. */ + public_ip = doc_get_elmt_value(response, "NewExternalIPAddress"); + if (!public_ip) { + PJ_LOG(3, (THIS_FILE, "IGD %s has no external IP", igd->dev_id.ptr)); + goto on_error; + } + pj_strdup2_with_null(upnp_mgr.pool, &igd->public_ip, public_ip); + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &igd->public_ip, &igd->public_ip_addr); + public_ip = igd->public_ip.ptr; + +on_error: + ixmlDocument_free(action); + if (response) + ixmlDocument_free(response); + + return public_ip; +} + +/* Download the XML document of the IGD. */ +static void download_igd_xml(unsigned dev_idx) +{ + struct igd *igd_dev = &upnp_mgr.igd_devs[dev_idx]; + const char *url = igd_dev->url.ptr; + IXML_Document *doc = NULL; + int upnp_err; + const char *dev_type; + const char *friendly_name; + const char *base_url; + const char *control_url; + const char *public_ip; + char *abs_control_url = NULL; + IXML_NodeList *service_list = NULL; + unsigned i, n; + + upnp_err = UpnpDownloadXmlDoc(url, &doc); + if (upnp_err != UPNP_E_SUCCESS || !doc) { + PJ_LOG(3, (THIS_FILE, "Error downloading device XML doc from %s: %s", url, UpnpGetErrorMessage(upnp_err))); + goto on_error; + } + + /* Check device type. */ + dev_type = doc_get_elmt_value(doc, "deviceType"); + if (!dev_type) + return; + if (pj_ansi_strcmp(dev_type, UPNP_IGD_DEVICE) != 0) { + /* Device type is not IGD. */ + goto on_error; + } + + /* Get friendly name. */ + friendly_name = doc_get_elmt_value(doc, "friendlyName"); + if (!friendly_name) + friendly_name = ""; + + /* Get base URL. */ + base_url = doc_get_elmt_value(doc, "URLBase"); + if (!base_url) + base_url = url; + + /* Get list of services defined by serviceType. */ + service_list = ixmlDocument_getElementsByTagName(doc, "serviceType"); + n = ixmlNodeList_length(service_list); + + for (i = 0; i < n; i++) { + IXML_Node *service_type_node = ixmlNodeList_item(service_list, i); + IXML_Node *service_node = ixmlNode_getParentNode(service_type_node); + IXML_Element *service_element = (IXML_Element *)service_node; + const char *service_type; + pj_bool_t call_cb = PJ_FALSE; + + /* Check if parent node is "service". */ + if (!service_node || (pj_ansi_strcmp(ixmlNode_getNodeName(service_node), "service"))) { + continue; + } + + /* We only want serviceType of WANIPConnection or WANPPPConnection. */ + service_type = get_node_value(service_type_node); + if (pj_ansi_strcmp(service_type, UPNP_WANIP_SERVICE) && pj_ansi_strcmp(service_type, UPNP_WANPPP_SERVICE)) { + continue; + } + + /* Get the controlURL. */ + control_url = elmt_get_elmt_value(service_element, "controlURL"); + if (!control_url) + continue; + + /* Resolve the absolute address of controlURL. */ + upnp_err = UpnpResolveURL2(base_url, control_url, &abs_control_url); + if (upnp_err == UPNP_E_SUCCESS) { + pj_strdup2_with_null(upnp_mgr.pool, &igd_dev->control_url, abs_control_url); + free(abs_control_url); + } else { + PJ_LOG(4, (THIS_FILE, "Error resolving absolute controlURL: %s", UpnpGetErrorMessage(upnp_err))); + pj_strdup2_with_null(upnp_mgr.pool, &igd_dev->control_url, control_url); + } + + pj_strdup2_with_null(upnp_mgr.pool, &igd_dev->service_type, service_type); + + /* Get the public IP of the IGD. */ + public_ip = action_get_external_ip(igd_dev); + if (!public_ip) + break; + + /* We find a valid IGD. */ + igd_dev->valid = PJ_TRUE; + igd_dev->alive = PJ_TRUE; + + PJ_LOG(4, (THIS_FILE, + "Valid IGD:\n" + "\tUDN : %s\n" + "\tName : %s\n" + "\tService Type : %s\n" + "\tControl URL : %s\n" + "\tPublic IP : %s", + igd_dev->dev_id.ptr, friendly_name, igd_dev->service_type.ptr, igd_dev->control_url.ptr, public_ip)); + + /* Use this as primary IGD if we haven't had one. */ + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.primary_igd_idx < 0) { + upnp_mgr.primary_igd_idx = dev_idx; + call_cb = PJ_TRUE; + upnp_mgr.status = PJ_SUCCESS; + } + pj_mutex_unlock(upnp_mgr.mutex); + + if (call_cb && upnp_mgr.upnp_cb) { + (*upnp_mgr.upnp_cb)(upnp_mgr.status); + } + + break; + } + +on_error: + if (service_list) + ixmlNodeList_free(service_list); + if (doc) + ixmlDocument_free(doc); +} + +/* Add a newly discovered IGD. */ +static void add_device(const char *dev_id, const char *url) +{ + unsigned i; + + if (upnp_mgr.igd_cnt >= MAX_DEVS) { + PJ_LOG(3, (THIS_FILE, "Warning: Too many UPnP devices discovered")); + return; + } + + pj_mutex_lock(upnp_mgr.mutex); + for (i = 0; i < upnp_mgr.igd_cnt; i++) { + if (!pj_strcmp2(&upnp_mgr.igd_devs[i].dev_id, dev_id) && !pj_strcmp2(&upnp_mgr.igd_devs[i].url, url)) { + /* Device exists. */ + pj_mutex_unlock(upnp_mgr.mutex); + return; + } + } + + pj_strdup2_with_null(upnp_mgr.pool, &upnp_mgr.igd_devs[upnp_mgr.igd_cnt].dev_id, dev_id); + pj_strdup2_with_null(upnp_mgr.pool, &upnp_mgr.igd_devs[upnp_mgr.igd_cnt++].url, url); + pj_mutex_unlock(upnp_mgr.mutex); + + PJ_LOG(4, (THIS_FILE, "Discovered a new IGD %s, url: %s", dev_id, url)); + + /* Download the IGD's XML doc. */ + download_igd_xml(upnp_mgr.igd_cnt - 1); +} + +/* Update online status of an IGD. */ +static void set_device_online(const char *dev_id) +{ + unsigned i; + + for (i = 0; i < upnp_mgr.igd_cnt; i++) { + struct igd *igd = &upnp_mgr.igd_devs[i]; + + /* We are only interested in valid IGDs that we can use. */ + if (!pj_strcmp2(&igd->dev_id, dev_id) && igd->valid) { + igd->alive = PJ_TRUE; + + if (upnp_mgr.primary_igd_idx < 0) { + /* If we don't have a primary IGD, use this. */ + pj_mutex_lock(upnp_mgr.mutex); + upnp_mgr.primary_igd_idx = i; + pj_mutex_unlock(upnp_mgr.mutex); + + PJ_LOG(4, (THIS_FILE, "Using primary IGD %s", upnp_mgr.igd_devs[i].dev_id.ptr)); + } + } + } +} + +/* Update IGD status to offline. */ +static void set_device_offline(const char *dev_id) +{ + int i; + + for (i = 0; i < (int)upnp_mgr.igd_cnt; i++) { + struct igd *igd = &upnp_mgr.igd_devs[i]; + + /* We are only interested in valid IGDs that we can use. */ + if (!pj_strcmp2(&igd->dev_id, dev_id) && igd->valid) { + igd->alive = PJ_FALSE; + + pj_mutex_lock(upnp_mgr.mutex); + if (i == upnp_mgr.primary_igd_idx) { + unsigned j; + + /* The primary IGD is offline, try to find another one. */ + upnp_mgr.primary_igd_idx = -1; + for (j = 0; j < upnp_mgr.igd_cnt; j++) { + igd = &upnp_mgr.igd_devs[j]; + if (igd->valid && igd->alive) { + upnp_mgr.primary_igd_idx = j; + break; + } + } + + PJ_LOG(4, (THIS_FILE, "Device %s offline, now using IGD %s", upnp_mgr.igd_devs[i].dev_id.ptr, + (upnp_mgr.primary_igd_idx < 0 ? "(none)" : igd->dev_id.ptr))); + } + pj_mutex_unlock(upnp_mgr.mutex); + } + } +} + +/* UPnP client callback. */ +static int client_cb(Upnp_EventType event_type, const void *event, void *user_data) +{ + /* Ignore if already uninitialized or incorrect user data. */ + if (!upnp_mgr.initialized || user_data != &upnp_mgr) + return UPNP_E_SUCCESS; + + if (!pj_thread_is_registered()) { + pj_bzero(upnp_mgr.thread_desc, sizeof(pj_thread_desc)); + pj_thread_register("upnp_cb", upnp_mgr.thread_desc, &upnp_mgr.thread); + } + + switch (event_type) { + case UPNP_DISCOVERY_SEARCH_RESULT: { + const UpnpDiscovery *d_event = (const UpnpDiscovery *)event; + int upnp_status = UpnpDiscovery_get_ErrCode(d_event); + const char *dev_id, *location; + + if (upnp_status != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "UPnP discovery error: %s", UpnpGetErrorMessage(upnp_status))); + break; + } + + dev_id = UpnpDiscovery_get_DeviceID_cstr(d_event); + location = UpnpDiscovery_get_Location_cstr(d_event); + + add_device(dev_id, location); + break; + } + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: { + const UpnpDiscovery *d_event = (const UpnpDiscovery *)event; + set_device_online(UpnpDiscovery_get_DeviceID_cstr(d_event)); + break; + } + + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: { + const UpnpDiscovery *d_event = (const UpnpDiscovery *)event; + set_device_offline(UpnpDiscovery_get_DeviceID_cstr(d_event)); + break; + } + + case UPNP_DISCOVERY_SEARCH_TIMEOUT: { + pj_bool_t call_cb = PJ_FALSE; + + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.search_cnt > 0) { + --upnp_mgr.search_cnt; + if (upnp_mgr.search_cnt == 0 && upnp_mgr.primary_igd_idx < 0) { + PJ_LOG(4, (THIS_FILE, "Search timed out, no valid IGD found")); + call_cb = PJ_TRUE; + upnp_mgr.status = PJ_ENOTFOUND; + } + } + pj_mutex_unlock(upnp_mgr.mutex); + + if (call_cb && upnp_mgr.upnp_cb) { + (*upnp_mgr.upnp_cb)(upnp_mgr.status); + } + + break; + } + case UPNP_CONTROL_ACTION_COMPLETE: { + int err_code; + IXML_Document *response = NULL; + const UpnpActionComplete *a_event = (const UpnpActionComplete *)event; + if (!a_event) + break; + + /* The only action complete event we're supposed to receive is + * from port mapping deletion action. + */ + err_code = UpnpActionComplete_get_ErrCode(a_event); + if (err_code != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, + "Port mapping deletion action complete " + "error: %d (%s)", + err_code, UpnpGetErrorMessage(err_code))); + break; + } + + response = UpnpActionComplete_get_ActionResult(a_event); + if (!response) { + PJ_LOG(4, (THIS_FILE, "Failed to get response to delete port " + "mapping")); + } else { + if (!check_error_response(response)) { + PJ_LOG(4, (THIS_FILE, "Successfully deleted port mapping")); + } + ixmlDocument_free(response); + } + + break; + } + default: + TRACE_("Unhandled UPnP client callback %d", event_type); + break; + } + + return UPNP_E_SUCCESS; +} + +/* Initiate search for Internet Gateway Devices. */ +static void search_igd(int search_time) +{ + int err; + + upnp_mgr.search_cnt = 4; + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_ROOT_DEVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_ROOT_DEVICE failed: %s", UpnpGetErrorMessage(err))); + } + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_IGD_DEVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_IGD_DEVICE failed: %s", UpnpGetErrorMessage(err))); + } + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_WANIP_SERVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_WANIP_SERVICE failed: %s", UpnpGetErrorMessage(err))); + } + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_WANPPP_SERVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_WANPPP_SERVICE failed: %s", UpnpGetErrorMessage(err))); + } +} + +/* Initialize UPnP. */ +PJ_DEF(pj_status_t) pj_upnp_init(const pj_upnp_init_param *param) +{ + int upnp_err; + const char *ip_address; + unsigned short port; + const char *ip_address6 = NULL; + unsigned short port6 = 0; + + if (upnp_mgr.initialized) + return PJ_SUCCESS; + +#if ENABLE_LOG + UpnpSetLogLevel(UPNP_ALL); + UpnpSetLogFileNames("upnp.log", NULL); + upnp_err = UpnpInitLog(); + if (upnp_err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Failed to initialize UPnP log: %s", UpnpGetErrorMessage(upnp_err))); + } +#endif + + pj_bzero(&upnp_mgr, sizeof(upnp_mgr)); + upnp_err = UpnpInit2(param->if_name, (unsigned short)param->port); + if (upnp_err != UPNP_E_SUCCESS) { + PJ_LOG(1, (THIS_FILE, + "Failed to initialize libupnp with " + "interface %s: %s", + (param->if_name ? param->if_name : "NULL"), UpnpGetErrorMessage(upnp_err))); + return PJ_EUNKNOWN; + } + + /* Register client. */ + upnp_err = UpnpRegisterClient(client_cb, &upnp_mgr, &upnp_mgr.client_hnd); + if (upnp_err != UPNP_E_SUCCESS) { + PJ_LOG(1, (THIS_FILE, "Failed to register client: %s", UpnpGetErrorMessage(upnp_err))); + UpnpFinish(); + return PJ_EINVALIDOP; + } + + /* Try to disable web server. */ + if (UpnpIsWebserverEnabled()) { + UpnpEnableWebserver(0); + if (UpnpIsWebserverEnabled()) { + PJ_LOG(4, (THIS_FILE, "Failed to disable web server")); + } + } + + /* Makes the XML parser more tolerant to malformed text. */ + ixmlRelaxParser(1); + + upnp_mgr.initialized = 1; + upnp_mgr.primary_igd_idx = -1; + upnp_mgr.upnp_cb = param->upnp_cb; + upnp_mgr.pool = pj_pool_create(param->factory, "upnp", 512, 512, NULL); + if (!upnp_mgr.pool) { + pj_upnp_deinit(); + return PJ_ENOMEM; + } + pj_mutex_create_recursive(upnp_mgr.pool, "upnp", &upnp_mgr.mutex); + + ip_address = UpnpGetServerIpAddress(); + port = UpnpGetServerPort(); +#if PJ_HAS_IPV6 + ip_address6 = UpnpGetServerIp6Address(); + port6 = UpnpGetServerPort6(); +#endif + if (param->if_name) { + PJ_LOG(4, (THIS_FILE, "UPnP initialized with interface %s", param->if_name)); + } + if (ip_address6 && port6) { + PJ_LOG(4, (THIS_FILE, + "UPnP initialized on %s:%u (IPv4) and " + "%s:%u (IPv6)", + ip_address, port, ip_address6, port6)); + } else { + PJ_LOG(4, (THIS_FILE, "UPnP initialized on %s:%u", ip_address, port)); + } + + /* Search for Internet Gateway Devices. */ + upnp_mgr.status = PJ_EPENDING; + search_igd(param->search_time > 0 ? param->search_time : PJ_UPNP_DEFAULT_SEARCH_TIME); + + return PJ_SUCCESS; +} + +/* Deinitialize UPnP. */ +PJ_DEF(pj_status_t) pj_upnp_deinit(void) +{ + PJ_LOG(4, (THIS_FILE, "UPnP deinitializing...")); + + /* Note that this function will wait until all its worker threads + * complete. + */ + UpnpFinish(); + + if (upnp_mgr.mutex) + pj_mutex_destroy(upnp_mgr.mutex); + + if (upnp_mgr.pool) + pj_pool_release(upnp_mgr.pool); + + pj_bzero(&upnp_mgr, sizeof(upnp_mgr)); + upnp_mgr.primary_igd_idx = -1; + + PJ_LOG(4, (THIS_FILE, "UPnP deinitialized")); + + return PJ_SUCCESS; +} + +/* Send request to add port mapping. */ +PJ_DECL(pj_status_t) +pj_upnp_add_port_mapping(unsigned sock_cnt, const pj_sock_t sock[], unsigned ext_port[], pj_sockaddr mapped_addr[]) +{ + unsigned max_wait = 20; + unsigned i; + struct igd *igd = NULL; + pj_status_t status = PJ_SUCCESS; + + if (!upnp_mgr.initialized) { + PJ_LOG(3, (THIS_FILE, "UPnP not initialized yet")); + return PJ_EINVALIDOP; + } + + /* If IGD search hasn't completed, wait momentarily. */ + while (upnp_mgr.status == PJ_EPENDING && max_wait > 0) { + pj_thread_sleep(100); + max_wait--; + } + + /* Need to lock in case the device becomes offline at the same time. */ + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.primary_igd_idx < 0) { + PJ_LOG(3, (THIS_FILE, "No valid IGD")); + pj_mutex_unlock(upnp_mgr.mutex); + return PJ_ENOTFOUND; + } + + igd = &upnp_mgr.igd_devs[upnp_mgr.primary_igd_idx]; + pj_mutex_unlock(upnp_mgr.mutex); + + for (i = 0; i < sock_cnt; i++) { + static const char *ACTION_ADD_PORT_MAPPING = "AddPortMapping"; + static const char *PORT_MAPPING_DESCRIPTION = "pjsip-upnp"; + int upnp_err; + IXML_Document *action = NULL; + IXML_Document *response = NULL; + char int_port_buf[10], ext_port_buf[10]; + char addr_buf[PJ_INET6_ADDRSTRLEN]; + unsigned int_port; + pj_sockaddr bound_addr; + int namelen = sizeof(pj_sockaddr); + const char *pext_port = (ext_port ? ext_port_buf : int_port_buf); + + /* Get socket's bound address. */ + status = pj_sock_getsockname(sock[i], &bound_addr, &namelen); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "getsockname() error")); + goto on_error; + } + + if (!pj_sockaddr_has_addr(&bound_addr)) { + pj_sockaddr addr; + + /* Get local IP address. */ + status = pj_gethostip(bound_addr.addr.sa_family, &addr); + if (status != PJ_SUCCESS) + goto on_error; + + pj_sockaddr_copy_addr(&bound_addr, &addr); + } + + pj_sockaddr_print(&bound_addr, addr_buf, sizeof(addr_buf), 0); + int_port = pj_sockaddr_get_port(&bound_addr); + pj_utoa(int_port, int_port_buf); + if (ext_port) + pj_utoa(ext_port[i], ext_port_buf); + + /* Create action XML. */ + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewRemoteHost", ""); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewExternalPort", pext_port); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewProtocol", "UDP"); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewInternalPort", int_port_buf); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewInternalClient", addr_buf); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewEnabled", "1"); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewPortMappingDescription", + PORT_MAPPING_DESCRIPTION); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewLeaseDuration", "0"); + + /* Send the action XML. */ + upnp_err = + UpnpSendAction(upnp_mgr.client_hnd, igd->control_url.ptr, igd->service_type.ptr, NULL, action, &response); + if (upnp_err != UPNP_E_SUCCESS || !response) { + PJ_LOG(3, (THIS_FILE, + "Failed to %s IGD %s to add port mapping " + "for %s:%s -> %s:%s: %d (%s)", + response ? "send action to" : "get response from", igd->dev_id.ptr, igd->public_ip.ptr, + pext_port, addr_buf, int_port_buf, upnp_err, UpnpGetErrorMessage(upnp_err))); + status = PJ_ETIMEDOUT; + } + + TRACE_("Add port mapping XML action:\n%s", ixmlPrintDocument(action)); + TRACE_("Add port mapping XML response:\n%s", (response ? ixmlPrintDocument(response) : "empty")); + + if (response && check_error_response(response)) { + /* The error detail will be printed by check_error_response(). */ + status = PJ_EINVALIDOP; + } + + ixmlDocument_free(action); + if (response) + ixmlDocument_free(response); + + pj_sockaddr_cp(&mapped_addr[i], &bound_addr); + pj_sockaddr_set_str_addr(bound_addr.addr.sa_family, &mapped_addr[i], &igd->public_ip); + pj_sockaddr_set_port(&mapped_addr[i], (pj_uint16_t)(ext_port ? ext_port[i] : int_port)); + + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(4, (THIS_FILE, + "Successfully add port mapping to IGD %s: " + "%s:%s -> %s:%s", + igd->dev_id.ptr, igd->public_ip.ptr, pext_port, addr_buf, int_port_buf)); + } + + return PJ_SUCCESS; + +on_error: + /* Port mapping was unsuccessful, so we need to delete all + * the previous port mappings. + */ + while (i > 0) { + pj_upnp_del_port_mapping(&mapped_addr[--i]); + } + + return status; +} + +/* Send request to delete port mapping. */ +PJ_DEF(pj_status_t) pj_upnp_del_port_mapping(const pj_sockaddr *mapped_addr) +{ + static const char *ACTION_DELETE_PORT_MAPPING = "DeletePortMapping"; + int upnp_err; + struct igd *igd = NULL; + IXML_Document *action = NULL; + pj_status_t status = PJ_SUCCESS; + pj_sockaddr host_addr; + unsigned ext_port; + char ext_port_buf[10]; + + if (!upnp_mgr.initialized) + return PJ_EINVALIDOP; + + /* Need to lock in case the device becomes offline at the same time. */ + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.primary_igd_idx < 0) { + PJ_LOG(3, (THIS_FILE, "No valid IGD")); + pj_mutex_unlock(upnp_mgr.mutex); + return PJ_ENOTFOUND; + } + + igd = &upnp_mgr.igd_devs[upnp_mgr.primary_igd_idx]; + pj_mutex_unlock(upnp_mgr.mutex); + + /* Compare IGD's public IP to the mapped public address. */ + pj_sockaddr_cp(&host_addr, mapped_addr); + pj_sockaddr_set_port(&host_addr, 0); + if (pj_sockaddr_cmp(&igd->public_ip_addr, &host_addr)) { + unsigned i; + + /* The primary IGD's public IP is different. Find the IGD + * that matches the mapped address. + */ + igd = NULL; + for (i = 0; i < upnp_mgr.igd_cnt; i++, igd = NULL) { + igd = &upnp_mgr.igd_devs[i]; + if (igd->valid && igd->alive && !pj_sockaddr_cmp(&igd->public_ip_addr, &host_addr)) { + break; + } + } + } + + if (!igd) { + /* Either the IGD we previously requested to add port mapping has become + * offline, or the address is actually not a valid. + */ + PJ_LOG(3, (THIS_FILE, "The IGD is offline or invalid mapped address")); + return PJ_EGONE; + } + + ext_port = pj_sockaddr_get_port(mapped_addr); + if (ext_port == 0) { + /* Deleting port zero should be harmless, but it's a waste of time. */ + PJ_LOG(3, (THIS_FILE, "Invalid port number to be deleted")); + return PJ_EINVALIDOP; + } + pj_utoa(ext_port, ext_port_buf); + + /* Create action XML. */ + UpnpAddToAction(&action, ACTION_DELETE_PORT_MAPPING, igd->service_type.ptr, "NewRemoteHost", ""); + UpnpAddToAction(&action, ACTION_DELETE_PORT_MAPPING, igd->service_type.ptr, "NewExternalPort", ext_port_buf); + UpnpAddToAction(&action, ACTION_DELETE_PORT_MAPPING, igd->service_type.ptr, "NewProtocol", "UDP"); + + /* For mapping deletion, send the action XML async, to avoid long + * wait in network disconnection scenario. + */ + upnp_err = UpnpSendActionAsync(upnp_mgr.client_hnd, igd->control_url.ptr, igd->service_type.ptr, NULL, action, + client_cb, &upnp_mgr); + if (upnp_err == UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, + "Successfully sending async action to " + "delete port mapping to IGD %s for " + "%s:%s", + igd->dev_id.ptr, igd->public_ip.ptr, ext_port_buf)); + } else { + PJ_LOG(3, (THIS_FILE, + "Failed to send action to IGD %s to delete " + "port mapping for %s:%s: %d (%s)", + igd->dev_id.ptr, igd->public_ip.ptr, ext_port_buf, upnp_err, UpnpGetErrorMessage(upnp_err))); + status = PJ_EINVALIDOP; + } + + ixmlDocument_free(action); + + return status; +} + +#if defined(_MSC_VER) +#pragma comment(lib, "libupnp") +#pragma comment(lib, "libixml") +#pragma comment(lib, "libpthread") +#endif + +#endif /* PJNATH_HAS_UPNP */ diff --git a/src/tuya_p2p/svc_ipc_core/CMakeLists.txt b/src/tuya_p2p/svc_ipc_core/CMakeLists.txt new file mode 100755 index 000000000..7d2a12afc --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/CMakeLists.txt @@ -0,0 +1,53 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h b/src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h new file mode 100755 index 000000000..a6c512c63 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h @@ -0,0 +1,137 @@ +/** + * @file tuya_ipc_skill.h + * @brief tuya ipc skill api + * @version 1.0 + * @date 2021-11-19 + * + * copyright Copyright (c) tuya.inc 2021 + */ +#ifndef __TUYA_IPC_SKILL_H__ +#define __TUYA_IPC_SKILL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_common_types.h" + +#define SKILL_INFO_LEN 512 + +/** + * @enum TUYA_IPC_SKILL_E + * @brief tuya ipc skill enum + */ +typedef enum { + TUYA_IPC_SKILL_CLOUDSTG, + TUYA_IPC_SKILL_WEBRTC, + TUYA_IPC_SKILL_LOWPOWER, + TUYA_IPC_SKILL_AIDETECT, + TUYA_IPC_SKILL_LOCALSTG, + TUYA_IPC_SKILL_CLOUDGW, + TUYA_IPC_SKILL_CLOUDGW_INFO, + TUYA_IPC_SKILL_DOORBELLSTG, + TUYA_IPC_SKILL_P2P, + TUYA_IPC_SKILL_TMM, + TUYA_IPC_SKILL_PX, + TUYA_IPC_SKILL_PERSON_FLOW, + TUYA_IPC_SKILL_UPNP, + TUYA_IPC_SKILL_MAX_P2P_SESSIONS, + TUYA_IPC_SKILL_CLOUDRULE, + TUYA_IPC_SKILL_CLOUDRULE_GW, + TUYA_IPC_SKILL_LOCAL_AI, + TUYA_IPC_SKILL_MOBILE_NETWORK, + TUYA_IPC_SKILL_MAX, +} TUYA_IPC_SKILL_E; + +/** + * @enum TUYA_IPC_LOCAL_AI_SKILL_E + * @brief tuya ipc local ai skill enum + */ +typedef enum { + TUYA_IPC_LOCAL_AI_SKILL_HUMAN = 1 << 0, // Human detection + TUYA_IPC_LOCAL_AI_SKILL_CAT = 1 << 1, // Pet detection + TUYA_IPC_LOCAL_AI_SKILL_CAR = 1 << 2, // Vehicle detection + TUYA_IPC_LOCAL_AI_SKILL_FACE = 1 << 3, // Face detection + TUYA_IPC_LOCAL_AI_SKILL_PACKAGE = 1 << 4, // Package detection +} TUYA_IPC_LOCAL_AI_SKILL_E; + +/** + * @union TUYA_IPC_SKILL_PARAM_U + * @brief tuya ipc skill parameter + */ +typedef union { + INT_T value; + CHAR_T info[SKILL_INFO_LEN]; +} TUYA_IPC_SKILL_PARAM_U; + +/** + * @brief enable ipc skill + * @param[in] skill skill defined in TUYA_IPC_SKILL_E + * @param[in] param skill is set by param->value + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_skill_enable(IN TUYA_IPC_SKILL_E skill, IN TUYA_IPC_SKILL_PARAM_U *param); + +/** + * @brief fill ipc skills in skills buf + * @param[inout] skills buffer used to fill ipc skills + * @return VOID + */ +VOID tuya_ipc_http_fill_skills_cb(INOUT CHAR_T *skills); + +/** + * @brief upload ipc media info + * @return VOID + */ +OPERATE_RET tuya_ipc_upload_media_info(VOID); + +/** + * @brief check whether it is low power + * @return BOOL_T + * - TRUE low power ipc + * - FALSE non low power ipc + */ +BOOL_T tuya_ipc_is_low_power(VOID); + +/** + * @brief get ipc skill + * @param[in] skill skill defined in TUYA_IPC_SKILL_E + * @param[out] param skill result + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_skill_get(TUYA_IPC_SKILL_E skill, INT_T *result); +/** + * @brief get clow gw skill + * @param[in] device device number + * @param[in] channel device channel + * @param[out] skill_result skill result + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_cloud_gw_kills_get(UINT_T device, UINT_T channel, CHAR_T *skill_result); + +/** + * check if VOID tuya_ipc_upload_skills(VOID) be called. + */ +BOOL_T tuya_ipc_get_if_skill_uploaded(); + +/** + * @brief set local ai skills + * @param[in] local_ai_skills local ai skills, refer to TUYA_IPC_LOCAL_AI_SKILL_E + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_set_local_ai_skills(IN UINT_T local_ai_skills); + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_SKILL_H_*/ diff --git a/src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h b/src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h new file mode 100755 index 000000000..cbd4ec558 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h @@ -0,0 +1,39 @@ +#ifndef __TUYA_P2P_SDK_H__ +#define __TUYA_P2P_SDK_H__ +#include "cJSON.h" +#include "tuya_cloud_types.h" +#include "tuya_ipc_p2p.h" + +typedef struct { + CHAR_T *pk; + CHAR_T *firmware_key; + CHAR_T *url; + CHAR_T *id; // devid + CHAR_T *uuid; + CHAR_T *hid; + CHAR_T *token; + CHAR_T *sw_ver; + CHAR_T *pv; + CHAR_T *bv; + CHAR_T *cad_ver; + CHAR_T *cd_ver; + CHAR_T *modules; // [{"type":3,online:true,"softVer":"1.0"}] + CHAR_T *feature; // user self define + CHAR_T *auth_key; + CHAR_T *options; + CHAR_T *dev_sw_ver; // no longer used after cad:1.0.4 +} GW_ACTV_IN_PARM_V41_S; + +typedef struct tagTuyaIpcSdkVar { + INT_T (*OnSignalDisconnectCallback)(); + INT_T (*OnGetVideoFrameCallback)(MEDIA_FRAME *pMediaFrame); + INT_T (*OnGetAudioFrameCallback)(MEDIA_FRAME *pMediaFrame); +} TUYA_IPC_SDK_VAR_S; + +OPERATE_RET TUYA_APP_Start(TUYA_IPC_SDK_VAR_S *pSdkVar); +OPERATE_RET TUYA_APP_End(); +OPERATE_RET OnIotInited(); +CHAR_T *gw_active_get_ext_param(); +VOID gw_p2p_mqtt_data_cb(IN cJSON *root_json); + +#endif diff --git a/src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c b/src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c new file mode 100755 index 000000000..7f752cfb6 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c @@ -0,0 +1,627 @@ +#include "tuya_ipc_skill.h" +#include +#include +#include +#include "tal_log.h" +#include "tal_time_service.h" +#include "tuya_ipc_media_adapter.h" +#include "atop_service.h" + +#define IOT_SDK_VER "6.2.1" + +#ifdef __UT_TEST_ +#define STATIC +#endif +#if (ENABLE_CLOUD_RULE == 1) +#define MAX_AI_SKILL_NUM 5 +STATIC CHAR_T g_ai_skill_table[MAX_AI_SKILL_NUM][32] = { + "ipc_human", "ipc_cat", "ipc_car", "ipc_face", "ipc_package", +}; +#endif +typedef struct { + INT_T cloud_stg; + INT_T webrtc; + INT_T low_power; + INT_T ai_detect; + INT_T local_stg; + INT_T cloud_gw; + INT_T door_bell_stg; + INT_T p2p; + INT_T px; + CHAR_T *cloud_gw_info; + CHAR_T *media_info; + INT_T person_flow; + INT_T upnp; + INT_T max_p2p_sessions; + INT_T cloud_rule; + INT_T local_ai_skills; + INT_T mobile_network; +} tuya_ipc_skill_info_s; + +STATIC tuya_ipc_skill_info_s g_skill_info = {0}; +STATIC BOOL_T sg_uploaded = FALSE; + +#define SKILL_PARAM_LEN 1024 +// Force HTTPS POST 4.1 +#define TI_DEV_SKILL_UPDATE "tuya.device.skill.update" +#define TI_DEV_SKILL_MULTIMEDIA_UPDATE "tuya.device.skill.multimedia.update" +extern VOID_T *tkl_system_psram_malloc(CONST SIZE_T size); +extern VOID_T tkl_system_psram_free(VOID_T *ptr); +// extern int tuya_ipc_ring_buffer_get_video_num_skill(int,int); + +INT_T tuya_imm_get_security_ability(VOID); + +OPERATE_RET httpc_dev_update_skill_multimedia(IN CONST CHAR_T *gw_id, IN CONST CHAR_T *skill) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(skill); + + INT_T buffer_len = 70 + strlen(skill); + CHAR_T *post_data = tkl_system_psram_malloc(buffer_len); + if (post_data == NULL) { + PR_ERR("tkl_system_psram_malloc Fail"); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, buffer_len); + + snprintf(post_data, buffer_len, "%s", skill); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = iot_httpc_common_post_no_remalloc(TI_DEV_SKILL_MULTIMEDIA_UPDATE, "1.0", + // NULL, gw_id, + // post_data, buffer_len, NULL, + // NULL); + op_ret = atop_service_comm_post_simple(TI_DEV_SKILL_MULTIMEDIA_UPDATE, "1.0", post_data, NULL, NULL); + tkl_system_psram_free(post_data); + return op_ret; +} + +OPERATE_RET httpc_dev_update_skill_v10(IN CONST CHAR_T *gw_id, IN CONST CHAR_T *sub_id, IN CONST CHAR_T *skill) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(skill); + + INT_T buffer_len = 70 + strlen(skill); + CHAR_T *post_data = tkl_system_psram_malloc(buffer_len); + if (post_data == NULL) { + PR_ERR("tkl_system_psram_malloc Fail"); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, buffer_len); + + if (sub_id == NULL) { + TIME_T timestamp = 0; + timestamp = tal_time_get_posix(); + snprintf(post_data, buffer_len, "{\"skill\":\"%s\",\"t\":\"%d\"}", skill, + timestamp); // Note: tuya-open needs to add a timestamp here + } else { + snprintf(post_data, buffer_len, "{\"subId\":\"%s\",\"skill\":\"%s\"}", sub_id, skill); + } + printf("post_data: %s\n", post_data); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = iot_httpc_common_post_no_remalloc(TI_DEV_SKILL_UPDATE, "1.0", + // NULL, gw_id, + // post_data, buffer_len, NULL, + // NULL); + op_ret = atop_service_comm_post_simple(TI_DEV_SKILL_UPDATE, "1.0", post_data, NULL, NULL); + tkl_system_psram_free(post_data); + return op_ret; +} + +OPERATE_RET http_device_update_skill(IN CONST CHAR_T *dev_id, IN CONST CHAR_T *skill) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_dev_update_skill_v10(/*gw_cntl->gw_if.id*/ NULL, dev_id, skill); + return op_ret; +} + +OPERATE_RET http_device_update_skill_multimedia(IN CONST CHAR_T *dev_id, IN CONST CHAR_T *skill) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_dev_update_skill_multimedia(/*gw_cntl->gw_if.id*/ NULL, skill); + return op_ret; +} + +STATIC OPERATE_RET __fill_skills(CHAR_T *skill_info, INT_T skill_len, CHAR_T *skills_buf, INT_T *buf_len) +{ + if (skill_len + *buf_len >= SKILL_PARAM_LEN) { + PR_ERR("skill buf is not enough\n"); + return OPRT_INVALID_PARM; + } + memcpy(skills_buf + *buf_len, skill_info, skill_len); + *buf_len += skill_len; + + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_skill_enable_person_flow(VOID) +{ + TUYA_IPC_SKILL_PARAM_U param = {0}; + param.value = 1; + return tuya_ipc_skill_enable(TUYA_IPC_SKILL_PERSON_FLOW, ¶m); +} + +VOID tuya_ipc_upload_skills() +{ + CHAR_T *skill_buf = (CHAR_T *)tkl_system_psram_malloc(SKILL_PARAM_LEN); + if (skill_buf == NULL) { + return; + } + skill_buf[0] = '{'; + INT_T buf_len = 1; + + CHAR_T tmp_buf[128] = {0}; + int len = 0; + sg_uploaded = TRUE; + +#if defined(ENABLE_TMM_LINK) && (ENABLE_TMM_LINK == 1) + len = sprintf(tmp_buf, "\\\"v\\\":2,\\\"sdk_version\\\":\\\"" IOT_SDK_VER "\\\""); +#else + len = sprintf(tmp_buf, "\\\"sdk_version\\\":\\\"" IOT_SDK_VER "\\\""); +#endif + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + +#ifdef IPC_CHANNEL_NUM + len = sprintf(tmp_buf, ",\\\"channel_num\\\":%d", IPC_CHANNEL_NUM); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } +#endif + + if (0 != g_skill_info.ai_detect) { + len = sprintf(tmp_buf, ",\\\"aiDetect\\\":%d", g_skill_info.ai_detect); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.cloud_gw) { + len = sprintf(tmp_buf, ",\\\"cloudGW\\\":%d", g_skill_info.cloud_gw); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.cloud_stg) { + len = sprintf(tmp_buf, ",\\\"cloudStorage\\\":%d", g_skill_info.cloud_stg); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.door_bell_stg) { + len = sprintf(tmp_buf, ",\\\"doorbellStorage\\\":%d", g_skill_info.door_bell_stg); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.local_stg) { + len = sprintf(tmp_buf, ",\\\"localStorage\\\":%d", g_skill_info.local_stg); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.low_power) { + len = sprintf(tmp_buf, ",\\\"lowPower\\\":%d", g_skill_info.low_power); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.webrtc) { + len = sprintf(tmp_buf, ",\\\"webrtc\\\":%d", g_skill_info.webrtc); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.p2p) { + len = sprintf(tmp_buf, ",\\\"p2p\\\":%d", g_skill_info.p2p); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.px) { + len = sprintf(tmp_buf, ",\\\"px\\\":%d", g_skill_info.px); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.upnp) { + len = sprintf(tmp_buf, ",\\\"upnp\\\":%d", g_skill_info.upnp); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.max_p2p_sessions) { + len = sprintf(tmp_buf, ",\\\"max_p2p_sessions\\\":%d", g_skill_info.max_p2p_sessions); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.mobile_network) { + len = sprintf(tmp_buf, ",\\\"mobileNetwork\\\":%d", g_skill_info.mobile_network); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + // int Num = tuya_ipc_ring_buffer_get_video_num_skill(0,0); + int Num = 1; + len = sprintf(tmp_buf, ",\\\"video_num\\\":%d", Num); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + + if (g_skill_info.cloud_gw_info) { + len = strlen(g_skill_info.cloud_gw_info); + if (len > 0) { + if (OPRT_OK != __fill_skills(",", 1, skill_buf, &buf_len)) { + goto out; + } + if (OPRT_OK != __fill_skills(g_skill_info.cloud_gw_info, len, skill_buf, &buf_len)) { + + goto out; + } + } + } + + if (0 != g_skill_info.person_flow) { + len = sprintf(tmp_buf, ",\\\"person_flow\\\":%d", g_skill_info.person_flow); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + INT_T security_ability = tuya_imm_get_security_ability(); + if (security_ability) { + len = sprintf(tmp_buf, ",\\\"security_ability\\\":%d", security_ability); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + +#if (ENABLE_CLOUD_RULE == 1) + if (0 != g_skill_info.cloud_rule) { + len = sprintf(tmp_buf, ",\\\"cloudRule\\\":%d", g_skill_info.cloud_rule); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + INT_T i = 0; + INT_T offset = 0; + BOOL_T need_comma = FALSE; + if (0 != g_skill_info.local_ai_skills) { + len = sprintf(tmp_buf, ",\\\"extSkill\\\":\\\"{"); + offset += len; + for (i = 0; i < MAX_AI_SKILL_NUM; i++) { + if (g_skill_info.local_ai_skills & (1 << i)) { + if (need_comma) { + tmp_buf[offset] = ','; + offset += 1; + } + len = sprintf(tmp_buf + offset, "\\\"%s\\\":true", g_ai_skill_table[i]); + offset += len; + need_comma = TRUE; + } + } + len = sprintf(tmp_buf + offset, "}\\\""); + offset += len; + if (OPRT_OK != __fill_skills(tmp_buf, offset, skill_buf, &buf_len)) { + goto out; + } + } +#endif + skill_buf[buf_len] = '}'; + http_device_update_skill(NULL, skill_buf); + +#if defined(ENABLE_TMM_LINK) && (ENABLE_TMM_LINK == 1) + PR_DEBUG("upload data: %s", skill_buf); + tuya_ipc_upload_media_info(); +#endif + +out: + if (skill_buf) { + tkl_system_psram_free(skill_buf); + skill_buf = NULL; + } + if (g_skill_info.cloud_gw_info) { + tkl_system_psram_free(g_skill_info.cloud_gw_info); + g_skill_info.cloud_gw_info = NULL; + } + return; +} + +OPERATE_RET tuya_ipc_upload_media_info(VOID) +{ + OPERATE_RET ret = OPRT_COM_ERROR; + CHAR_T *skill_buf = tkl_system_psram_malloc(SKILL_PARAM_LEN); + if (NULL == skill_buf) { + PR_ERR("malloc skill buf failed"); + return ret; + } + + if (g_skill_info.media_info) { + int len = strlen(g_skill_info.media_info); + if (len > 0) { + memset(skill_buf, 0, SKILL_PARAM_LEN); + int buf_len = 0; + if (OPRT_OK == __fill_skills(g_skill_info.media_info, len, skill_buf, &buf_len)) { + PR_DEBUG("upload data: %s", skill_buf); + ret = http_device_update_skill_multimedia(NULL, skill_buf); + } + } + tkl_system_psram_free(g_skill_info.media_info); + g_skill_info.media_info = NULL; + } + + if (skill_buf) { + tkl_system_psram_free(skill_buf); + skill_buf = NULL; + } + PR_DEBUG("upload finish"); + return ret; +} + +OPERATE_RET tuya_ipc_skill_enable(IN TUYA_IPC_SKILL_E skill, IN TUYA_IPC_SKILL_PARAM_U *param) +{ + switch (skill) { + + case TUYA_IPC_SKILL_AIDETECT: + g_skill_info.ai_detect = param->value; + break; + + case TUYA_IPC_SKILL_CLOUDGW: + g_skill_info.cloud_gw = g_skill_info.cloud_gw | param->value; + break; + + case TUYA_IPC_SKILL_CLOUDSTG: + g_skill_info.cloud_stg = param->value; + break; + + case TUYA_IPC_SKILL_DOORBELLSTG: + g_skill_info.door_bell_stg = param->value; + break; + + case TUYA_IPC_SKILL_LOCALSTG: + g_skill_info.local_stg = param->value; + break; + + case TUYA_IPC_SKILL_LOWPOWER: + g_skill_info.low_power = param->value; + break; + + case TUYA_IPC_SKILL_WEBRTC: + g_skill_info.webrtc = param->value; + break; + case TUYA_IPC_SKILL_P2P: + g_skill_info.p2p = param->value; + break; + case TUYA_IPC_SKILL_PX: + g_skill_info.px = param->value; + break; + case TUYA_IPC_SKILL_TMM: + if (g_skill_info.media_info == NULL) { + g_skill_info.media_info = tkl_system_psram_malloc(SKILL_INFO_LEN); + if (g_skill_info.media_info == NULL) { + PR_ERR("malloc media_info failed"); + return OPRT_MALLOC_FAILED; + } + } + strncpy(g_skill_info.media_info, param->info, SKILL_INFO_LEN); + break; + case TUYA_IPC_SKILL_PERSON_FLOW: + g_skill_info.person_flow = param->value; + break; + case TUYA_IPC_SKILL_CLOUDGW_INFO: + if (g_skill_info.cloud_gw_info == NULL) { + g_skill_info.cloud_gw_info = tkl_system_psram_malloc(SKILL_INFO_LEN); + if (g_skill_info.cloud_gw_info == NULL) { + PR_ERR("malloc cloud_gw_info failed"); + return OPRT_MALLOC_FAILED; + } + } + strncpy(g_skill_info.cloud_gw_info, param->info, SKILL_INFO_LEN); + break; + case TUYA_IPC_SKILL_UPNP: + g_skill_info.upnp = param->value; + break; + case TUYA_IPC_SKILL_MAX_P2P_SESSIONS: + g_skill_info.max_p2p_sessions = param->value; + break; + case TUYA_IPC_SKILL_CLOUDRULE: + g_skill_info.cloud_rule = param->value; + break; + case TUYA_IPC_SKILL_CLOUDRULE_GW: + g_skill_info.cloud_gw = g_skill_info.cloud_gw | param->value; + break; + case TUYA_IPC_SKILL_MOBILE_NETWORK: + g_skill_info.mobile_network = param->value; + break; + default: + PR_ERR("unknown skill(%d)\n", skill); + break; + } + return OPRT_OK; +} + +VOID tuya_ipc_http_fill_skills_cb(INOUT CHAR_T *skills) +{ + if (NULL == skills) { + return; + } + int Num = 0; + strcat(skills, "{\\\"localStorage\\\":1"); +#if ENABLE_CLOUD_STORAGE == 1 + strcat(skills, ",\\\"cloudStorage\\\":1"); +#endif +#if ENABLE_ECHO_SHOW == 1 + strcat(skills, ",\\\"echoshow\\\":2"); +#endif +#if ENABLE_CHROMECAST == 1 + strcat(skills, ",\\\"chromecast\\\":2"); +#endif + +#if ENABLE_MQTT_WEBRTC == 1 + strcat(skills, ",\\\"webrtc\\\":2"); +#endif + +#if ENABLE_CLOUD_RULE == 1 +#if ENABLE_CLOUD_RULE_CRON == 1 + strcat(skills, ",\\\"cloudRule\\\":7"); +#else + strcat(skills, ",\\\"cloudRule\\\":5"); +#endif +#endif + + if (g_skill_info.low_power) { + strcat(skills, ",\\\"lowPower\\\":1"); + } + + INT_T security_ability = tuya_imm_get_security_ability(); + if (security_ability) { + CHAR_T buf[40] = {0}; + snprintf(buf, SIZEOF(buf), ",\\\"security_ability\\\":%d", security_ability); + strcat(skills, buf); + } + + Num = 1 /*tuya_ipc_ring_buffer_get_video_num_skill(0,0)*/; + unsigned char Data[20] = {0}; + sprintf((char *)Data, ",\\\"video_num\\\":%d", Num); + strcat(skills, (char *)Data); + + strcat(skills, ",\\\"sdk_version\\\":\\\"" IOT_SDK_VER "\\\"}"); +} + +BOOL_T tuya_ipc_is_low_power(VOID) +{ + return g_skill_info.low_power != 0; +} + +OPERATE_RET tuya_ipc_skill_get(TUYA_IPC_SKILL_E skill, INT_T *result) +{ + OPERATE_RET ret = OPRT_OK; + switch (skill) { + + case TUYA_IPC_SKILL_AIDETECT: + *result = g_skill_info.ai_detect; + break; + + case TUYA_IPC_SKILL_CLOUDGW: + *result = g_skill_info.cloud_gw; + break; + + case TUYA_IPC_SKILL_CLOUDSTG: + *result = g_skill_info.cloud_stg; + break; + + case TUYA_IPC_SKILL_DOORBELLSTG: + *result = g_skill_info.door_bell_stg; + break; + + case TUYA_IPC_SKILL_LOCALSTG: + *result = g_skill_info.local_stg; + break; + + case TUYA_IPC_SKILL_LOWPOWER: + *result = g_skill_info.low_power; + break; + + case TUYA_IPC_SKILL_WEBRTC: + *result = g_skill_info.webrtc; + break; + case TUYA_IPC_SKILL_P2P: + *result = g_skill_info.p2p; + break; + case TUYA_IPC_SKILL_PX: + *result = g_skill_info.px; + break; + case TUYA_IPC_SKILL_PERSON_FLOW: + *result = g_skill_info.person_flow; + break; + case TUYA_IPC_SKILL_UPNP: + *result = g_skill_info.upnp; + break; + case TUYA_IPC_SKILL_MAX_P2P_SESSIONS: + *result = g_skill_info.max_p2p_sessions; + break; + case TUYA_IPC_SKILL_MOBILE_NETWORK: + *result = g_skill_info.mobile_network; + break; + default: + PR_ERR("unknown skill(%d)\n", skill); + ret = OPRT_NOT_SUPPORTED; + break; + } + return ret; +} + +OPERATE_RET tuya_ipc_cloud_gw_kills_get(UINT_T device, UINT_T channel, CHAR_T *skill_result) +{ + if (skill_result == NULL) { + return OPRT_INVALID_PARM; + } + DEVICE_MEDIA_INFO_T media_info; + memset(&media_info, 0, sizeof(DEVICE_MEDIA_INFO_T)); + // tuya_ipc_media_adapter_get_media_info(device,channel,&media_info); + INT_T len = sprintf(skill_result, "\\\"cloudGW\\\":%d,", 1); + snprintf(skill_result + len, 512, + "\\\"videos\\\":[{\\\"streamType\\\":2,\\\"codecType\\\":%d,\\\"sampleRate\\\":%d," + "\\\"profileId\\\":\\\"\\\",\\\"width\\\":%d,\\\"height\\\":%d}," + "{\\\"streamType\\\":4,\\\"codecType\\\":%d,\\\"sampleRate\\\":%d," + "\\\"width\\\":%d,\\\"height\\\":%d}]," + "\\\"audios\\\":[{\\\"codecType\\\":%d,\\\"sampleRate\\\":%d," + "\\\"dataBit\\\":%d,\\\"channels\\\":1}]", + media_info.av_encode_info.video_codec[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_freq[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_width[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_height[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_codec[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.video_freq[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.video_width[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.video_height[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.audio_codec[E_IPC_STREAM_AUDIO_MAIN], + media_info.av_encode_info.audio_sample[E_IPC_STREAM_AUDIO_MAIN], + media_info.av_encode_info.audio_databits[E_IPC_STREAM_AUDIO_MAIN]); + + PR_DEBUG("CLOUD GW skill info:%s", skill_result); + return OPRT_OK; +} + +BOOL_T tuya_ipc_get_if_skill_uploaded() +{ + return sg_uploaded; +} + +OPERATE_RET tuya_ipc_set_local_ai_skills(IN UINT_T local_ai_skills) +{ + g_skill_info.local_ai_skills = local_ai_skills; + return OPRT_OK; +} + +INT_T tuya_imm_get_security_ability(VOID) +{ + return ((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4)); +} + +VOID_T *tkl_system_psram_malloc(CONST SIZE_T size) +{ + return malloc(size); +} + +VOID_T tkl_system_psram_free(VOID_T *ptr) +{ + free(ptr); +} \ No newline at end of file diff --git a/src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c b/src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c new file mode 100755 index 000000000..c5f52b5a5 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c @@ -0,0 +1,219 @@ +#include "tuya_p2p_sdk.h" +#include +#include +#include "cJSON.h" +#include "tal_log.h" +#include "tuya_cloud_types.h" +#include "tuya_error_code.h" +#include "tuya_iot.h" +#include "tuya_ipc_skill.h" +#include "tuya_media_service_rtc.h" +#include "tuya_ipc_media_stream.h" +#include "tuya_ipc_media_stream_common.h" + +#define PRE_TOPIC "smart/device/in/" +#define MQ_SERV_TOPIC "smart/device/out/" + +VOID tuya_ipc_upload_skills(VOID); +OPERATE_RET gw_active_set_ext_param(IN CHAR_T *param); +CHAR_T *gw_active_get_ext_param(VOID); +OPERATE_RET httpc_gw_active(IN CONST GW_ACTV_IN_PARM_V41_S *param, OUT cJSON **result); +OPERATE_RET __p2p_v3_login_init(INT_T preconnect, INT_T max_client, INT_T bitrate); +VOID tuya_p2p_rtc_signaling_cb(CHAR_T *remote_id, CHAR_T *signaling, UINT_T len); + +OPERATE_RET TUYA_APP_Start(TUYA_IPC_SDK_VAR_S *pSdkVar) +{ + OPERATE_RET ret = OPRT_OK; + + // Set activation skill parameters and report + TUYA_IPC_SKILL_PARAM_U skill_param = {.value = 0}; + skill_param.value = tuya_p2p_rtc_get_skill(); + tuya_ipc_skill_enable(TUYA_IPC_SKILL_P2P, &skill_param); + skill_param.value = 1; + tuya_ipc_skill_enable(TUYA_IPC_SKILL_LOWPOWER, &skill_param); + tuya_ipc_upload_skills(); + + // Initialize P2P component + MEDIA_STREAM_VAR_T stream_var = {0}; + stream_var.max_client_num = 1; + stream_var.def_live_mode = TRANS_DEFAULT_STANDARD; + stream_var.recv_buffer_size = 16 * 1024; + INT_T preconnect = stream_var.low_power ? 0 : 1; + ret = __p2p_v3_login_init(preconnect, stream_var.max_client_num, /*media_info.av_encode_info.video_bitrate[0]*/ 0); + if (OPRT_OK != ret) { + PR_ERR("__p2p_v3_login_init failed\n"); + return ret; + } + + p2p_rtc_listen_start(); + + TUYA_IPC_P2P_VAR_T var = {0}; + var.max_client_num = stream_var.max_client_num; + var.def_live_mode = stream_var.def_live_mode; + var.low_power = stream_var.low_power; + var.recv_buffer_size = stream_var.recv_buffer_size; + var.on_disconnect_callback = pSdkVar->OnSignalDisconnectCallback; + var.on_get_video_frame_callback = pSdkVar->OnGetVideoFrameCallback; + var.on_get_audio_frame_callback = pSdkVar->OnGetAudioFrameCallback; + if (var.recv_buffer_size == 0) { + var.recv_buffer_size = 16 * 1024; + } + ret = p2p_init(&var); + if (OPRT_OK != ret) { + PR_ERR("tuya_ipc_p2p_init failed \n"); + return ret; + } + return ret; +} + +OPERATE_RET TUYA_APP_End() +{ + return 0; +} + +OPERATE_RET OnIotInited() +{ + OPERATE_RET rt = OPRT_OK; + //mqtt extra init cb + //tuya_ipc_mqtt_register_cb_init(); + // Enable skill + TUYA_IPC_SKILL_PARAM_U skill_param = {.value = 1}; + tuya_ipc_skill_enable(TUYA_IPC_SKILL_LOWPOWER, &skill_param); + // Set activation skill parameters + CHAR_T *ipc_skills = NULL; +#if defined(HARDWARE_INFO_CHECK) && (HARDWARE_INFO_CHECK == 1) + int len = 4096; + ipc_skills = (CHAR_T *)malloc(len); +#else + int len = 256; + ipc_skills = (CHAR_T *)malloc(len); +#endif + memset(ipc_skills, 0, len); + if (ipc_skills) { + // strcpy(ipc_skills, "\"skillParam\":\""); + snprintf(ipc_skills + strlen(ipc_skills), len - strlen(ipc_skills), + "{\\\"type\\\":%d,\\\"skill\\\":", TUYA_P2P); // P2P type + tuya_ipc_http_fill_skills_cb(ipc_skills); +#if defined(HARDWARE_INFO_CHECK) && (HARDWARE_INFO_CHECK == 1) + TUYA_IPC_SENSOR_INFO_T sensor_info = {0}; + tuya_ipc_hardware_info_fill(ipc_skills, &sensor_info); +#endif + // strcat(ipc_skills,"}\""); + strcat(ipc_skills, "}"); + } + gw_active_set_ext_param(ipc_skills); + +#if defined(ENABLE_IPC_4G) && (ENABLE_IPC_4G == 1) + tuya_ipc_dev_manager_init(); +#endif + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +STATIC CHAR_T *s_ext_param = NULL; // user defined functions +OPERATE_RET gw_active_set_ext_param(IN CHAR_T *param) +{ + s_ext_param = param; + return OPRT_OK; +} + +CHAR_T *gw_active_get_ext_param() +{ + return s_ext_param; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +VOID gw_p2p_mqtt_data_cb(IN cJSON *root_json) +{ + int ret = 0; + if (root_json == NULL) { + PR_ERR("root_json is null"); + return; + } + + // cJSON *json = NULL; + // json = cJSON_GetObjectItem(root_json, "data"); + // if (NULL == json){ + // PR_ERR("data failed"); + // return ; + // } + + CHAR_T *sendBuff = NULL; + sendBuff = cJSON_PrintUnformatted(root_json); + if (NULL == sendBuff) { + PR_ERR("send buff is NULL"); + return; + } + + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + ret = tuya_p2p_rtc_set_signaling(NULL, sendBuff, strlen(sendBuff)); + // ret = tuya_p2p_parse_signal(gw_cntl->gw_if.id, sendBuff, strlen(sendBuff)); + if (OPRT_OK != ret) { + PR_ERR("tuya_p2p_rtc_set_signaling error"); + } + + if (sendBuff) { + cJSON_free(sendBuff); + } + + return; +} + +OPERATE_RET __p2p_v3_login_init(INT_T preconnect, INT_T max_client, INT_T bitrate) +{ + OPERATE_RET mqttP2pRet = OPRT_OK; + + tuya_iot_client_t *pIotClient = tuya_iot_client_get(); + char *dev_id = pIotClient->activate.devid; + + tuya_p2p_rtc_options_t strOpt; + memset(&strOpt, 0x00, sizeof(tuya_p2p_rtc_options_t)); + memcpy(strOpt.local_id, /*gw_cntl->gw_if.id*/ dev_id, /*sizeof(gw_cntl->gw_if.id)*/ strlen(dev_id)); + + strOpt.preconnect_enable = preconnect; + strOpt.fragement_len = /*RTP_MTU_LEN*/ 1100 + 100; // Reserve 100 bytes for RTP header and private header + // strOpt.cb.on_moto_signaling = tuya_p2p_rtc_moto_signaling_cb; + strOpt.cb.on_signaling = tuya_p2p_rtc_signaling_cb; + // strOpt.cb.on_lan_signaling = tuya_p2p_lan_signaling_cb; + // strOpt.cb.on_log = __media_service_rtc_log_upload; + // strOpt.cb.on_log_get_level = tuya_imm_service_log_get_level; + // strOpt.cb.on_auth = tuya_p2p_rtc_auth; + strOpt.max_channel_number = /*TUYA_CHANNEL_MAX*/ 6; + strOpt.max_session_number = max_client; + strOpt.max_pre_session_number = max_client; + strOpt.video_bitrate_kbps = + bitrate; // Current video_bitrate_kbps parameter is used for setting webrtc channel memory size in p2p library + strOpt.send_buf_size[TUYA_CMD_CHANNEL] = 4096; + strOpt.recv_buf_size[TUYA_CMD_CHANNEL] = 4096; + strOpt.send_buf_size[TUYA_VDATA_CHANNEL] = (300 * 1024) * 1.1; + strOpt.recv_buf_size[TUYA_VDATA_CHANNEL] = 1024; + strOpt.send_buf_size[TUYA_ADATA_CHANNEL] = 2 * P2P_WR_BF_MAX_SIZE + P2P_SEND_REDUNDANCE_LEN; + strOpt.recv_buf_size[TUYA_ADATA_CHANNEL] = 1024 * 64; + strOpt.send_buf_size[TUYA_TRANS_CHANNEL] = P2P_WR_BF_MAX_SIZE + P2P_SEND_REDUNDANCE_LEN; + strOpt.recv_buf_size[TUYA_TRANS_CHANNEL] = 1024; + strOpt.send_buf_size[TUYA_TRANS_CHANNEL5] = P2P_WR_BF_MAX_SIZE + P2P_SEND_REDUNDANCE_LEN; + strOpt.recv_buf_size[TUYA_TRANS_CHANNEL5] = 1024 * 1024; // do not use + mqttP2pRet = tuya_p2p_rtc_init(&strOpt); + if (0 != mqttP2pRet) { + PR_ERR("mqtt p2p init failed"); + return -2; + } + return mqttP2pRet; +} + +VOID tuya_p2p_rtc_signaling_cb(CHAR_T *remote_id, CHAR_T *signaling, UINT_T len) +{ + // Send answer signaling + tuya_iot_client_t *pIotClient = tuya_iot_client_get(); + char *dev_id = pIotClient->activate.devid; + + CHAR_T send_topic[18 + GW_ID_LEN] = {0}; + snprintf(send_topic, SIZEOF(send_topic), "%s%s", MQ_SERV_TOPIC, dev_id); + PR_DEBUG("mqtt send topic:%s", send_topic); + tuya_mqtt_protocol_data_publish_with_topic(&pIotClient->mqctx, send_topic, PRO_RTC_REQ, signaling, len); + + return; +} \ No newline at end of file diff --git a/src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt b/src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt new file mode 100755 index 000000000..7d2a12afc --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt @@ -0,0 +1,53 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h new file mode 100755 index 000000000..20c7fa208 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h @@ -0,0 +1,213 @@ +#ifndef _TUYA_IPC_MEDIA_H_ +#define _TUYA_IPC_MEDIA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" + +/** @brief default max frame size that can be put into ring buffer + */ +#define MAX_MEDIA_FRAME_SIZE (300 * 1024) + +/** @enum MEDIA_FRAME_TYPE_E + * @brief media frame type + */ +typedef enum { + E_VIDEO_PB_FRAME = 0, ///< p frame + E_VIDEO_I_FRAME, ///< i frame + E_VIDEO_TS_FRAME, ///< ts frame + E_AUDIO_FRAME, ///< audio frame + E_CMD_FRAME, ///< cmd frame + E_MEDIA_FRAME_TYPE_MAX +} MEDIA_FRAME_TYPE_E; + +/** @enum IPC_STREAM_E + * @brief stream type + */ +typedef enum { + E_IPC_STREAM_VIDEO_MAIN, ///< first video stream + E_IPC_STREAM_VIDEO_SUB, ///< second video stream + E_IPC_STREAM_VIDEO_3RD, ///< third video stream + E_IPC_STREAM_VIDEO_4TH, ///< forth video stream + E_IPC_STREAM_VIDEO_MAX = 8, + E_IPC_STREAM_AUDIO_MAIN, ///< first audio stream + E_IPC_STREAM_AUDIO_SUB, ///< second audio stream + E_IPC_STREAM_AUDIO_3RD, ///< third audio stream + E_IPC_STREAM_AUDIO_4TH, ///< forth audio stream + E_IPC_STREAM_MAX = 16, +} IPC_STREAM_E; + +/** @enum TUYA_CODEC_ID_E + * @warning sync with tuya cloud and app, should not be changed + */ +typedef enum { + TUYA_CODEC_VIDEO_MPEG4 = 0, + TUYA_CODEC_VIDEO_H263, + TUYA_CODEC_VIDEO_H264, + TUYA_CODEC_VIDEO_MJPEG, + TUYA_CODEC_VIDEO_H265, + TUYA_CODEC_VIDEO_YUV420, + TUYA_CODEC_VIDEO_YUV422, + TUYA_CODEC_VIDEO_MAX = 99, + + TUYA_CODEC_AUDIO_ADPCM, // 100 + TUYA_CODEC_AUDIO_PCM, + TUYA_CODEC_AUDIO_AAC_RAW, + TUYA_CODEC_AUDIO_AAC_ADTS, + TUYA_CODEC_AUDIO_AAC_LATM, + TUYA_CODEC_AUDIO_G711U, // 105 + TUYA_CODEC_AUDIO_G711A, + TUYA_CODEC_AUDIO_G726, + TUYA_CODEC_AUDIO_SPEEX, + TUYA_CODEC_AUDIO_MP3, + TUYA_CODEC_AUDIO_G722, // 110 + TUYA_CODEC_AUDIO_MAX = 199, + TUYA_CODEC_INVALID +} TUYA_CODEC_ID_E; + +/** @enum TUYA_AUDIO_SAMPLE_E + * @brief audio sample rate + */ +typedef enum { + TUYA_AUDIO_SAMPLE_8K = 8000, + TUYA_AUDIO_SAMPLE_11K = 11000, + TUYA_AUDIO_SAMPLE_12K = 12000, + TUYA_AUDIO_SAMPLE_16K = 16000, + TUYA_AUDIO_SAMPLE_22K = 22000, + TUYA_AUDIO_SAMPLE_24K = 24000, + TUYA_AUDIO_SAMPLE_32K = 32000, + TUYA_AUDIO_SAMPLE_44K = 44000, + TUYA_AUDIO_SAMPLE_48K = 48000, + TUYA_AUDIO_SAMPLE_MAX = 0xFFFFFFFF +} TUYA_AUDIO_SAMPLE_E; + +/** @enum TUYA_VIDEO_BITRATE_E + * @brief video bitrate, in kb + */ +typedef enum { + TUYA_VIDEO_BITRATE_64K = 64, + TUYA_VIDEO_BITRATE_128K = 128, + TUYA_VIDEO_BITRATE_256K = 256, + TUYA_VIDEO_BITRATE_512K = 512, + TUYA_VIDEO_BITRATE_768K = 768, + TUYA_VIDEO_BITRATE_1M = 1024, + TUYA_VIDEO_BITRATE_1_5M = 1536, + TUYA_VIDEO_BITRATE_2M = 2048, // maximum 2Mbps stream is supported, as consideration of cloud storage order price + TUYA_VIDEO_BITRATE_5M = 5120 +} TUYA_VIDEO_BITRATE_E; // Kbps + +/** @enum TUYA_AUDIO_DATABITS_E + * @brief audio databits + */ +typedef enum { + TUYA_AUDIO_DATABITS_8 = 8, + TUYA_AUDIO_DATABITS_16 = 16, + TUYA_AUDIO_DATABITS_MAX = 0xFF +} TUYA_AUDIO_DATABITS_E; + +/** @enum TUYA_AUDIO_CHANNEL_E + * @brief audio channel + */ +typedef enum { + TUYA_AUDIO_CHANNEL_MONO, + TUYA_AUDIO_CHANNEL_STERO, +} TUYA_AUDIO_CHANNEL_E; + +/** @struct IPC_MEDIA_INFO_T + * @brief media info of encoder + */ +typedef struct { + BOOL_T stream_enable[E_IPC_STREAM_MAX]; ///< set to true if this stream has data + + UINT_T video_fps[E_IPC_STREAM_VIDEO_MAX]; ///< video fps + UINT_T video_gop[E_IPC_STREAM_VIDEO_MAX]; ///< video gop size + TUYA_VIDEO_BITRATE_E video_bitrate[E_IPC_STREAM_VIDEO_MAX]; ///< video bitrate + UINT_T video_width[E_IPC_STREAM_VIDEO_MAX]; ///< video width + UINT_T video_height[E_IPC_STREAM_VIDEO_MAX]; ///< video height + UINT_T video_freq[E_IPC_STREAM_VIDEO_MAX]; ///< video frequency + TUYA_CODEC_ID_E video_codec[E_IPC_STREAM_VIDEO_MAX]; ///< video codec + + TUYA_CODEC_ID_E audio_codec[E_IPC_STREAM_MAX]; ///< audio codec + UINT_T audio_fps[E_IPC_STREAM_MAX]; ///< audio fps + TUYA_AUDIO_SAMPLE_E audio_sample[E_IPC_STREAM_MAX]; ///< audio sample + TUYA_AUDIO_DATABITS_E audio_databits[E_IPC_STREAM_MAX]; ///< audio databits + TUYA_AUDIO_CHANNEL_E audio_channel[E_IPC_STREAM_MAX]; ///< audio channel +} IPC_MEDIA_INFO_T; + +/** @enum VIDEO_AVC_PROFILE_TYPE_E + * @brief video profile type + */ +typedef enum { + VIDEO_AVC_PROFILE_BASE_LINE = 0x01, + VIDEO_AVC_PROFILE_MAIN = 0x02, + VIDEO_AVC_PROFILE_EXTENDED = 0x04, + VIDEO_AVC_PROFILE_HIGH = 0x08, + VIDEO_AVC_PROFILE_HIGH10 = 0x10, + VIDEO_AVC_PROFILE_HIGH422 = 0x20, + VIDEO_AVC_PROFILE_HIGH444 = 0x40, + VIDEO_AVC_PROFILE_KHRONOS_EXTENTIONS = 0x6F000000, + VIDEO_AVC_PROFILE_VENDOR_START_UNUSED = 0x7F000000, + VIDEO_AVC_PROFILE_MAX = 0x7FFFFFFF, +} VIDEO_AVC_PROFILE_TYPE_E; + +/** @struct TUYA_VIDEO_DECODER_DESC_T + * @brief video decoder information + */ +typedef struct { + VIDEO_AVC_PROFILE_TYPE_E profile; ///< video profile type + UINT_T height; ///< video height + UINT_T width; ///< video width +} TUYA_VIDEO_DECODER_DESC_T; + +/** @struct TUYA_AUDIO_DECODER_DESC_T + * @brief audio decoder information + */ +typedef struct { + TUYA_AUDIO_SAMPLE_E sample; ///< audio sample + TUYA_AUDIO_DATABITS_E databits; ///< audio databits +} TUYA_AUDIO_DECODER_DESC_T; + +/** @union TUYA_DECODER_DESC_U + * @brief decoder information + */ +typedef union { + TUYA_VIDEO_DECODER_DESC_T v_decoder; ///< video decoder information + TUYA_AUDIO_DECODER_DESC_T a_decoder; ///< audio decoder information +} TUYA_DECODER_DESC_U; + +/** @struct TUYA_DECODER_T + * @brief decoder information + */ +typedef struct { + TUYA_CODEC_ID_E codec_id; ///< decoder codec id + TUYA_DECODER_DESC_U decoder_desc; ///< decoder information +} TUYA_DECODER_T; + +/** @struct STORAGE_FRAME_HEAD_T + * @brief storage frame head + */ +typedef struct { + UINT_T type; ///< type + UINT_T size; ///< size + UINT64_T timestamp; ///< timestamp + UINT64_T pts; ///< pts +} STORAGE_FRAME_HEAD_T; + +/** @struct MEDIA_FRAME_T + * @brief media frame + */ +typedef struct { + MEDIA_FRAME_TYPE_E type; ///< frame type + BYTE_T *p_buf; ///< frame data buf + UINT_T size; ///< frame size + UINT64_T pts; ///< timestamp in us + UINT64_T timestamp; ///< timestamp is ms +} MEDIA_FRAME_T; + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_MEDIA_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h new file mode 100755 index 000000000..1485183f1 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h @@ -0,0 +1,434 @@ +#ifndef __TUYA_IPC_MEDIA_ADAPTER_H__ +#define __TUYA_IPC_MEDIA_ADAPTER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_media.h" +#include "tuya_ipc_media_stream_event.h" + +#define MAX_DECODER_CNT 10 + +/** @enum MEDIA_RECV_VIDEO_FRAME_TYPE_E + * @brief frame type + */ +typedef enum { + TUYA_VIDEO_FRAME_PBFRAME, ///< P/B frame */ + TUYA_VIDEO_FRAME_IFRAME, ///< I frame */ +} MEIDA_RECV_VIDEO_FRAME_TYPE_E; + +/** @enum STREAM_SOURCE_OPEN_TYPE_E + * @brief open for read or write + */ +typedef enum { + STREAM_READ = 0, ///< open for read + STREAM_WRITE = 1, ///< open for write +} STREAM_SOURCE_OPEN_TYPE_E; + +/**************************struct define***************************************/ + +/** @struct MEDIA_VIDEO_FRAME_T + * @brief video frame information + */ +typedef struct { + TUYA_CODEC_ID_E video_codec; ///< video codec + MEIDA_RECV_VIDEO_FRAME_TYPE_E video_frame_type; ///< video frame type + UINT_T width; ///< video width + UINT_T height; ///< video height + UINT_T fps; ///< video fps + BYTE_T *p_video_buf; ///< video data buf + UINT_T buf_len; ///< video buf len + UINT64_T pts; ///< video timestamp in us + UINT64_T timestamp; ///< video timestamp in ms + VOID *p_reserved; ///< reserved +} MEDIA_VIDEO_FRAME_T; + +/** @struct MEDIA_AUDIO_FRAME_T + * @brief audio frame information + */ +typedef struct { + TUYA_CODEC_ID_E audio_codec; ///< audio codec + TUYA_AUDIO_SAMPLE_E audio_sample; ///< audio sample + TUYA_AUDIO_DATABITS_E audio_databits; ///< audio databits + TUYA_AUDIO_CHANNEL_E audio_channel; ///< audio channel + BYTE_T *p_audio_buf; ///< audio data buffer + UINT_T buf_len; ///< audio buf len + UINT64_T pts; ///< audio timestamp is us + UINT64_T timestamp; ///< audio timestamp is ms + VOID *p_reserved; ///< reserved +} MEDIA_AUDIO_FRAME_T; + +/** @struct MEDIA_FILE_DATA_T + */ +typedef struct { + INT_T session_id; + TY_DATA_TRANSFER_STAT status; + CHAR_T *file_buf; + INT_T file_len; + VOID *fragment_info; + VOID *trans_info; +} MEDIA_FILE_DATA_T; + +/** @struct MEDIA_AUDIO_DECODE_INFO_T + * @brief audio decode information for talking + */ +typedef struct { + BOOL_T enable; ///< enable or not + TUYA_CODEC_ID_E audio_codec; ///< audio codec type supported + TUYA_AUDIO_SAMPLE_E audio_sample; ///< audio sample rate + TUYA_AUDIO_DATABITS_E audio_databits; ///< audio databits + TUYA_AUDIO_CHANNEL_E audio_channel; ///< audio channel number +} MEDIA_AUDIO_DECODE_INFO_T; + +/** @struct DEVICE_MEDIA_INFO_T + */ +typedef struct { + IPC_MEDIA_INFO_T av_encode_info; ///< encoder info + MEDIA_AUDIO_DECODE_INFO_T audio_decode_info; ///< decoder info + INT_T max_pic_len; ///< max picture size, in KB + INT_T decoder_cnt; ///< other decoder cnt + TUYA_DECODER_T *decoders; ///< other decoders +} DEVICE_MEDIA_INFO_T; + +/** @struct MEDIA_ALLOC_RESOURCE_T + * @brief memory resource of a stream + */ +typedef struct { + INT_T need_memory; ///< memory resource need to alloc +} MEDIA_ALLOC_RESOURCE_T; + +typedef VOID *MEDIA_USER_HANDLE; + +typedef VOID *MEDIA_STREAM_HANDLE; + +/** @brief callback to recv audio frame + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_audio_frame audio frame info + */ +typedef VOID (*MEDIA_RECV_AUDIO_CB)(IN INT_T device, IN INT_T channel, IN CONST MEDIA_AUDIO_FRAME_T *media_audio_frame); + +/** @brief callback to recv video frame + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_video_frame video frame info + */ +typedef VOID (*MEDIA_RECV_VIDEO_CB)(IN INT_T device, IN INT_T channel, IN CONST MEDIA_VIDEO_FRAME_T *media_video_frame); + +/** @brief callback to recv file + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_file_data file data info + */ +typedef VOID (*MEDIA_RECV_FILE_CB)(IN INT_T device, IN INT_T channel, IN CONST MEDIA_FILE_DATA_T *media_file_data); + +/** @brief callback to get a snapshot + * @param[in] device device number + * @param[in] channel channel number + * @param[in] pic_buf picture buffer + * @param[in] pic_len picture length + */ +typedef VOID (*MEDIA_GET_SNAPSHOT_CB)(IN INT_T device, IN INT_T channel, INOUT CHAR_T *pic_buf, INOUT INT_T *pic_len); + +/** @brief callback when media info change + * @param[in] info new media info + */ +typedef VOID (*MEDIA_INFO_CB)(IN INT_T device, IN INT_T channel, IN CONST DEVICE_MEDIA_INFO_T info); +typedef VOID (*MEDIA_CLOSE_CB)(IN INT_T device, IN INT_T channel); + +/** @struct TUYA_IPC_MEDIA_ADAPTER_VAR_T + * @brief media adapter paramters + */ +typedef struct { + MEDIA_GET_SNAPSHOT_CB get_snapshot_cb; ///< snapshot callback + MEDIA_RECV_VIDEO_CB on_recv_video_cb; ///< recv video callback + MEDIA_RECV_AUDIO_CB on_recv_audio_cb; ///< recv audio callback + MEDIA_RECV_FILE_CB on_recv_file_cb; ///< recv file callback + INT_T available_media_memory; ///< max memory size of P2P/webrtc(MB). 0 means default, 5MB +} TUYA_IPC_MEDIA_ADAPTER_VAR_T; + +/** @struct FRAME_FRAGMENT_T + * @brief fragme fragment info + */ +typedef struct { + MEDIA_FRAME_TYPE_E type; ///< frame type + UCHAR_T *data; ///< fragment data + UINT_T size; ///< fragment size + UINT64_T pts; ///< timestamp is us + UINT64_T timestamp; ///< timestamp is ms + UINT_T seq_no; ///< sequence number, start from 1, keep same within one frame + UCHAR_T frag_status; ///< fragment flag:0-no fragment,1-first fragment,2-middle fragment,3-last fragment + UCHAR_T frag_no; ///< fragment number, start from 0 +} FRAME_FRAGMENT_T; + +/** @struct TUYA_IPC_MEDIA_SOURCE_T + * @brief media source interface for stream operation + */ +typedef struct { + /** @brief get a frame fragment + * @param[in] handle handle returned by open_stream + * @param[in] is_retry whether to retry to get the last frame + * @param[out] p_media_frame the frame fragment + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*get_media_frame)(MEDIA_STREAM_HANDLE handle, BOOL_T is_retry, FRAME_FRAGMENT_T *p_media_frame); + + /** @brief get a video/audio frame fragment syncd by timestamp + * @param[in] v_handle video handle return by open_stream + * @param[in] a_handle audio handle return by open_stream + * @param[in] is_retry whether to retry to get the last frame + * @param[in] p_media_frame the frame fragment + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*get_av_frame) + (MEDIA_STREAM_HANDLE v_handle, MEDIA_STREAM_HANDLE a_handle, BOOL_T is_retry, FRAME_FRAGMENT_T *p_media_frame); + + /** @brief open a stream for read or write + * @param[in] device device number + * @param[in] channel channel number + * @param[in] stream stream number + * @param[in] open_type open for read or write + * @return stream handle + */ + MEDIA_STREAM_HANDLE (*open_stream) + (INT_T device, INT_T channel, IPC_STREAM_E stream, STREAM_SOURCE_OPEN_TYPE_E open_type); + + /** @brief close a stream + * @param[in] handle handle return by open_stream + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*close_stream)(MEDIA_STREAM_HANDLE handle); + + /** @brief seek backward several frames in stream + * @param[in] handle handle return by open_stream + * @param[in] frame_num frame number to backward to + * @param[in] check_overlap whether check overlap, if set to true, it will not seek to frames that have got + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*seek_frame)(MEDIA_STREAM_HANDLE handle, UINT_T frame_num, BOOL_T check_overlap); + + /** @brief sync video and audio stream + * @param[in] v_handle video handle + * @param[in] a_handle audio handle + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*sync_stream)(MEDIA_STREAM_HANDLE v_handle, MEDIA_STREAM_HANDLE a_handle); + + /** @brief seek frames in stream by timestamp + * @param[in] handle handle return by open_stream + * @param[in] frame_timestamp_ms frame time to backward to + * @param[in] check_overlap whether check overlap, if set to true, it will not seek to frames that have got + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*seek_frame_by_time)(MEDIA_STREAM_HANDLE handle, UINT64_T frame_timestamp_ms, BOOL_T check_overlap); + +} TUYA_IPC_MEDIA_SOURCE_T; + +/**************************function define***************************************/ + +/** @brief media adapter module init + * @param[in] p_var init parameters + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_init(TUYA_IPC_MEDIA_ADAPTER_VAR_T *p_var); + +/** @brief alloc media resource + * @param[in] resource media source + * @param[out] user_handle user handle + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_alloc_resource(MEDIA_ALLOC_RESOURCE_T resource, MEDIA_USER_HANDLE *user_handle); + +/** @brief release media resource + * @param[in] user_handle user handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_release_resource(MEDIA_USER_HANDLE user_handle); + +/** @brief get a snapshot + * @param[in] device device number + * @param[in] channel channel number + * @param[out] buf image buffer + * @param[out] buf_len image size + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_snapshot_get(INT_T device, INT_T channel, CHAR_T **buf, INT_T *buf_len); + +/** @brief delete a snapshot + * @param[in] buf the image buffer returned by snapshot_get + */ +VOID tuya_ipc_media_adapter_snapshot_delete(CHAR_T *buf); + +/** @brief get max picture length that sdk can handle + * @param[in] device device number + * @param[in] channel channel number + * @return max picture length + */ +INT_T tuya_ipc_media_adapter_get_max_pic_len(INT_T device, INT_T channel); + +/** @brief get max frame length that sdk can handle + * @param[in] device device number + * @param[in] channel channel number + * @param[in] stream stream number + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_get_max_frame(INT_T device, INT_T channel, IPC_STREAM_E stream); + +/** @brief get media info + * @param[in] device device number + * @param[in] channel channel number + * @param[out] media_info media info + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_get_media_info(INT_T device, INT_T channel, DEVICE_MEDIA_INFO_T *media_info); + +/** @brief set media info + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_info media info + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_set_media_info(INT_T device, INT_T channel, DEVICE_MEDIA_INFO_T media_info); + +/** @brief attach to a media info, when the media change, it will receive a callback + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_cb callback + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_attach_media_info(INT_T device, INT_T channel, MEDIA_INFO_CB media_cb, + MEDIA_CLOSE_CB media_close_cb); + +/** @brief detach from a media info + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_cb callback + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_detach_media_info(INT_T device, INT_T channel, MEDIA_INFO_CB media_cb, + MEDIA_CLOSE_CB media_close_cb); + +/** @brief start speaker + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_speak_start(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief stop speaker + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_speak_stop(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief start display + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_display_start(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief stop display + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_display_stop(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief output audio frame to application + * @param[in] device device number + * @param[in] channel channel number + * @param[in] audio audio frame + * @param[in] user_handle handle returned by alloc_resource + */ +VOID tuya_ipc_media_adapter_audio_output(INT_T device, INT_T channel, MEDIA_AUDIO_FRAME_T *audio, + MEDIA_USER_HANDLE user_handle); + +/** @brief output video frame to application + * @param[in] device device number + * @param[in] channel channel number + * @param[in] video video frame + * @param[in] user_handle handle returned by alloc_resource + */ +VOID tuya_ipc_media_adapter_video_output(INT_T device, INT_T channel, MEDIA_VIDEO_FRAME_T *video, + MEDIA_USER_HANDLE user_handle); + +/** @brief output file data to application + * @param[in] device device number + * @param[in] channel channel number + * @param[in] file file data + * @param[in] user_handle handle returned by alloc_resource + */ +VOID tuya_ipc_media_adapter_file_output(INT_T device, INT_T channel, MEDIA_FILE_DATA_T *file, + MEDIA_USER_HANDLE user_handle); + +/** @brief get media source + * @return media source + */ +TUYA_IPC_MEDIA_SOURCE_T *tuya_ipc_media_adapter_get_media_source(); + +/** @brief register media source + * @param[in] media_source + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_register_media_source(TUYA_IPC_MEDIA_SOURCE_T *media_frame_instance); + +/** @brief check if output device busy + * @param[in] device device number + * @param[in] channel channel number + * @return TRUE or FALSE + */ +BOOL_T tuya_ipc_media_adapter_output_device_is_busy(INT_T device, INT_T channel); + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_SKILL_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h new file mode 100755 index 000000000..3e30338d1 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h @@ -0,0 +1,120 @@ +#ifndef _TUYA_IPC_MEDIA_STREAM_H_ +#define _TUYA_IPC_MEDIA_STREAM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_media_adapter.h" +#include "tuya_ipc_media_stream_event.h" +#include "tuya_ipc_p2p.h" +//#include "tuya_imm_service_log.h" + +/** @struct MEDIA_STREAM_VAR_T + * @brief media stream parameter + */ +typedef struct { + MEDIA_STREAM_EVENT_CB on_event_cb; /** p2p event callback function */ + INT_T max_client_num; /** max client number supported in p2p and webrtc streaming */ + TRANS_DEFAULT_QUALITY_E def_live_mode; /** for multi-streaming ipc, the default quality for live preview */ + BOOL_T low_power; /** whether is lowpower device */ + UINT_T recv_buffer_size; /*recv app data size. if recv_buffer_size = 0,default = 16*1024*/ +} MEDIA_STREAM_VAR_T; + +/** @brief media stream module init + * @param[in] stream_var initialize parameter + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_stream_init(MEDIA_STREAM_VAR_T *stream_var); + +/** @brief get number of clients currently streaming + * @return number of clients + */ +INT_T tuya_ipc_get_client_online_num(); + +/** @brief pause media streaming + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_service_pause(); + +/** @brief resume media streaming + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_service_resume(); + +/** @brief uninitialize stream module + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_stream_deinit(); + +/** + * @brief send playback video frame to APP via P2P channel + * + * @param[in] client:client cliend id + * @param[in] p_video_frame:p_video_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_video_frame(IN CONST UINT_T client, + IN CONST MEDIA_VIDEO_FRAME_T *p_video_frame); +OPERATE_RET +tuya_ipc_media_playback_send_video_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_video_frame); + +/** + * @brief send playback audio frame to APP via P2P channel + * + * @param[in] client:client cliend id + * @param[in] p_audio_frame:p_audio_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_audio_frame(IN CONST UINT_T client, + IN CONST MEDIA_AUDIO_FRAME_T *p_audio_frame); +OPERATE_RET +tuya_ipc_media_playback_send_audio_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_audio_frame); + +/** + * @brief notify client(APP) playback fragment is finished, send frag info to app + * + * @param[in] client:client cliend id + * @param[in] fgmt:playback time + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_fragment_end(IN CONST UINT_T client, IN CONST PLAYBACK_TIME_S *fgmt); + +/** + * @brief notify client(APP) playback data is finished, no more data outgoing + * + * @param[in] client:client cliend id + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_finish(IN CONST UINT_T client); + +/** + * @brief put log to tuya cloud service. + * + * @param level + * @param log + * @param log_len + * @return VOID + */ +// VOID tuya_imm_media_online_log_print(IMM_SERVICE_LOG_LV_T level, CHAR_T *p, CHAR_T* pFmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif /*__TUYA_IPC_MEDIA_STREAM_H__*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h new file mode 100755 index 000000000..116e04a86 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h @@ -0,0 +1,38 @@ +#ifndef __TUYA_IPC_MEDIA_STREAM_COMMON_H__ +#define __TUYA_IPC_MEDIA_STREAM_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" + +#ifdef IPC_CHANNEL_NUM +#define P2P_IPC_CHAN_NUM IPC_CHANNEL_NUM +#else +#define P2P_IPC_CHAN_NUM 1 +#endif + +#define TUYA_CMD_CHANNEL (0) // Signaling channel, signal mode refer to P2P_CMD_E +#define TUYA_VDATA_CHANNEL (1) // Video data channel +#define TUYA_ADATA_CHANNEL (2) // Audio data channel +#define TUYA_TRANS_CHANNEL (3) // Video download +#define TUYA_TRANS_CHANNEL4 (4) // Deprecated, occupied by TianShiTong on APP side +#define TUYA_TRANS_CHANNEL5 (5) // Album function download + +#if defined(ENABLE_IPC_P2P) +#define TUYA_CHANNEL_MAX (6) // NOTICE: Can be reduced when memory is insufficient +#elif defined(ENABLE_XVR_P2P) +#define TUYA_CHANNEL_MAX (200) // NOTICE: Can be reduced when memory is insufficient +#else +#define TUYA_CHANNEL_MAX (6) // NOTICE: Can be reduced when memory is insufficient +#endif + +#define P2P_WR_BF_MAX_SIZE (128 * 1024) // Maximum size of read/write buffer, no data sent when exceeding threshold +#define P2P_SEND_REDUNDANCE_LEN (1250 + 100) // Redundant length + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_MEDIA_STREAM_COMMON_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h new file mode 100755 index 000000000..e90d3c71b --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h @@ -0,0 +1,787 @@ +#ifndef _TUYA_IPC_MEDIA_STREAM_EVENT_H_ +#define _TUYA_IPC_MEDIA_STREAM_EVENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" + +/**************************enum define***************************************/ + +// Media stream related events +typedef enum { + // Enum 0~29 reserved for hardware related events + MEDIA_STREAM_NULL = 0, + MEDIA_STREAM_SPEAKER_START = 1, + MEDIA_STREAM_SPEAKER_STOP = 2, + MEDIA_STREAM_DISPLAY_START = 3, + MEDIA_STREAM_DISPLAY_STOP = 4, + + MEDIA_STREAM_LIVE_VIDEO_START = 30, + MEDIA_STREAM_LIVE_VIDEO_STOP = 31, + MEDIA_STREAM_LIVE_AUDIO_START = 32, + MEDIA_STREAM_LIVE_AUDIO_STOP = 33, + MEDIA_STREAM_LIVE_VIDEO_CLARITY_SET = 34, + MEDIA_STREAM_LIVE_VIDEO_CLARITY_QUERY = 35, /* query clarity informations*/ + MEDIA_STREAM_LIVE_LOAD_ADJUST = 36, + MEDIA_STREAM_PLAYBACK_LOAD_ADJUST = 37, + MEDIA_STREAM_PLAYBACK_QUERY_MONTH_SIMPLIFY = 38, /* query storage info of month */ + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS = 39, /* query storage info of day */ + MEDIA_STREAM_PLAYBACK_START_TS = 40, /* start playback */ + MEDIA_STREAM_PLAYBACK_PAUSE = 41, /* pause playback */ + MEDIA_STREAM_PLAYBACK_RESUME = 42, /* resume playback */ + MEDIA_STREAM_PLAYBACK_MUTE = 43, /* mute playback */ + MEDIA_STREAM_PLAYBACK_UNMUTE = 44, /* unmute playback */ + MEDIA_STREAM_PLAYBACK_STOP = 45, /* stop playback */ + MEDIA_STREAM_PLAYBACK_SET_SPEED = 46, /*set playback speed*/ + MEDIA_STREAM_ABILITY_QUERY = 47, /* query the alibity of audion video strraming */ + MEDIA_STREAM_DOWNLOAD_START = 48, /* start to download */ + MEDIA_STREAM_DOWNLOAD_STOP = 49, /* abondoned */ + MEDIA_STREAM_DOWNLOAD_PAUSE = 50, + MEDIA_STREAM_DOWNLOAD_RESUME = 51, + MEDIA_STREAM_DOWNLOAD_CANCLE = 52, + + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_WITH_ENCRYPT = 53, /* query storage info of day */ + MEDIA_STREAM_DOWNLOAD_START_WITH_ENCRYPT = 54, + + /*Related to interconnection*/ + MEDIA_STREAM_LIVE_VIDEO_SEND_START = 60, // Remote requests to pull video stream + MEDIA_STREAM_LIVE_VIDEO_SEND_STOP = 61, // Remote requests to stop pulling video stream + MEDIA_STREAM_LIVE_AUDIO_SEND_START = 62, // Remote requests to pull audio stream + MEDIA_STREAM_LIVE_AUDIO_SEND_STOP = 63, // Remote requests to stop pulling audio stream + MEDIA_STREAM_LIVE_VIDEO_SEND_PAUSE = 64, // Remote pauses video sending + MEDIA_STREAM_LIVE_VIDEO_SEND_RESUME = 65, // Remote resumes video sending + + MEDIA_STREAM_STREAMING_VIDEO_START = 100, + MEDIA_STREAM_STREAMING_VIDEO_STOP = 101, + + MEDIA_STREAM_DOWNLOAD_IMAGE = 201, /* download image */ + MEDIA_STREAM_PLAYBACK_DELETE = 202, /* delete video */ + MEDIA_STREAM_ALBUM_QUERY = 203, + MEDIA_STREAM_ALBUM_DOWNLOAD_START = 204, + MEDIA_STREAM_ALBUM_DOWNLOAD_CANCEL = 205, + MEDIA_STREAM_ALBUM_DELETE = 206, + MEDIA_STREAM_ALBUM_PLAY_CTRL = 207, + + // XVR related + MEDIA_STREAM_VIDEO_START_GW = 300, /**< Live video start, parameter is C2C_TRANS_CTRL_VIDEO_START*/ + MEDIA_STREAM_VIDEO_STOP_GW, /**< Live video stop, parameter is C2C_TRANS_CTRL_VIDEO_STOP*/ + MEDIA_STREAM_AUDIO_START_GW, /**< Live audio start, parameter is C2C_TRANS_CTRL_AUDIO_START*/ + MEDIA_STREAM_AUDIO_STOP_GW, /**< Live audio stop, parameter is C2C_TRANS_CTRL_AUDIO_STOP*/ + MEDIA_STREAM_VIDEO_CLARITY_SET_GW, /**< Set video live clarity, parameter is*/ + MEDIA_STREAM_VIDEO_CLARITY_QUERY_GW, /**< Query video live clarity, parameter is*/ + MEDIA_STREAM_LOAD_ADJUST_GW, /**< Live load change, parameter is*/ + MEDIA_STREAM_PLAYBACK_LOAD_ADJUST_GW, /**< Start playback, parameter is*/ + MEDIA_STREAM_PLAYBACK_QUERY_MONTH_SIMPLIFY_GW, /* Query local video info by month, parameter is */ + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_GW, /* Query local video info by day, parameter is */ + + MEDIA_STREAM_PLAYBACK_START_TS_GW, /* Start playback video, parameter is */ + MEDIA_STREAM_PLAYBACK_PAUSE_GW, /**< Pause playback video, parameter is */ + MEDIA_STREAM_PLAYBACK_RESUME_GW, /**< Resume playback video, parameter is */ + MEDIA_STREAM_PLAYBACK_MUTE_GW, /**< Mute, parameter is */ + MEDIA_STREAM_PLAYBACK_UNMUTE_GW, /**< Unmute, parameter is */ + MEDIA_STREAM_PLAYBACK_STOP_GW, /**< Stop playback video, parameter is */ + + MEDIA_STREAM_PLAYBACK_SPEED_GW, /**< Set playback speed, parameter is */ + MEDIA_STREAM_DOWNLOAD_START_GW, /**< Download start*/ + MEDIA_STREAM_DOWNLOAD_PAUSE_GW, /**< Download pause */ + MEDIA_STREAM_DOWNLOAD_RESUME_GW, /**< Download resume*/ + MEDIA_STREAM_DOWNLOAD_CANCLE_GW, /**< Download stop*/ + + MEDIA_STREAM_SPEAKER_START_GW, /**< Start intercom, no parameters */ + MEDIA_STREAM_SPEAKER_STOP_GW, /**< Stop intercom, no parameters */ + MEDIA_STREAM_ABILITY_QUERY_GW, /**< Ability query C2C_MEDIA_STREAM_QUERY_FIXED_ABI_REQ*/ + MEDIA_STREAM_CONN_START_GW, /**< Start connection */ + MEDIA_STREAM_PLAYBACK_DELETE_GW, /* delete video */ + + // for page mode play back enum + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_PAGE_MODE, /* query storage info of day with page id*/ + MEDIA_STREAM_PLAYBACK_QUERY_EVENT_DAY_TS_PAGE_MODE, /* query storage evnet info of day with page id */ + +} MEDIA_STREAM_EVENT_E; + +typedef enum { + TRANS_EVENT_SUCCESS = 0, /* Return success */ + TRANS_EVENT_SPEAKER_ISUSED = 10, /* Speaker already in use, different TRANSFER_SOURCE_TYPE_E */ + TRANS_EVENT_SPEAKER_REPSTART = 11, /* Speaker repeatedly started, same TRANSFER_SOURCE_TYPE_E */ + TRANS_EVENT_SPEAKER_STOPFAILED = 12, /* Speaker stop failed*/ + TRANS_EVENT_SPEAKER_INVALID = 99 +} TRANSFER_EVENT_RETURN_E; + +typedef enum { + TRANSFER_SOURCE_TYPE_P2P = 1, + TRANSFER_SOURCE_TYPE_WEBRTC = 2, + TRANSFER_SOURCE_TYPE_STREAMER = 3, +} TRANSFER_SOURCE_TYPE_E; + +/** + * \brief P2P online status + * \enum TRANSFER_ONLINE_E + */ +typedef enum { + TY_DEVICE_OFFLINE, + TY_DEVICE_ONLINE, +} TRANSFER_ONLINE_E; + +typedef enum { + TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_VIDEO = 0x1, // if support video + TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_SPEAKER = 0x2, // if support speaker + TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_MIC = 0x4, // is support MIC +} TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE; + +// request, response +typedef struct tagC2CCmdQueryFixedAbility { + unsigned int channel; + unsigned int ability_mask; // ability is assigned by bit +} C2C_TRANS_QUERY_FIXED_ABI_REQ, C2C_TRANS_QUERY_FIXED_ABI_RESP; + +typedef enum { + TY_VIDEO_CLARITY_STANDARD = 0, + TY_VIDEO_CLARITY_HIGH, + TY_VIDEO_CLARITY_THIRD, + TY_VIDEO_CLARITY_FOURTH, + TY_VIDEO_CLARITY_MAX +} TRANSFER_VIDEO_CLARITY_TYPE_E; + +/**************************struct define***************************************/ +typedef INT_T (*MEDIA_STREAM_EVENT_CB)(IN CONST INT_T device, IN CONST INT_T channel, + IN CONST MEDIA_STREAM_EVENT_E event, IN PVOID_T args); + +typedef struct { + TRANSFER_VIDEO_CLARITY_TYPE_E clarity; + VOID *pReserved; +} C2C_TRANS_LIVE_CLARITY_PARAM_S; + +typedef struct tagC2C_TRANS_CTRL_LIVE_VIDEO { + unsigned int channel; + unsigned int type; // Stream type +} C2C_TRANS_CTRL_VIDEO_START, C2C_TRANS_CTRL_VIDEO_STOP; + +typedef struct tagC2C_TRANS_CTRL_LIVE_AUDIO { + unsigned int channel; +} C2C_TRANS_CTRL_AUDIO_START, C2C_TRANS_CTRL_AUDIO_STOP; + +typedef struct { + UINT_T start_timestamp; /* start timestamp in second of playback */ + UINT_T end_timestamp; /* end timestamp in second of playback */ +} PLAYBACK_TIME_S; + +typedef struct tagPLAY_BACK_ALARM_FRAGMENT { + unsigned short video_type; ///< 0: Regular recording, 1: AOV recording + unsigned short type; ///< event type + PLAYBACK_TIME_S time_sect; +} PLAY_BACK_ALARM_FRAGMENT; + +typedef struct { + unsigned int file_count; // file count of the day + PLAY_BACK_ALARM_FRAGMENT file_arr[0]; // play back file array +} PLAY_BACK_ALARM_INFO_ARR; + +#pragma pack(4) +typedef struct tagPLAY_BACK_FILE_INFOS_WITH_ENCRYPT { + unsigned short video_type; ///< 0: Regular recording, 1: AOV recording + unsigned short type; ///< event type + char uuid[32]; + PLAYBACK_TIME_S time_sect; + int encrypt; + unsigned char key_hash[16]; +} PLAY_BACK_FILE_INFOS_WITH_ENCRYPT; +#pragma pack() + +typedef struct { + unsigned int file_count; // file count of the day + PLAY_BACK_FILE_INFOS_WITH_ENCRYPT file_arr[0]; // play back file array +} PLAY_BACK_ALARM_INFO_WITH_ENCRYPT_ARR; + +typedef struct { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; +} C2C_TRANS_QUERY_PB_DAY_INNER_REQ; + +typedef struct { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + PLAY_BACK_ALARM_INFO_ARR *alarm_arr; + unsigned int ipcChan; +} C2C_TRANS_QUERY_PB_DAY_RESP; + +typedef struct { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + unsigned int ipcChan; //??? todo position + int allow_encrypt; + PLAY_BACK_ALARM_INFO_WITH_ENCRYPT_ARR *alarm_arr; +} C2C_TRANS_QUERY_PB_DAY_WITH_ENCRYPT_RESP; + +// Playback data deletion by day request +typedef struct tagC2C_TRANS_CTRL_PB_DELDATA_BYDAY_REQ { + unsigned int channel; + unsigned int year; // Year to delete + unsigned int month; // Month to delete + unsigned int day; // Day to delete +} C2C_TRANS_CTRL_PB_DELDATA_BYDAY_REQ; + +typedef struct tagC2C_TRANS_CTRL_PB_DOWNLOAD_IMAGE_S { + unsigned int channel; + PLAYBACK_TIME_S time_sect; // Start download time point + char reserved[32]; + int result; // Result, can extend error code TY_C2C_CMD_IO_CTRL_STATUS_CODE + int image_fileLength; // File length followed by h file content + unsigned char *pBuffer; // File content +} C2C_TRANS_CTRL_PB_DOWNLOAD_IMAGE_PARAM_S; + +// query playback data by month +typedef struct tagC2CCmdQueryPlaybackInfoByMonth { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; // list days that have playback data. Use each bit for one day. For example day=26496=0110 0111 + // 1000 0000 means day 7/8/9/19/13/14 have playback data. + unsigned int ipcChan; +} C2C_TRANS_QUERY_PB_MONTH_REQ, C2C_TRANS_QUERY_PB_MONTH_RESP; + +typedef struct tagC2CCmdQueryPlaybackInfoByMonthInner { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; // list days that have playback data. Use each bit for one day. For example day=26496=0110 0111 + // 1000 0000 means day 7/8/9/19/13/14 have playback data. +} C2C_TRANS_QUERY_PB_MONTH_INNER_REQ, C2C_TRANS_QUERY_PB_MONTH_INNER_RESP; + +typedef struct tagC2C_TRANS_CTRL_PB_START { + unsigned int channel; + PLAYBACK_TIME_S time_sect; + UINT_T playTime; /* the actual playback time, in second */ + TRANSFER_SOURCE_TYPE_E type; + unsigned int reqId; /* request ID, need by send frame api */ + int allow_encrypt; +} C2C_TRANS_CTRL_PB_START; + +typedef struct tagC2C_TRANS_CTRL_PB_STOP { + unsigned int channel; +} C2C_TRANS_CTRL_PB_STOP; + +typedef struct tagC2C_TRANS_CTRL_PB_PAUSE { + unsigned int channel; +} C2C_TRANS_CTRL_PB_PAUSE; + +typedef struct tagC2C_TRANS_CTRL_PB_RESUME { + unsigned int channel; + unsigned int reqId; +} C2C_TRANS_CTRL_PB_RESUME; + +typedef struct tagC2C_TRANS_CTRL_PB_MUTE { + unsigned int channel; +} C2C_TRANS_CTRL_PB_MUTE; + +typedef struct tagC2C_TRANS_CTRL_PB_UNMUTE { + unsigned int channel; + unsigned int reqId; +} C2C_TRANS_CTRL_PB_UNMUTE; + +typedef struct tagC2C_TRANS_CTRL_PB_SET_SPEED { + unsigned int channel; + unsigned int speed; + unsigned int reqId; +} C2C_TRANS_CTRL_PB_SET_SPEED; + +/** + * \brief network load change callback struct + * \note NOT supported now + */ +typedef struct { + INT_T client_index; + INT_T curr_load_level; /**< 0:best 5:worst */ + INT_T new_load_level; /**< 0:best 5:worst */ + + VOID *pReserved; +} C2C_TRANS_PB_LOAD_PARAM_S; + +typedef struct { + INT_T client_index; + INT_T curr_load_level; /**< 0:best 5:worst */ + INT_T new_load_level; /**< 0:best 5:worst */ + + VOID *pReserved; +} C2C_TRANS_LIVE_LOAD_PARAM_S; + +typedef struct tagC2C_TRANS_CTRL_DL_START { + unsigned int channel; + unsigned int fileNum; + unsigned int downloadStartTime; + unsigned int downloadEndTime; + PLAYBACK_TIME_S *pFileInfo; +} C2C_TRANS_CTRL_DL_START; + +typedef struct tagC2C_TRANS_CTRL_DL_ENCRYPT_START { + unsigned int channel; + unsigned int fileNum; + unsigned int downloadStartTime; + unsigned int downloadEndTime; + int allow_encrypt; // Whether to allow encrypted data + int reqId; // request ID, need by send frame api + PLAYBACK_TIME_S *pFileInfo; +} C2C_TRANS_CTRL_DL_ENCRYPT_START; + +typedef struct tagC2C_TRANS_CTRL_DL_STOP { + unsigned int channel; +} C2C_TRANS_CTRL_DL_STOP, C2C_TRANS_CTRL_DL_PAUSE, C2C_TRANS_CTRL_DL_RESUME, C2C_TRANS_CTRL_DL_CANCLE; + +typedef enum { + TUYA_DOWNLOAD_VIDEO = 0, + TUYA_DOWNLOAD_ALBUM, + TUYA_DOWNLOAD_VIDEO_ALLOW_ENCRYPT, + TUYA_DOWNLOAD_MAX, +} TUYA_DOWNLOAD_DATA_TYPE; + +typedef struct { + INT_T video_codec; + UINT_T frame_rate; + UINT_T video_width; + UINT_T video_height; +} TRANSFER_IPC_VIDEO_INFO_S; + +typedef struct { + INT_T audio_codec; + INT_T audio_sample; // TUYA_AUDIO_SAMPLE_E + INT_T audio_databits; // TUYA_AUDIO_DATABITS_E + INT_T audio_channel; // TUYA_AUDIO_CHANNEL_E +} TRANSFER_IPC_AUDIO_INFO_S; + +typedef struct { + INT_T encrypt; // Whether to encrypt + INT_T security_level; // Security level + CHAR_T uuid[32]; // Device UUID + BYTE_T iv[16]; // Encryption vector +} TRANSFER_MEDIA_ENCRYPT_INFO_T; + +typedef struct { + INT_T frame_type; // MEDIA_FRAME_TYPE_E + BYTE_T *p_buf; + UINT_T size; + UINT64_T pts; + UINT64_T timestamp; + union { + TRANSFER_IPC_VIDEO_INFO_S video; + TRANSFER_IPC_AUDIO_INFO_S audio; + } media; + + TRANSFER_MEDIA_ENCRYPT_INFO_T encrypt_info; +} TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T; // Used for playback + +typedef struct { + INT_T type; // MEDIA_FRAME_TYPE_E + UINT_T size; + UINT64_T timestamp; + UINT64_T pts; + union { + TRANSFER_IPC_VIDEO_INFO_S video; + TRANSFER_IPC_AUDIO_INFO_S audio; + } media; + TRANSFER_MEDIA_ENCRYPT_INFO_T encrypt_info; +} TUYA_DOWNLOAD_FRAME_HEAD_ENCRYPT_T; + +typedef struct { + INT_T type; // See MEDIA_FRAME_TYPE_E + UINT_T size; + UINT64_T timestamp; + UINT64_T pts; + union { + TRANSFER_IPC_VIDEO_INFO_S video; + TRANSFER_IPC_AUDIO_INFO_S audio; + } media; + TRANSFER_MEDIA_ENCRYPT_INFO_T encrypt_info; + BYTE_T *p_buf; // frame data +} TUYA_ALBUM_PLAY_FRAME_T; + +/***********************************album protocol ****************************************/ +#define TUYA_ALBUM_APP_FILE_NAME_MAX_LEN (48) +#define IPC_SWEEPER_ROBOT "ipc_sweeper_robot" +typedef struct { + unsigned int channel; // Currently not needed, reserved + char albumName[48]; + int fileLen; + void *pIndexFile; +} C2C_QUERY_ALBUM_REQ; // Query request header +typedef struct tagC2C_ALBUM_INDEX_ITEM { + int idx; // Provided by device and guaranteed uniqueness + char valid; // 0 invalid, 1 valid + char channel; // 0 1 Channel number + char type; // 0 Reserved, 1 pic, 2 mp4, 3 panoramic image (folder), 4 binary file, 5 stream file + char dir; // 0 file 1 dir + char filename[48]; // 123456789_1.mp4 123456789_1.jpg xxx.xxx + int createTime; // File creation time + short duration; // Video file duration + char reserved[18]; +} C2C_ALBUM_INDEX_ITEM; // Index Item +typedef struct { + unsigned int crc; + int version; // Album function version (>2 supports online file playback) + char magic[16]; + unsigned long long min_idx; + unsigned long long max_idx; + char reserved[512 - 44]; + int itemCount; // include invalid items + C2C_ALBUM_INDEX_ITEM itemArr[0]; +} C2C_ALBUM_INDEX_HEAD; // Query return: 520 = 8 + 512, index file header + item + +typedef struct { + unsigned int channel; // Currently not needed for business, reserved + int result; // Query return result + char reserved[512 - 4]; // Reserved, total 512 + int itemCount; // include invalid items + C2C_ALBUM_INDEX_ITEM itemArr[0]; +} C2C_CMD_IO_CTRL_ALBUM_QUERY_RESP; // Query return: 520 = 8 + 512, index file header + item + +typedef struct tagC2C_CMD_IO_CTRL_ALBUM_fileInfo { + char filename[48]; // File name, without absolute path +} C2C_CMD_IO_CTRL_ALBUM_fileInfo; +typedef struct tagC2C_CMD_IO_CTRL_ALBUM_DOWNLOAD_START { + unsigned int channel; // Currently unused, reserved + int operation; // See TY_CMD_IO_CTRL_DOWNLOAD_OP + char albumName[48]; + int thumbnail; // 0 Original image, 1 Thumbnail + int fileTotalCnt; // max 50 + C2C_CMD_IO_CTRL_ALBUM_fileInfo pFileInfoArr[0]; +} C2C_CMD_IO_CTRL_ALBUM_DOWNLOAD_START; +typedef struct tagC2C_ALBUM_DOWNLOAD_CANCEL { + unsigned int channel; // Currently unused, reserved + char albumName[48]; +} C2C_ALBUM_DOWNLOAD_CANCEL; + +typedef struct tagC2C_CMD_IO_CTRL_ALBUM_DELETE { + unsigned int channel; + char albumName[48]; + int fileNum; // -1 All, others: number of files + char res[64]; + C2C_CMD_IO_CTRL_ALBUM_fileInfo pFileInfoArr[0]; +} C2C_CMD_IO_CTRL_ALBUM_DELETE; // Delete files + +typedef struct { + int reqId; + int fileIndex; // start from 0 + int fileCnt; // max 50 + char fileName[48]; // File name + int packageSize; // Actual data length of current file segment + int fileSize; // File size + int fileEnd; // File end flag, last segment 10KB +} C2C_DOWNLOAD_ALBUM_HEAD; // Download data header + +typedef struct { + unsigned int channel; + int result; // See TY_C2C_CMD_IO_CTRL_STATUS_CODE_E + int operation; // See TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E, after online file playback ends it becomes + // TY_CMD_IO_CTRL_ALBUM_PLAY_OVER +} C2C_CMD_IO_CTRL_ALBUM_PLAY_RESULT_RESP_T; + +typedef enum { + TY_CMD_IO_CTRL_ALBUM_PLAY_START = 0, // Start playback + TY_CMD_IO_CTRL_ALBUM_PLAY_STOP, // Stop + TY_CMD_IO_CTRL_ALBUM_PLAY_PAUSE, // Pause + TY_CMD_IO_CTRL_ALBUM_PLAY_RESUME, // Resume + TY_CMD_IO_CTRL_ALBUM_PLAY_CANCEL, // Cancel + TY_CMD_IO_CTRL_ALBUM_PLAY_OVER, // Playback ended, device SDK actively sends +} TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E; + +typedef struct { + unsigned int channel; // Currently unused, reserved + int operation; // See TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E + int thumbnail; // 0 Original image, 1 Thumbnail + unsigned int start_time; // Start playback time, unit: s + char album_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // Album name + char file_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // File name to play, without absolute path +} C2C_CMD_IO_CTRL_ALBUM_PLAY_CTRL_REQ_T; + +typedef struct { + unsigned int channel; // Currently unused, reserved (multi-camera channel) + int user_idx; + int req_id; + int operation; // See TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E + int thumbnail; // 0 Original image, 1 Thumbnail + unsigned int start_time; // Start playback time, unit: s + char album_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // Album name + char file_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // File name to play, without absolute path +} C2C_CMD_IO_CTRL_ALBUM_PLAY_CTRL_T; + +typedef enum { + E_FILE_TYPE_2_APP_PANORAMA = 1, // Panoramic image +} FILE_TYPE_2_APP_E; +typedef struct { + FILE_TYPE_2_APP_E fileType; + int param; // For panoramic images, total number of sub-images +} TUYA_IPC_BRIEF_FILE_INFO_4_APP; + +/** + * \fn tuya_ipc_start_send_file_to_app + * \brief start send file to app by p2p + * \param[in] strBriefInfo: brief file infomation + * \return handle , >=0 valid, -1 err + */ +OPERATE_RET tuya_ipc_start_send_file_to_app(IN CONST TUYA_IPC_BRIEF_FILE_INFO_4_APP *pStrBriefInfo); + +/** + * \fn tuya_ipc_stop_send_file_to_app + * \brief stop send file to app by p2p + * \param[in] handle + * \return ret + */ +OPERATE_RET tuya_ipc_stop_send_file_to_app(IN CONST INT_T handle); + +typedef struct { + CHAR_T *fileName; // Maximum 48 bytes, if null, use SDK internal naming + INT_T len; + CHAR_T *buff; +} TUYA_IPC_FILE_INFO_4_APP; +/** + * \fn tuya_ipc_send_file_to_app + * \brief start send file to app by p2p + * \param[in] handle: handle + * \param[in] strfileInfo: file infomation + * \param[in] timeOut_s: suggest 30s, 0 no_block (current not support), + * \return ret + */ +OPERATE_RET tuya_ipc_send_file_to_app(IN CONST INT_T handle, IN CONST TUYA_IPC_FILE_INFO_4_APP *pStrfileInfo, + IN CONST INT_T timeOut_s); + +typedef enum { + SWEEPER_ALBUM_FILE_TYPE_MIN = 0, + SWEEPER_ALBUM_FILE_MAP = SWEEPER_ALBUM_FILE_TYPE_MIN, // map file + SWEEPER_ALBUM_FILE_CLEAN_PATH = 1, + SWEEPER_ALBUM_FILE_NAVPATH = 2, + SWEEPER_ALBUM_FILE_TYPE_MAX = SWEEPER_ALBUM_FILE_NAVPATH, + + SWEEPER_ALBUM_STREAM_TYPE_MIN = 3, + SWEEPER_ALBUM_STREAM_MAP = + SWEEPER_ALBUM_STREAM_TYPE_MIN, // map stream , devcie should send map file to app continue + SWEEPER_ALBUM_STREAM_CLEAN_PATH = 4, + SWEEPER_ALBUM_STREAM_NAVPATH = 5, + SWEEPER_ALBUM_STREAM_TYPE_MAX = SWEEPER_ALBUM_STREAM_NAVPATH, + + SWEEPER_ALBUM_FILE_ALL_TYPE_MAX = SWEEPER_ALBUM_STREAM_TYPE_MAX, // Maximum value 5 + SWEEPER_ALBUM_FILE_ALL_TYPE_COUNT, // Count 6 +} SWEEPER_ALBUM_FILE_TYPE_E; + +typedef enum { + SWEEPER_TRANS_NULL, + SWEEPER_TRANS_FILE, // File transfer + SWEEPER_TRANS_STREAM, // File stream transfer +} SWEEPER_TRANS_MODE_E; + +// File transfer status +typedef enum { + TY_DATA_TRANSFER_IDLE, + TY_DATA_TRANSFER_START, + TY_DATA_TRANSFER_PROCESS, + TY_DATA_TRANSFER_END, + TY_DATA_TRANSFER_ONCE, + TY_DATA_TRANSFER_CANCEL, + TY_DATA_TRANSFER_MAX +} TY_DATA_TRANSFER_STAT; + +/***********************************album protocol end ****************************************/ + +/***********************************xvr protocol start ****************************************/ +typedef struct { + unsigned int channel; + unsigned int idx; + char subdid[64]; + unsigned int year; // Year to query + unsigned int month; // Month to query + unsigned int day; // Day to query +} C2C_TRANS_QUERY_GW_PB_DAY_REQ; + +typedef struct { + unsigned int channel; + unsigned int idx; // Session index + unsigned int map_chan_index; + ; // Channel bound in a session. Users can pass through transparently. + char subdid[64]; + unsigned int year; // Year to query + unsigned int month; // Month to query + unsigned int day; // Day to query + PLAY_BACK_ALARM_INFO_ARR *alarm_arr; // Query result returned by user +} C2C_TRANS_QUERY_GW_PB_DAY_RESP; + +/** +UINT has 32 bits in total, each bit indicates whether the corresponding day has data, the rightmost bit represents day +0. For example, day = 26496 = B0110 0111 1000 0000 This indicates that days 7, 8, 9, 10, 13, 14 have playback data. + */ +// Query days with playback data by month request, response +typedef struct tagC2CCmdQueryGWPlaybackInfoByMonth { + unsigned int channel; + unsigned int idx; // Session index + unsigned int map_chan_index; // Channel bound in session. Users can pass through transparently. + char subdid[64]; + unsigned int year; // Year to query + unsigned int month; // Month to query + unsigned int day; // Day with playback data +} C2C_TRANS_QUERY_GW_PB_MONTH_REQ, C2C_TRANS_QUERY_GW_PB_MONTH_RESP; + +// request +// Playback-related operation structure +typedef struct tagC2C_TRANS_CTRL_GW_PB_START { + unsigned int channel; + unsigned int idx; + char subdid[64]; + PLAYBACK_TIME_S time_sect; + UINT_T playTime; /**< Actual playback start timestamp (in seconds) */ +} C2C_TRANS_CTRL_GW_PB_START; + +typedef struct tagC2C_TRANS_CTRL_GW_PB_STOP { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_PB_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_PB_PAUSE { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_PB_PAUSE, C2C_TRANS_CTRL_GW_PB_RESUME; + +typedef struct tagC2C_TRANS_CTRL_GW_PB_MUTE { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_PB_MUTE, C2C_TRANS_CTRL_GW_PB_UNMUTE; + +// Capability set query C2C_CMD_QUERY_FIXED_ABILITY +// request, response +typedef struct tagC2CCmdQueryGWFixedAbility { + unsigned int channel; + unsigned int idx; + char subdid[64]; + unsigned int ability_mask; // Capability result bit assignment +} C2C_TRANS_QUERY_GW_FIXED_ABI_REQ, C2C_TRANS_QUERY_GW_FIXED_ABI_RESP; + +/** + * \brief Parameter structure for requesting modification or querying clarity callback in live mode + * \struct C2C_TRANS_LIVE_CLARITY_PARAM_S + */ +typedef struct { + unsigned int channel; + unsigned int idx; + char subdid[64]; + TRANSFER_VIDEO_CLARITY_TYPE_E clarity; /**< Video clarity */ + VOID *pReserved; +} C2C_TRANS_LIVE_GW_CLARITY_PARAM_S; + +// Preview-related operation structure +typedef struct tagC2C_TRANS_CTRL_GW_LIVE_VIDEO { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_VIDEO_START, C2C_TRANS_CTRL_GW_VIDEO_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_LIVE_AUDIO { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_AUDIO_START, C2C_TRANS_CTRL_GW_AUDIO_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_SPEAKER { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_SPEAKER_START, C2C_TRANS_CTRL_GW_SPEAKER_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_DEV_CONN { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_DEV_CONN; + +typedef struct tagC2C_TRANS_CTRL_PB_SET_SPEED_GW { + char devid[64]; + unsigned int channel; + unsigned int speed; +} C2C_TRANS_CTRL_PB_SET_SPEED_GW; + +// Playback data deletion by day request +typedef struct tagC2C_TRANS_CTRL_PB_DELDATA_BYDAY_GW_REQ { + char subdid[64]; + unsigned int channel; + unsigned int year; // Year to delete + unsigned int month; // Month to delete + unsigned int day; // Day to delete +} C2C_TRANS_CTRL_PB_DELDATA_BYDAY_GW_REQ; + +typedef struct tagC2C_CMD_PROTOCOL_VERSION { + unsigned int version; // High bit main version number, low 16 bits sub-version number +} C2C_CMD_PROTOCOL_VERSION; +typedef struct { + char subid[64]; + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + int page_id; +} C2C_TRANS_QUERY_PB_DAY_V2_REQ, C2C_TRRANS_QUERY_EVENT_PB_DAY_REQ; +#pragma pack(4) +typedef struct { + char subid[64]; + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + int page_id; + int total_cnt; + int page_size; + PLAY_BACK_ALARM_INFO_ARR *alarm_arr; + int idx; // Session index +} C2C_TRANS_QUERY_PB_DAY_V2_RESP; +typedef struct { + unsigned int start_timestamp; /* start timestamp in second of playback */ + unsigned int end_timestamp; + unsigned short video_type; ///< 0: Regular recording, 1: AOV recording + unsigned short type; ///< event type + char pic_id[20]; +} C2C_PB_EVENT_INFO_S; +typedef struct { + int version; + int event_cnt; + C2C_PB_EVENT_INFO_S event_info_arr[0]; +} C2C_PB_EVENT_INFO_ARR_S; +typedef struct { + char subid[64]; + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + int page_id; + int total_cnt; + int page_size; + C2C_PB_EVENT_INFO_ARR_S *event_arr; + int idx; +} C2C_TRANS_QUERY_EVENT_PB_DAY_RESP; +#pragma pack() +typedef struct tagC2C_TRANS_CTRL_GW_DL_STOP { + char devid[64]; + unsigned int channel; +} C2C_TRANS_CTRL_GW_DL_STOP, C2C_TRANS_CTRL_GW_DL_PAUSE, C2C_TRANS_CTRL_GW_DL_RESUME, C2C_TRANS_CTRL_GW_DL_CANCLE; +typedef struct tagC2C_TRANS_CTRL_GW_DL_START { + char devid[64]; + unsigned int channel; + unsigned int downloadStartTime; + unsigned int downloadEndTime; +} C2C_TRANS_CTRL_GW_DL_START; +/***********************************xvr protocol end ****************************************/ + +/**************************function define***************************************/ + +OPERATE_RET tuya_ipc_media_stream_register_event_cb(MEDIA_STREAM_EVENT_CB event_cb); + +OPERATE_RET tuya_ipc_media_stream_event_call(INT_T device, INT_T channel, MEDIA_STREAM_EVENT_E event, PVOID_T args); + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_MEDIA_STREAM_EVENT_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h new file mode 100755 index 000000000..2d07e64cf --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h @@ -0,0 +1,120 @@ +#ifndef __TUYA_IPC_P2P2_H__ +#define __TUYA_IPC_P2P2_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_p2p_inner.h" +#include "tuya_ipc_media_adapter.h" + +#define RTC_CLOSE_REASON_SECRET_MODE (2) +#define RTC_CLOSE_REASON_THREAD_CREATE_FAIL (3) +#define RTC_CLOSE_REASON_SESSION_FULL (4) +#define RTC_CLOSE_REASON_AUTH_FAIL (5) +#define RTC_CLOSE_REASON_WEBRTC_THREAD_FAIL (7) +#define RTC_CLOSE_REASON_ZOMBIE_SESSION (8) +#define RTC_CLOSE_REASON_USER_CLOSE (9) +#define RTC_CLOSE_REASON_P2P_EXIT (10) +#define RTC_CLOSE_REASON_BE_SECRET_MODE (11) +#define RTC_CLOSE_REASON_RECV_ERR (12) +#define RTC_CLOSE_REASON_MALLOC_ERR (14) +#define RTC_CLOSE_REASON_RESTRICT_MODE (15) + +typedef enum tagMediaFrameType { + eVideoPBFrame = 0, ///< p frame + eVideoIFrame, ///< i frame + eVideoTsFrame, ///< ts frame + eAudioFrame, ///< audio frame + eCmdFrame, ///< cmd frame + eMediaFrameTypeMax +} MEDIA_FRAME_TYPE; + +typedef struct tagMediaFrame { + MEDIA_FRAME_TYPE type; ///< frame type + UCHAR_T *data; ///< fragment data + UINT_T size; ///< fragment size + UINT64_T pts; ///< timestamp is us + UINT64_T timestamp; ///< timestamp is ms +} MEDIA_FRAME; + +typedef INT_T (*tuya_p2p_rtc_disconnect_cb_t)(); +typedef INT_T (*tuya_p2p_rtc_get_frame_cb_t)(MEDIA_FRAME *pMediaFrame); + +/** + * @enum TRANS_DEFAULT_QUALITY_E + * + * @brief default quality for live P2P transferring + */ +typedef enum { + TRANS_DEFAULT_STANDARD = 0, /**ex. 640*480, 15fps */ + TRANS_DEFAULT_HIGH, /** ex. 1920*1080, 20fps */ + TRANS_DEFAULT_THIRD, + TRANS_DEFAULT_FOURTH, + TRANS_DEFAULT_MAX +} TRANS_DEFAULT_QUALITY_E; + +typedef struct { + INT_T max_client_num; /**max p2p connect num*/ + TRANS_DEFAULT_QUALITY_E def_live_mode; /** for multi-streaming ipc, the default quality for live preview */ + BOOL_T low_power; + UINT_T recv_buffer_size; /*recv app data size. if recv_buffer_size = 0,default = 16*1024*/ + TRANS_IPC_AV_INFO_T av_info; + tuya_p2p_rtc_disconnect_cb_t on_disconnect_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_video_frame_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_audio_frame_callback; +} TUYA_IPC_P2P_VAR_T; + +//////////////////////////////external interface//////////////////////////////////////////// +OPERATE_RET p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var); +OPERATE_RET p2p_rtc_listen_start(); +OPERATE_RET p2p_rtc_listen_stop(); +///////////////////////////////////////////////////////////////////////////////// + +// OPERATE_RET tuya_ipc_init_trans_av_info(TRANS_IPC_AV_INFO_T *av_info); +OPERATE_RET tuya_p2p_rtc_register_get_video_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback); +OPERATE_RET tuya_p2p_rtc_register_get_audio_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback); +INT_T OnGetVideoFrameCallback(MEDIA_FRAME *pMediaFrame); +INT_T OnGetAudioFrameCallback(MEDIA_FRAME *pMediaFrame); + +// OPERATE_RET tuya_ipc_tranfser_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var); +// OPERATE_RET tuya_ipc_tranfser_quit(VOID); +// OPERATE_RET tuya_ipc_get_client_conn_info(OUT UINT_T *p_client_num, OUT CLIENT_CONNECT_INFO_T **p_p_conn_info); +// OPERATE_RET tuya_ipc_free_client_conn_info(IN CLIENT_CONNECT_INFO_T *p_conn_info); +OPERATE_RET tuya_ipc_tranfser_secret_mode(BOOL_T mode); +OPERATE_RET tuya_ipc_delete_video_finish(IN CONST UINT_T client); +OPERATE_RET tuya_ipc_delete_video_finish_v2(IN CONST UINT_T client, TUYA_DOWNLOAD_DATA_TYPE type, int success); +OPERATE_RET tuya_ipc_p2p_debug(VOID); +OPERATE_RET tuya_ipc_p2p_client_connect(OUT INT_T *handle, IN char *remote_id, IN char *local_key); +OPERATE_RET tuya_ipc_p2p_client_disconnect(int handle); +OPERATE_RET tuya_ipc_p2p_client_start_prev(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_stop_prev(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_start_audio(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_stop_audio(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_set_video_clarity_standard(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_set_video_clarity_high(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_video_send_start(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_video_send_stop(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_audio_send_start(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_audio_send_stop(INT_T handle); +// OPERATE_RET tuya_ipc_bind_clarity_with_chn(TRANSFER_VIDEO_CLARITY_TYPE_E type, TRANSFER_VIDEO_CLARITY_VALUE_E value); +OPERATE_RET tuya_ipc_p2p_set_limit_mode(BOOL_T islimit); + +/***********************************album protocol ****************************************/ +OPERATE_RET tuya_ipc_sweeper_convert_file_info(IN INT_T *fileArray, OUT VOID **pIndexFileInfo, OUT INT_T *fileInfoLen); +OPERATE_RET tuya_ipc_sweeper_parse_file_info(IN C2C_CMD_IO_CTRL_ALBUM_DOWNLOAD_START *srcfileInfo, + INOUT INT_T *fileArray, IN INT_T arrSize); +OPERATE_RET tuya_ipc_sweeper_send_data_with_buff(IN INT_T client, SWEEPER_ALBUM_FILE_TYPE_E type, IN INT_T fileLen, + IN CHAR_T *fileBuff); +OPERATE_RET tuya_ipc_sweeper_send_finish_2_app(IN INT_T client); +OPERATE_RET tuya_ipc_stop_send_data_to_app(IN INT_T client); +OPERATE_RET tuya_sweeper_send_data_with_buff(IN INT_T client, IN CHAR_T *name, IN INT_T fileLen, IN CHAR_T *fileBuff, + IN INT_T timeout_ms); +OPERATE_RET tuya_p2p_keep_alive(IN INT_T client); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h new file mode 100755 index 000000000..cb5f854c0 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h @@ -0,0 +1,154 @@ +/** + * @ tuya_ipc_p2p_common.h + * @brief p2p common define + * @version 0.1 + * @date 2021-11-17 + * + * @copyright Copyright (c) tuya.inc 2011 + * + */ + +#ifndef __TUYA_IPC_P2P_COMMON_H__ +#define __TUYA_IPC_P2P_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_common_types.h" + +#define P2P_ID_LEN 25 /**P2P ID MAX LEN*/ +#define P2P_NAME_LEN 8 /**P2P NAME LEN*/ +#define P2P_PASSWD_LEN 8 /**P2P PASSWORD LEN*/ +#define P2P_GW_LOCAL_KEY_LEN 16 /**GW KEY MAX LEN*/ +#define P2P_TYPE_LEN 8 /**P2P TYPE MAX LEN*/ +#define TUYA_P2P 4 + +/** + * @struct TUYA_IPC_P2P_AUTH_T + * + * @brief p2p auth info + */ +typedef struct { + CHAR_T p2p_id[P2P_ID_LEN + 1]; /** p2p id*/ + CHAR_T p2p_name[P2P_NAME_LEN + 1]; /** p2p name*/ + CHAR_T p2p_passwd[P2P_PASSWD_LEN + 1]; /** p2p auth passeord*/ + CHAR_T gw_local_key[P2P_GW_LOCAL_KEY_LEN + 1]; /** p2p auth key*/ + VOID *p_reserved; /** reserved ptr*/ +} TUYA_IPC_P2P_AUTH_T; + +/** + * @enum TRANSFER_AUDIO_SAMPLE_E + * + * @brief audio sample + */ +typedef enum { + TY_AUDIO_SAMPLE_8K, /** audio sample 8K*/ + TY_AUDIO_SAMPLE_11K, /** audio sample 11K*/ + TY_AUDIO_SAMPLE_12K, /** audio sample 12K*/ + TY_AUDIO_SAMPLE_16K, /** audio sample 16K*/ + TY_AUDIO_SAMPLE_22K, /** audio sample 22K*/ + TY_AUDIO_SAMPLE_24K, /** audio sample 24K*/ + TY_AUDIO_SAMPLE_32K, /** audio sample 32K*/ + TY_AUDIO_SAMPLE_44K, /** audio sample 44K*/ + TY_AUDIO_SAMPLE_48K, /** audio sample 48K*/ + TY_AUDIO_SAMPLE_96K, /** audio sample 96K*/ +} TRANSFER_AUDIO_SAMPLE_E; + +/** + * @enum TRANSFER_AUDIO_DATABITS_E + * + * @brief audio databit + */ +typedef enum { + TY_AUDIO_DATABITS_8, /** 8 databit*/ + TY_AUDIO_DATABITS_16, /** 16 databit*/ +} TRANSFER_AUDIO_DATABITS_E; + +/** + * @enum TRANSFER_AUDIO_CHANNEL_E + * + * @brief audio track + */ +typedef enum { + TY_AUDIO_CHANNEL_MONO, + TY_AUDIO_CHANNEL_STERO, +} TRANSFER_AUDIO_CHANNEL_E; + +#define P2P_SESSION_DETECH_INTV (10000) /**session check time interval (ms)*/ +#define P2P_SESSION_DETECH_COUNT (120) /**session check cnt*/ +#define P2P_LOGIN_DETECH_CNT (180000) /**login checkout time interval*/ + +/** + * @structP2P_SESSION_DETECH_T + * + * @brief debug cnt + */ +typedef struct { + UINT_T lstCnt; /** send cnt*/ + UINT_T lstCnt2; /** recv cnt*/ + UINT_T staticCnt; /** detech cnt*/ +} P2P_SESSION_DETECH_T; + +/** +* @brief get p2p id +* +* @param[out] p2p_id:p2p id + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_p2p_get_id(INOUT CHAR_T p2p_id[]); + +/** +* @brief check p2p auth update +* +* @param (VOID) + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_check_p2p_auth_update(VOID); + +/** +* @brief get p2p auth info +* +* @param[out] pAuth:p2p auth info + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_get_p2p_auth(TUYA_IPC_P2P_AUTH_T *pAuth); + +/** +* @brief get p2p auth info +* +* @param[out] p2p_pw:p2p password + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_p2p_update_pw(INOUT CHAR_T p2p_pw[]); + +/** + * @brief p2p log report + * + * @param[in] devid:device id + * @param[in] pData:log data + * @param[in] len:data len + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET mqc_p2p_data_rept_v41(IN CONST CHAR_T *devid, IN CONST CHAR_T *pData, IN CONST INT_T len); + +/** + * @brief iot reset config callback + * + * @param[in] rst_tp:reset tp + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +INT_T iot_gw_reset_cb(VOID *rst_tp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h new file mode 100755 index 000000000..2a176baea --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h @@ -0,0 +1,46 @@ +/** + * @ tuya_ipc_p2p_error.h + * @brief p2p err define + * @version 0.1 + * @date 2021-11-17 + * + * @copyright Copyright (c) tuya.inc 2011 + * + */ + +#ifndef _TUYA_IPC_P2P_ERROR_H_ +#define _TUYA_IPC_P2P_ERROR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define ERROR_P2P_SUCCESSFUL 0 /** p2p operation success*/ +#define ERROR_P2P_NOT_INITIALIZED -1 /** p2p has not init*/ +#define ERROR_P2P_ALREADY_INITIALIZED -2 /** p2p has inited*/ +#define ERROR_P2P_TIME_OUT -3 /** p2p has inited*/ +#define ERROR_P2P_INVALID_ID -4 /** p2p invalid id*/ +#define ERROR_P2P_INVALID_PARAMETER -5 /** p2p invalid param*/ +#define ERROR_P2P_DEVICE_NOT_ONLINE -6 /** device outline*/ +#define ERROR_P2P_FAIL_TO_RESOLVE_NAME -7 /** p2p name err*/ +#define ERROR_P2P_INVALID_PREFIX -8 /** p2p prefix err*/ +#define ERROR_P2P_ID_OUT_OF_DATE -9 /** device outline*/ +#define ERROR_P2P_NO_RELAY_SERVER_AVAILABLE -10 /** server no relay*/ +#define ERROR_P2P_INVALID_SESSION_HANDLE -11 /** invalid session*/ +#define ERROR_P2P_SESSION_CLOSED_REMOTE -12 /** remote close*/ +#define ERROR_P2P_SESSION_CLOSED_TIMEOUT -13 /** close timeout*/ +#define ERROR_P2P_SESSION_CLOSED_CALLED -14 /** close called*/ +#define ERROR_P2P_REMOTE_SITE_BUFFER_FULL -15 /** remote buffer full*/ +#define ERROR_P2P_USER_LISTEN_BREAK -16 /** listen break*/ +#define ERROR_P2P_MAX_SESSION -17 /** limit max session*/ +#define ERROR_P2P_UDP_PORT_BIND_FAILED -18 /** port bind fail*/ +#define ERROR_P2P_USER_CONNECT_BREAK -19 /** connect err*/ +#define ERROR_P2P_SESSION_CLOSED_INSUFFICIENT_MEMORY -20 /** memory insufficent*/ +#define ERROR_P2P_INVALID_APILICENSE -21 /** invalid apilicense*/ +#define ERROR_P2P_FAIL_TO_CREATE_THREAD -22 /** create pthread fail*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h new file mode 100755 index 000000000..a73761f7a --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h @@ -0,0 +1,284 @@ +#ifndef __TUYA_IPC_P2P_INNER_H__ +#define __TUYA_IPC_P2P_INNER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_p2p_common.h" + +// #define ERROR_P2P_SUCCESSFUL 0 /** p2p operation success*/ +// #define ERROR_P2P_NOT_INITIALIZED -1 /** p2p has not init*/ +// #define ERROR_P2P_ALREADY_INITIALIZED -2 /** p2p has inited*/ +// #define ERROR_P2P_TIME_OUT -3 /** p2p has inited*/ +// #define ERROR_P2P_INVALID_ID -4 /** p2p invalid id*/ +// #define ERROR_P2P_INVALID_PARAMETER -5 /** p2p invalid param*/ +// #define ERROR_P2P_DEVICE_NOT_ONLINE -6 /** device outline*/ +// #define ERROR_P2P_FAIL_TO_RESOLVE_NAME -7 /** p2p name err*/ +// #define ERROR_P2P_INVALID_PREFIX -8 /** p2p prefix err*/ +// #define ERROR_P2P_ID_OUT_OF_DATE -9 /** device outline*/ +// #define ERROR_P2P_NO_RELAY_SERVER_AVAILABLE -10 /** server no relay*/ +// #define ERROR_P2P_INVALID_SESSION_HANDLE -11 /** invalid session*/ +// #define ERROR_P2P_SESSION_CLOSED_REMOTE -12 /** remote close*/ +// #define ERROR_P2P_SESSION_CLOSED_TIMEOUT -13 /** close timeout*/ +// #define ERROR_P2P_SESSION_CLOSED_CALLED -14 /** close called*/ +// #define ERROR_P2P_REMOTE_SITE_BUFFER_FULL -15 /** remote buffer full*/ +// #define ERROR_P2P_USER_LISTEN_BREAK -16 /** listen break*/ +// #define ERROR_P2P_MAX_SESSION -17 /** limit max session*/ +// #define ERROR_P2P_UDP_PORT_BIND_FAILED -18 /** port bind fail*/ +// #define ERROR_P2P_USER_CONNECT_BREAK -19 /** connect err*/ +// #define ERROR_P2P_SESSION_CLOSED_INSUFFICIENT_MEMORY -20 /** memory insufficent*/ +// #define ERROR_P2P_INVALID_APILICENSE -21 /** invalid apilicense*/ +// #define ERROR_P2P_FAIL_TO_CREATE_THREAD -22 /** create pthread fail*/ + +#define C2C_MAJOR_VERSION 1 +#define C2C_MINOR_VERSION 2 + +// Tuya fixed protocol header +typedef struct { + unsigned int type; // Request type (0: request, 1:response) + unsigned short high_cmd; // Main command refer to TY_MAIN_CMD_TYPE_E; + unsigned short low_cmd; // Last 2 bytes: sub-command + unsigned int length; +} C2C_CMD_FIXED_HEADER_T; + +typedef struct C2C_AV_TRANS_FIXED_HEADER_ { + unsigned int request_id; + unsigned int reserve1; + unsigned long long int time_ms; + int extension_length; + unsigned int reserve2; +} C2C_AV_TRANS_FIXED_HEADER; + +typedef enum { + TY_EXT_VIDEO_PARAM = 0x01, + TY_EXT_AUDIO_PARAM = 0x02, +} TY_AV_EXTENSION_TYPE_T; + +// Video playback commands [playback, live] +// https://wiki.tuya-inc.com:7799/page/74675515 +typedef enum { + TY_CMD_IO_CTRL_VIDEO_PLAY, // 0 Start + TY_CMD_IO_CTRL_VIDEO_PAUSE, // 1 Pause + TY_CMD_IO_CTRL_VIDEO_RESUME, // 2 Resume playback, not used for live + TY_CMD_IO_CTRL_VIDEO_STOP, // 3 Stop + TY_CMD_IO_CTRL_AUDIO_MIC_START, // 4 IPC -> APP, start audio accompaniment + TY_CMD_IO_CTRL_AUDIO_MIC_STOP, // 5 IPC -> APP, end audio accompaniment + + TY_CMD_IO_CTRL_VIDEO_PLAY_V2 = 20, // 20 Start + TY_CMD_IO_CTRL_PLAYBACK_START_WITH_MODE = 21, // 21 Playback start supports playback mode + TY_CMD_IO_CTRL_VIDEO_SEND_START = 50, // 50 Video ready to send + TY_CMD_IO_CTRL_VIDEO_SEND_STOP = 51, // 51 Video stop sending + TY_CMD_IO_CTRL_AUDIO_SEND_START = 52, // 52 Audio ready to send + TY_CMD_IO_CTRL_AUDIO_SEND_STOP = 53, // 53 Audio stop sending + TY_CMD_IO_CTRL_VIDEO_SEND_PAUSE = 54, // 54 Sender pauses video sending + TY_CMD_IO_CTRL_VIDEO_SEND_RESUME = 55, // 55 Sender resumes video sending +} TY_CMD_IO_CTRL_VIDEO_E; + +typedef struct { + unsigned int channel; + unsigned int operation; // Refer to TY_CMD_IO_CTRL_VIDEO_E +} C2C_TRANS_CTRL_VIDEO_REQ_T; + +typedef struct { + unsigned int channel; + unsigned int operation; // Refer to TY_CMD_IO_CTRL_AUDIO_OP_E +} C2C_TRANS_CTRL_AUDIO_REQ_T; + +typedef enum { + TY_C2C_CMD_IO_CTRL_COMMAND_INVALID, // 0 Invalid command + TY_C2C_CMD_IO_CTRL_COMMAND_RECV, // 1 Command received + TY_C2C_CMD_IO_CTRL_COMMAND_FAILED, // 2 Command execution failed (intercom: camera occupied by others) + TY_C2C_CMD_IO_CTRL_COMMAND_SUCCESS, // 3 Command completed + TY_C2C_CMD_IO_CTRL_COMMAND_BUSY, /* 4 Command completed, intercom: operation error, already in intercom state + Device side updates reqid, app side restarts mic */ +} TY_C2C_CMD_IO_CTRL_STATUS_CODE_E; + +// General response structure +typedef struct { + unsigned int channel; + int result; // Refer to TY_C2C_CMD_IO_CTRL_STATUS_CODE_E +} C2C_CMD_IO_CTRL_COM_RESP_T; + +// Audio C2C_CMD_QUERY_AUDIO_PARAMS +// request +typedef struct { + unsigned int channel; +} C2C_TRANS_QUERY_AUDIO_PARAM_REQ_T; + +// response +typedef struct { + unsigned int type; // Refer to TY_AV_CODEC_ID + unsigned int sample_rate; // Refer to TRANSFER_AUDIO_SAMPLE_E + unsigned int bitwidth; // Refer to TRANSFER_AUDIO_DATABITS_E + unsigned int channel_num; // Refer to TRANSFER_AUDIO_CHANNEL_E +} AUDIO_PARAM_T; + +typedef struct { + unsigned int channel; + unsigned int count; + AUDIO_PARAM_T audioParams[0]; +} C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E; + +typedef enum { + TY_VIDEO_CLARITY_INNER_PROFLOW = 0x1, /**< Data saving */ + TY_VIDEO_CLARITY_INNER_STANDARD = 0x2, /**< Standard definition */ + TY_VIDEO_CLARITY_INNER_HIGH = 0x4, /**< High definition */ + TY_VIDEO_CLARITY_S_INNER_HIGH = 0x8, /**< Ultra high definition */ + TY_VIDEO_CLARITY_SS_INNER_HIGH = 0x10, /**< Super ultra high definition */ +} TRANSFER_VIDEO_CLARITY_TYPE_INNER_E; + +typedef enum { + TY_AV_CODEC_VIDEO_UNKOWN = 0, + TY_AV_CODEC_VIDEO_MPEG4 = 0x10, + TY_AV_CODEC_VIDEO_H263 = 0x11, + TY_AV_CODEC_VIDEO_H264 = 0x12, + TY_AV_CODEC_VIDEO_MJPEG = 0x13, + TY_AV_CODEC_VIDEO_H265 = 0x14, + + TY_AV_CODEC_AUDIO_ADPCM = 0x80, + TY_AV_CODEC_AUDIO_PCM = 0x81, + TY_AV_CODEC_AUDIO_AAC_RAW = 0x82, + TY_AV_CODEC_AUDIO_AAC_ADTS = 0x83, + TY_AV_CODEC_AUDIO_AAC_LATM = 0x84, + TY_AV_CODEC_AUDIO_G711U = 0x85, // 10 + TY_AV_CODEC_AUDIO_G711A = 0x86, + TY_AV_CODEC_AUDIO_G726 = 0x87, + TY_AV_CODEC_AUDIO_SPEEX = 0x88, + TY_AV_CODEC_AUDIO_MP3 = 0x89, + + TY_AV_CODEC_MAX = 0xFF +} TY_AV_CODEC_ID; + +typedef struct { + // Video part parameters + TY_AV_CODEC_ID video_codec[8]; + UINT_T fps[8]; + UINT_T gop[8]; + UINT_T bitrate[8]; // kbps + UINT_T width[8]; + UINT_T height[8]; + // Audio part parameters + TY_AV_CODEC_ID audio_codec; + TRANSFER_AUDIO_SAMPLE_E audio_sample; + TRANSFER_AUDIO_DATABITS_E audio_databits; + TRANSFER_AUDIO_CHANNEL_E audio_channel; +} TRANS_IPC_AV_INFO_T; + +// request Video clarity query +typedef struct { + unsigned int channel; +} C2C_TRANS_QUERY_VIDEO_CLARITY_REQ_T; + +// response +typedef struct { + unsigned int channel; + unsigned int sp_mode; // Supported clarity mode + unsigned int cur_mode; // Current clarity, refer to TRANSFER_VIDEO_CLARITY_TYPE_INNER_E +} C2C_TRANS_QUERY_VIDEO_CLARITY_RESP_T; + +// Set clarity +typedef struct { + unsigned int channel; + unsigned int mode; // Clarity, refer to TRANSFER_VIDEO_CLARITY_TYPE_INNER_E +} C2C_TRANS_CTRL_VIDEO_CLARITY_T; + +// Video stream info parameters C2C_CMD_QUERY_VIDEO_STREAM_PARAMS +// request +typedef struct { + unsigned int channel; +} C2C_TRANS_QUERY_VIDEO_PARAM_REQ_T; + +// response +typedef struct { + unsigned int codec_type; // Refer to TY_AV_CODEC_ID + unsigned int width; + unsigned int height; + unsigned int frame_rate; +} VIDEO_PARAM_T; + +typedef struct { + unsigned int channel; + unsigned int count; + VIDEO_PARAM_T VideoParams[0]; +} C2C_TRANS_QUERY_VIDEO_PARAM_RESP_T; + +typedef struct { + unsigned int version; // High bits major version, low 16 bits minor version +} C2C_CMD_PROTOCOL_VERSION_T; + +typedef enum { + // Transparent transmission + TY_C2C_CMD_QUERY_TEXT, // Client transparently transmits string to device. [Avoid recompiling SDK when extending + // certain functions] + + // Query class + TY_C2C_CMD_QUERY_FIXED_ABILITY, // 1, Query device capability set, sub-type see + // TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_E + TY_C2C_CMD_QUERY_AUDIO_PARAMS, // 2, Query audio parameters, audio info type see TY_CMD_QUERY_AUDIO_PARAMS + TY_C2C_CMD_QUERY_PLAYBACK_INFO, // 3, Query SD card playback info, parameters see + TY_C2C_CMD_QUERY_VIDEO_STREAM_PARAMS, // 4, Query video mode info { [channel, HD/standard/smooth, width/height, + // encoding type], [channel, HD/standard/smooth, width/height, encoding type], + // [channel, HD/standard/smooth, width/height, encoding type] } + TY_C2C_CMD_QUERY_VIDEO_CLARITY, // 5, Query clarity + + // IO control class + TY_C2C_CMD_IO_CTRL_VIDEO, // 6, Live command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_PLAYBACK, // 7, Playback command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_AUDIO, // 8, Audio command, sub-type see TY_CMD_IO_CTRL_AUDIO + TY_C2C_CMD_IO_CTRL_VIDEO_CLARITY, // 9, Set clarity + + TY_C2C_CMD_PROTOCOL_VERSION, // 10, Protocol version + // for download for feit + TY_C2C_CMD_IO_CTRL_PLAYBACK_DOWNLOAD, // 11 Download command, sub-type see TY_CMD_IO_CTRL_DOWNLOAD_OP_E + + // for camera of GW + TY_C2C_CMD_QUERY_AUDIO_PARAMS_GW, // 12, Query audio parameters, audio info type see TY_CMD_QUERY_AUDIO_PARAMS .for + // camera of GW + TY_C2C_CMD_QUERY_PLAYBACK_INFO_GW, // 13, Query SD card playback info, parameters see + TY_C2C_CMD_QUERY_VIDEO_STREAM_PARAMS_GW, // 14, Query video mode info { [channel, HD/standard/smooth, width/height, + // encoding type], [channel, HD/standard/smooth, width/height, encoding + // type], [channel, HD/standard/smooth, width/height, encoding type] } + TY_C2C_CMD_QUERY_VIDEO_CLARITY_GW, // 15, Query clarity + TY_C2C_CMD_IO_CTRL_VIDEO_GW, // 16, Live command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_PLAYBACK_GW, // 17, Playback command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_AUDIO_GW, // 18, Audio command, sub-type see TY_CMD_IO_CTRL_AUDIO + TY_C2C_CMD_IO_CTRL_VIDEO_CLARITY_GW, // 19, Set clarity + TY_C2C_CMD_QUERY_FIXED_ABILITY_GW, // 20, Query device capability set, sub-type see + // TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_E + + TY_C2C_CMD_CHAN_SWITCH = 51, // 51 Single-channel multi-channel device channel setting + TY_C2C_CMD_IO_CTRL_PLAYBACK_EXT0 = 100, // 100 Playback speed control extension + TY_C2C_CMD_IO_CTRL_PLAYBACK_GW_EXT0 = 101, // 101 Playback speed control extension gateway + // for p2p new authorization + TY_C2C_CMD_AUTHORIZATION = 250, + TY_C2C_CMD_SUB_BINDS_INFO = 300, +} TY_MAIN_CMD_TYPE_E; + +typedef enum tagTransferVideoClarityType { + eVideoClarityStandard = 0, + eVideoClarityHigh, + eVideoClarityThird, + eVideoClarityFourth, + eVideoClarityMax +} TRANSFER_VIDEO_CLARITY_TYPE; + +typedef enum tagIpcStreamType { + eIpcStreamVideoMain, ///< first video stream + eIpcStreamVideoSub, ///< second video stream + eIpcStreamVideo3rd, ///< third video stream + eIpcStreamVideo4th, ///< forth video stream + eIpcStreamVideoMax = 8, + eIpcStreamAudioMain, ///< first audio stream + eIpcStreamAudioSub, ///< second audio stream + eIpcStreamAudio3rd, ///< third audio stream + eIpcStreamAudio4th, ///< forth audio stream + eIpcStreamMax = 16, +} IPC_STREAM_TYPE; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h new file mode 100755 index 000000000..ed57064bb --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h @@ -0,0 +1,200 @@ +/* + * tuya_p2p_api.h + *Copyright(C),2017-2022, TUYA company www.tuya.com + * + *FILE description: + * + */ + +#ifndef INCLUDE_COMPONENTS_SVC_STREAMING_P2P_INCLUDE_TUYA_P2P_API_H_ +#define INCLUDE_COMPONENTS_SVC_STREAMING_P2P_INCLUDE_TUYA_P2P_API_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include "tuya_cloud_types.h" +#include "tuya_ipc_media.h" +#include "tuya_ipc_media_stream_event.h" +#include "tuya_ipc_media_adapter.h" + +/** +* @brief initialize tuya P2P suggestion do init after ipc has been activated(mqtt online) +* +* @param[in] p_var:p2p param + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_imm_p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var); + +/** +* @brief close all P2P conections, live preivew & playback +* +* @param[in]VOID + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_imm_p2p_close(VOID); + +/** + * @brief cur p2p connect num + * + * @param[in] VOID + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_alive_cnt(VOID); + +/** + * @brief close p2p all connect + * + * @param[in] VOID + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_all_stream_close(INT_T close_reason); +/** + * @brief delete video finish v2 + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:current connected client number + * @param[in] type:download type + * @param[in] success:0 fail 1 success + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_delete_video_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, int success); + +/** + * @brief send playback video frame to APP via P2P channel + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * @param[in] p_video_frame:p_video_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_video_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_VIDEO_FRAME_T *p_video_frame); + +/** + * @brief send playback audio frame to APP via P2P channel + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * @param[in] p_audio_frame:p_audio_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_audio_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_AUDIO_FRAME_T *p_audio_frame); + +/** + * @brief send video frame with encrypt + * + * @param[in] client:current connected client number + * @param[in] reqId:request id + * @param[in] p_video_frame:video frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET +tuya_imm_p2p_playback_send_video_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_video_frame); + +/** + * @brief send audio frame with encrypt + * + * @param[in] client:current connected client number + * @param[in] reqId:request id + * @param[in] p_audio_frame:audio frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET +tuya_imm_p2p_playback_send_audio_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_audio_frame); + +/** + * @brief notify client(APP) playback fragment is finished, send frag info to app + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * @param[in] fgmt:playback time + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_fragment_end(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST PLAYBACK_TIME_S *fgmt); + +/** + * @brief notify client(APP) playback data is finished, no more data outgoing + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client); + +/** + * @brief download data transfer api V2 + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client: current connected client number + * @param[in] type: frame head info + * @param[in] pHead: download type + * @param[in] pData: media data + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_app_download_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, IN CONST void *pHead, IN CONST CHAR_T *pData); + +/** + * @brief cur download status + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client: current connected client number + * @param[in] percent: percent(0-100),cur only 100 in use + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ + +OPERATE_RET tuya_imm_p2p_app_download_status(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, IN CONST UINT_T percent); + +/** + * @brief cur download status is over + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:current connected client number + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_app_download_is_send_over(IN CONST CHAR_T *dev_id, IN CONST UINT_T client); + +/** + * @brief album file play data transfer api V2 + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client: current connected client number + * @param[in] p_frame: media frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_app_album_play_send_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST TUYA_ALBUM_PLAY_FRAME_T *p_frame); + +/** + * @brief notify client(APP) album play data is finished, no more data outgoing + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_album_play_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client); + +#ifdef __cplusplus +} +#endif +#endif /* INCLUDE_COMPONENTS_SVC_STREAMING_P2P_INCLUDE_TUYA_P2P_API_H_ */ diff --git a/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c new file mode 100755 index 000000000..d52ef4008 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c @@ -0,0 +1,1753 @@ +#include +#include +#include +#include +#include +#include +#include "tal_log.h" +#include "tal_hash.h" +#include "tal_mutex.h" +#include "tal_system.h" +#include "tal_memory.h" +#include "tal_thread.h" +#include "tuya_ipc_p2p.h" +#include "tuya_ipc_p2p_error.h" +#include "tuya_ipc_p2p_inner.h" +#include "tuya_ipc_p2p_common.h" +#include "tuya_media_service_rtc.h" +#include "rtp-payload.h" + +#define TUYA_CMD_CHANNEL (0) // Signaling channel, signal mode refer to P2P_CMD_E +#define TUYA_VDATA_CHANNEL (1) // Video data channel +#define TUYA_ADATA_CHANNEL (2) // Audio data channel +#define TUYA_P2P_CMD_CHECK(cmd) (cmd == P2P_LIVE || cmd == P2P_PLAYBACK || cmd == P2P_PAUSE) +#define TUYA_P2P_CHN_CHECK(cmd) (cmd == TUYA_CMD_CHANNEL || cmd == TUYA_VDATA_CHANNEL || cmd == TUYA_ADATA_CHANNEL) + +#define P2P_SESSION_IDLE (0) +#define P2P_SESSION_RUNNING (1) +#define P2P_SESSION_CLOSING (2) +#define P2P_SESSION_INITING (3) + +#define TUYA_IPC_P2P_DEFAULT_CAMERA (0) +#define P2P_RTP_PACK_LEN (1100 + 128) // RTP packet buffer size +#define P2P_RECV_TIMEOUT (30) + +#define P2P_CHECK_USER_TIMES (10000) // 10s +// Password synchronization structure +typedef struct P2P_CMD_PASSWD_ { + int mark; // Custom identification mark + int reqId; // Client-defined request ID, used as unique identifier + char user[32]; // Username + char passwd[64]; // Password +} P2P_CMD_PASSWD_T; + +// Control signal header structure +typedef struct P2P_CMD_PARSE_ { + int mark; // Custom identification mark + int reqId; // Client-defined request ID, used as unique identifier + C2C_CMD_FIXED_HEADER_T str_header; +} P2P_CMD_PARSE_T; + +#define P2P_CMD_PARSE_MAX_SIZE_V2 (4096) +#define P2P_CMD_HEAD_LEN (sizeof(P2P_CMD_PARSE_T)) + +#define MAX_PAYLOAD_SIZE (1100) /**MAX PAYLOAD SIZE*/ +#define RTP_MTU_LEN MAX_PAYLOAD_SIZE +#define RTP_SPLIT_LEN RTP_MTU_LEN +#define TUYA_RTP_HEAD 0x12345678 // Custom RTP identification packet header +#define P2P_CMD_MARK TUYA_RTP_HEAD // Temporarily reuse with RTP + +#define READ_HEADER_PART 0 // Read header part +#define READ_PAYLOAD_PART 1 // Read payload part + +#define EXT_PROTOCOL_V0_LEN (12) +#define P2P_EXT_HEAD_MAX_LEN \ + (sizeof(C2C_AV_TRANS_FIXED_HEADER) + EXT_PROTOCOL_V0_LEN) // Extended video header protocol V0 head+ext(8+4)+rtp_len + +#define OFFSET(TYPE, MEMBER) ((SIZE_T)(&(((TYPE *)0)->MEMBER))) + +#define STACK_SIZE_P2P_MEDIA_SEND 65536 +#define STACK_SIZE_P2P_MEDIA_RECV 65536 +#define STACK_SIZE_P2P_CMD_SEND 65536 +#define STACK_SIZE_P2P_CMD_RECV 65536 +#define STACK_SIZE_P2P_DETECT 65536 +#define STACK_SIZE_P2P_LISTEN 131072 + +typedef struct { + INT_T client; + INT_T channel; + CHAR_T *p_rtp_buff; // RTP data buffer, reference size MTU+100 + INT_T fix_len; // Supplementary private header data + CHAR_T ext_head_buff[P2P_EXT_HEAD_MAX_LEN]; // According to extended video header protocol head+ext(8)+rtp_len +} RTP_PACK_NAL_ARG_T; + +typedef enum { + P2P_IDLE = 0, + P2P_VIDEO = 0x1, // Start live stream request + P2P_AUDIO = 0x2, + P2P_PB_VIDEO = 0x4, // Start playback request + P2P_PB_AUDIO = 0x8, + P2P_PB_PAUSE = 0x10, // Pause video request + P2P_SPEAKER = 0x20, // Intercom request +} P2P_CMD_E; + +typedef struct { + INT_T read_size; // init P2P_CMD_HEAD_LEN; + CHAR_T read_buff[P2P_CMD_PARSE_MAX_SIZE_V2]; + INT_T cur_read; // Current read length + INT_T flag; // READ_HEADER_PART/READ_PAYLOAD_PART +} P2P_DATA_PARSE_T; + +typedef struct { + MUTEX_HANDLE cmutex; + TUYA_IPC_P2P_AUTH_T str_P2p_auth; + /*******client*******/ + INT_T session; // Save session number + INT_T status; // Session status 0 not started + /*******p2p server*******/ + P2P_CMD_E cmd; // Signal status information + P2P_CMD_PARSE_T pb_resp_head; + CHAR_T *p_video_rtp_buff; // Video RTP data buffer, reference size MTU+100 + CHAR_T *p_audio_rtp_buff; // Audio RTP data buffer, reference size MTU+100 + USHORT_T video_seq_num; // Video RTP packet sequence number + USHORT_T audio_seq_num; // Audio RTP packet sequence number + BOOL_T key_frame; + UINT64_T v_pts; // Video PTS + UINT64_T v_timestamp; // Video absolute time (ms) + UINT64_T a_pts; // Audio PTS + UINT64_T a_timestamp; // Audio absolute time (ms) + INT_T video_req_id; // Video request ID, used for preview, playback and other services + INT_T audio_req_id; // Audio request ID + TRANSFER_VIDEO_CLARITY_TYPE_INNER_E cur_clarity; // Current video clarity type + P2P_DATA_PARSE_T proto_parse; + TRANS_IPC_AV_INFO_T av_Info; // TODO currently video parameters must be consistent + + tuya_p2p_rtc_disconnect_cb_t on_disconnect_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_video_frame_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_audio_frame_callback; + THREAD_HANDLE cmd_recv_proc_thread; // Command receive thread handle + THREAD_HANDLE video_send_proc_thread; // Video send thread handle + // TAL_VENC_FRAME_T tal_video_frame; + // TAL_AUDIO_FRAME_INFO_T tal_audio_frame; + MEDIA_FRAME media_frame; + MEDIA_FRAME media_audio_frame; + /******* p2p server*******/ +} P2P_SESSION_T; + +STATIC P2P_SESSION_T *sg_p2p_session = NULL; +INT_T g_listen_start = 0; // Flag variable to control listen thread start or stop +THREAD_HANDLE g_listen_thrd_hdl = NULL; // Listen thread handle + +OPERATE_RET p2p_deal_with_listen(INT_T session); +OPERATE_RET p2p_get_userinfo(INT_T session, INT_T p2pType); +IPC_STREAM_TYPE p2p_get_chn_idx(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E cur_clarity); +TRANSFER_VIDEO_CLARITY_TYPE p2p_clarity_trans(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E type); +INT_T p2p_prepare_video_send_resource(P2P_SESSION_T *pSession); +INT_T p2p_release_video_send_resource(P2P_SESSION_T *pSession); +INT_T p2p_prepare_audio_send_resource(P2P_SESSION_T *pSession); +INT_T p2p_release_audio_send_resource(P2P_SESSION_T *pSession); +INT_T __p2p_session_clear(P2P_SESSION_T *pSession); +INT_T __p2p_session_all_stop(P2P_SESSION_T *pSession); +INT_T __p2p_session_release_va(P2P_SESSION_T *pSession); +VOID __p2p_thread_exit(THREAD_HANDLE thread); +VOID __p2p_rtc_close(INT_T rtc_session, INT_T reason, P2P_SESSION_T* p2p_session); + +void *rtp_alloc(void *param, int bytes); +void rtp_free(void *param, void *packet); +int rtp_pack_packet_handler(void *param, const void *packet, int bytes, uint32_t timestamp, int flags); + +void ctx_listen_thread_func(void *arg) +{ + printf("listen task start\n"); + while (1) { + INT_T session_id = tuya_p2p_rtc_listen(); + if (session_id < 0) { + printf("listen failed session:[%d]\n", session_id); + break; + } + tuya_p2p_rtc_session_info_t session_info = {0}; + tuya_p2p_rtc_get_session_info(session_id, &session_info); + p2p_deal_with_listen(session_id); + } + printf("listen task exit\n"); + return; +} + +OPERATE_RET p2p_rtc_listen_start() +{ + THREAD_CFG_T param; + param.priority = THREAD_PRIO_3; + param.stackDepth = 128 * 1024; + param.thrdname = "tuya_p2p_listen_task"; + if (g_listen_start) { + printf("p2p listen thread already started"); + return OPRT_COM_ERROR; + } + int result = tal_thread_create_and_start(&g_listen_thrd_hdl, NULL, NULL, ctx_listen_thread_func, NULL, ¶m); + if (OPRT_OK != result) { + printf("create p2p listen thread failed %d", result); + return result; + } + g_listen_start = 1; + return OPRT_OK; +} + +OPERATE_RET p2p_rtc_listen_stop() +{ + if (g_listen_start != 1) { + printf("p2p listen thread not started"); + return OPRT_COM_ERROR; + } + tuya_p2p_rtc_listen_break(); + tal_thread_delete(g_listen_thrd_hdl); + g_listen_start = 0; + return OPRT_OK; +} + +P2P_SESSION_T *p2p_get_idle_session(INT_T *index) +{ + INT_T status = -1; + INT_T i; + if (sg_p2p_session == NULL) + return NULL; + PR_DEBUG("p2p_get_idle_session begin\n"); + status = sg_p2p_session->status; + if (P2P_SESSION_IDLE == status) { + *index = i; + sg_p2p_session->status = P2P_SESSION_INITING; + return sg_p2p_session; + } + PR_DEBUG("p2p_get_idle_session end\n"); + return NULL; +} + +OPERATE_RET p2p_deal_with_listen(INT_T session) +{ + OPERATE_RET ret = OPRT_OK; + BOOL_T userCheckEnable = FALSE; + + // First verify user information, close corresponding session if not qualified + if (OPRT_OK != p2p_get_userinfo(session, 1)) { + PR_ERR("check userinfo error"); + //__p2p_rtc_close(session, RTC_CLOSE_REASON_AUTH_FAIL, NULL); + PR_ERR("Close session[%d] \n", session); + if (FALSE == userCheckEnable) { + PR_ERR("resend p2p passwd to service"); + // Resend passwd once + if (OPRT_OK == tuya_ipc_p2p_update_pw(sg_p2p_session->str_P2p_auth.p2p_passwd)) { + userCheckEnable = TRUE; + } + } + __p2p_rtc_close(session, RTC_CLOSE_REASON_AUTH_FAIL, NULL); + tuya_p2p_rtc_notify_exit(); + tuya_p2p_rtc_deinit(); + return OPRT_COM_ERROR; + } else { + // Once verification is successful, no more authentication exception handling + userCheckEnable = TRUE; + } + + // Request session-related resources + if (OPRT_OK != (ret = p2p_prepare_video_send_resource(sg_p2p_session))) { + goto RET; + } + if (OPRT_OK != (ret = p2p_prepare_audio_send_resource(sg_p2p_session))) { + goto RET; + } + + // Save connection information + sg_p2p_session->session = session; + sg_p2p_session->status = P2P_SESSION_RUNNING; + +RET: + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +/*********************************************************** + * Function: __p2p_get_passwd + * Note:Session listening thread, start corresponding session thread when there is session connection + * Input: session session number + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET p2p_get_userinfo(INT_T session, INT_T p2pType) +{ + CHAR_T *read_buff = NULL; + P2P_CMD_PASSWD_T strUserInfo; + INT_T ret; + INT_T cur_read = 0; + INT_T read_size = sizeof(P2P_CMD_PASSWD_T); + INT_T tmpSize = 0; + BOOL_T flag = FALSE; + INT_T timeout = P2P_RECV_TIMEOUT; // ms + INT_T retry = P2P_CHECK_USER_TIMES * 6 / timeout; + + memset(&strUserInfo, 0x00, sizeof(P2P_CMD_PASSWD_T)); + read_buff = (CHAR_T *)&strUserInfo; + + while (retry > 0) { + retry--; + tmpSize = read_size; + ret = tuya_p2p_rtc_recv_data(session, TUYA_CMD_CHANNEL, read_buff + cur_read, &read_size, timeout); + if ((ret < 0) && (ERROR_P2P_TIME_OUT != ret)) { + // Exception handling + if (ERROR_P2P_SESSION_CLOSED_REMOTE == ret || ERROR_P2P_SESSION_CLOSED_TIMEOUT == ret || + ERROR_P2P_SESSION_CLOSED_CALLED == ret) { + // Session was closed by client, need to close session + PR_ERR("session[%d] was close by client ret[%d]", session, ret); + return OPRT_COM_ERROR; + } else { + // Other exceptions to be supplemented later + } + // Not read, restore value + read_size = tmpSize; + } else { + if (sizeof(P2P_CMD_PASSWD_T) == (read_size + cur_read)) { + // Complete user information obtained, perform simple mark verification + if (P2P_CMD_MARK != ((P2P_CMD_PASSWD_T *)read_buff)->mark) { + // Header parsing exception, exception handling to be completed later (unlikely to reach this + // condition) + PR_ERR("session[%d] read data error mark[0x%x]", session, ((P2P_CMD_PASSWD_T *)read_buff)->mark); + return OPRT_COM_ERROR; + } + flag = TRUE; + break; + } else if (sizeof(P2P_CMD_PASSWD_T) > (read_size + cur_read)) { + cur_read += read_size; + read_size = sizeof(P2P_CMD_PASSWD_T) - cur_read; + } else { + PR_ERR("get userinfo error session[%d]", session); + return OPRT_COM_ERROR; + } + } + } // while (retry > 0) + + if (FALSE == flag) { + PR_ERR("get userinfo timeout session[%d]", session); + return OPRT_COM_ERROR; + } + + PR_DEBUG("compare passwd"); + CHAR_T sign[32 + 1] = {0}; + TKL_HASH_HANDLE md5; + tal_md5_create_init(&md5); + tal_md5_starts_ret(md5); + unsigned char decrypt[16]; + tal_md5_update_ret(md5, (BYTE_T *)(sg_p2p_session->str_P2p_auth.p2p_passwd), + strlen(sg_p2p_session->str_P2p_auth.p2p_passwd)); + tal_md5_update_ret(md5, (BYTE_T *)"||", 2); + tal_md5_update_ret(md5, (BYTE_T *)(sg_p2p_session->str_P2p_auth.gw_local_key), + strlen(sg_p2p_session->str_P2p_auth.gw_local_key)); + tal_md5_finish_ret(md5, decrypt); + tal_md5_free(md5); + + INT_T offset = 0; + INT_T i = 0; + for (i = 0; i < 16; i++) { + sprintf(&sign[offset], "%02x", decrypt[i]); + offset += 2; + } + sign[offset] = 0; + + if (strcmp(strUserInfo.user, sg_p2p_session->str_P2p_auth.p2p_name) == 0 && strcmp(strUserInfo.passwd, sign) == 0) { + PR_DEBUG("auth success"); + return OPRT_OK; + } + + CHAR_T lk_dm5[32 + 1] = {0}; + tal_md5_create_init(&md5); + tal_md5_starts_ret(md5); + tal_md5_update_ret(md5, (BYTE_T *)(sg_p2p_session->str_P2p_auth.gw_local_key), + strlen(sg_p2p_session->str_P2p_auth.gw_local_key)); + tal_md5_finish_ret(md5, decrypt); + tal_md5_free(md5); + offset = 0; + for (i = 0; i < 16; i++) { + sprintf(&lk_dm5[offset], "%02x", decrypt[i]); + offset += 2; + } + lk_dm5[offset] = 0; + // PR_DEBUG("Client Auth %s %s <-> %s %s ", strUserInfo.user, strUserInfo.passwd, sg_p2p_ctl.str_P2p_auth.p2p_name, + // sg_p2p_ctl.str_P2p_auth.p2p_passwd); + PR_DEBUG("localkey md5:%s final:%s", lk_dm5, sign); + + PR_ERR("auth failed"); + + return OPRT_COM_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +VOID __p2p_thread_exit(THREAD_HANDLE thread) +{ + if (NULL != thread) { + tal_thread_delete(thread); + } + return; +} + +VOID __p2p_rtc_close(INT_T rtc_session, INT_T reason, P2P_SESSION_T* p2p_session) +{ + tuya_p2p_rtc_close(rtc_session, reason); + return; +} + +IPC_STREAM_TYPE p2p_get_chn_idx(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E cur_clarity) +{ + IPC_STREAM_TYPE chn = eIpcStreamVideoMain; + TRANSFER_VIDEO_CLARITY_TYPE type = p2p_clarity_trans(cur_clarity); + + switch (type) { + case eVideoClarityStandard: + chn = eIpcStreamVideoSub; + break; + case eVideoClarityHigh: + chn = eIpcStreamVideoMain; + break; + case eVideoClarityThird: + chn = eIpcStreamVideo3rd; + break; + case eVideoClarityFourth: + chn = eIpcStreamVideo4th; + break; + default: + chn = eIpcStreamVideoMain; + break; + } + + return chn; +} + +TRANSFER_VIDEO_CLARITY_TYPE p2p_clarity_trans(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E type) +{ + if (TY_VIDEO_CLARITY_INNER_STANDARD == type) { + return eVideoClarityStandard; + } else if (TY_VIDEO_CLARITY_INNER_HIGH == type) { + return eVideoClarityHigh; + } + return eVideoClarityHigh; +} + +INT_T p2p_prepare_video_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL != pSession->p_video_rtp_buff) { + return OPRT_OK; + } + + pSession->p_video_rtp_buff = (CHAR_T *)Malloc(P2P_RTP_PACK_LEN); + if (NULL == pSession->p_video_rtp_buff) { + PR_ERR("session:[%d] video rtp buffer malloc failed", pSession->session); + return OPRT_MALLOC_FAILED; + } + memset(pSession->p_video_rtp_buff, 0x00, P2P_RTP_PACK_LEN); + + PR_DEBUG("session:[%d] malloc video send buffer success", pSession->session); + return OPRT_OK; +} + +INT_T p2p_release_video_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL == pSession->p_video_rtp_buff) { + return OPRT_OK; + } + + Free(pSession->p_video_rtp_buff); + pSession->p_video_rtp_buff = NULL; + + PR_DEBUG("session:[%d] release video send buffer success", pSession->session); + return OPRT_OK; +} + +INT_T p2p_prepare_audio_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL != pSession->p_audio_rtp_buff) { + return OPRT_OK; + } + + pSession->p_audio_rtp_buff = (CHAR_T *)Malloc(P2P_RTP_PACK_LEN); + if (NULL == pSession->p_audio_rtp_buff) { + PR_ERR("session:[%d] audio rtp buffer malloc failed", pSession->session); + return OPRT_MALLOC_FAILED; + } + memset(pSession->p_audio_rtp_buff, 0x00, P2P_RTP_PACK_LEN); + + PR_DEBUG("session:[%d] malloc audio send buffer success", pSession->session); + return OPRT_OK; +} + +INT_T p2p_release_audio_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL == pSession->p_audio_rtp_buff) { + return OPRT_OK; + } + + Free(pSession->p_audio_rtp_buff); + pSession->p_audio_rtp_buff = NULL; + + PR_DEBUG("session:[%d] release audio send buffer success", pSession->session); + return OPRT_OK; +} + +OPERATE_RET p2p_send_rtp_data(INT_T client, INT_T channel, CHAR_T *buff, INT_T length) +{ + if (channel < TUYA_VDATA_CHANNEL || channel > TUYA_ADATA_CHANNEL) { + PR_ERR("input errorclient[%d]channel[%d]", client, channel); + return OPRT_INVALID_PARM; + } + INT_T ret = 0; + // Send data + if ((0 == (P2P_VIDEO & sg_p2p_session->cmd)) && (0 == (P2P_PB_VIDEO & sg_p2p_session->cmd)) && + (0 == (P2P_AUDIO & sg_p2p_session->cmd)) && (0 == (P2P_PB_AUDIO & sg_p2p_session->cmd))) { + return OPRT_OK; + } + ret = tuya_p2p_rtc_send_data(sg_p2p_session->session, channel, buff, length, -1); + if (ret != length) { + PR_ERR("Write data failed [%d][%d]", ret, length); + } + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_ext_protocol_pack + * Note:Transport extension protocol packet assembly + * Input: client channel number, pResult result buffer, type 0/1 video/audio + * Output: pResultLen result buffer size + * Return: + ***********************************************************/ +STATIC VOID __p2p_ext_protocol_pack(INT_T client, INT_T type, CHAR_T *p_result, INT_T *p_result_len) +{ + if (NULL == p_result || NULL == p_result_len) { + PR_ERR("input error"); + return; + } + + INT_T fix_len = 0; // 20180428 supplementary header data + UINT64_T tmpTime; + INT_T ipcChan = client; + IPC_STREAM_E curClirtyChn = p2p_get_chn_idx(sg_p2p_session->cur_clarity); + C2C_AV_TRANS_FIXED_HEADER *pav_Info = (C2C_AV_TRANS_FIXED_HEADER *)p_result; + + if (0 == type) { + tmpTime = sg_p2p_session->v_timestamp; + pav_Info->request_id = sg_p2p_session->video_req_id; + if (TRUE == sg_p2p_session->key_frame) { + fix_len = sizeof(C2C_AV_TRANS_FIXED_HEADER) + EXT_PROTOCOL_V0_LEN; + pav_Info->extension_length = 8; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER)] = TY_EXT_VIDEO_PARAM; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 1] = 0; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 2] = + (SHORT_T)sg_p2p_session->av_Info.width[curClirtyChn]; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 4] = + (SHORT_T)sg_p2p_session->av_Info.height[curClirtyChn]; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 6] = + (SHORT_T)sg_p2p_session->av_Info.fps[curClirtyChn]; + } else { + fix_len = sizeof(C2C_AV_TRANS_FIXED_HEADER) + 4; + pav_Info->extension_length = 0; + } + } else { + tmpTime = sg_p2p_session->a_timestamp; + pav_Info->request_id = sg_p2p_session->audio_req_id; + fix_len = sizeof(C2C_AV_TRANS_FIXED_HEADER) + EXT_PROTOCOL_V0_LEN; + pav_Info->extension_length = 8; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER)] = TY_EXT_AUDIO_PARAM; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 1] = 0; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 2] = (SHORT_T)sg_p2p_session->av_Info.audio_sample; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 4] = (SHORT_T)sg_p2p_session->av_Info.audio_channel; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 6] = (SHORT_T)sg_p2p_session->av_Info.audio_databits; + } + pav_Info->time_ms = tmpTime; + *p_result_len = fix_len; + + return; +} + +STATIC OPERATE_RET __p2p_check_free_buffer_size(INT_T client, INT_T channel, INT_T len) +{ + OPERATE_RET ret = OPRT_OK; + INT_T sendFreeSize = 0; + INT_T writeSize = 0; + + ret = tuya_p2p_rtc_check_buffer(sg_p2p_session->session, channel, (uint32_t *)&writeSize, NULL, + (uint32_t *)&sendFreeSize); + if (OPRT_OK != ret) { + return ret; + } + + INT_T rtp_cnt = len / RTP_MTU_LEN + 1; + INT_T need_size = rtp_cnt * 1600; // kcp send, one segment occupies 1600 bytes + if (need_size > sendFreeSize) { + STATIC INT_T retry_sum = 0; // Total retry count when buffer is full + if (retry_sum % 100 == 0) { + PR_ERR("Check_Buffer not enough writeSize[%d] sendFreeSize[%d] len[%d] session[%d] channel[%d]", writeSize, + sendFreeSize, len, sg_p2p_session->session, channel); + } + retry_sum++; + ret = OPRT_RESOURCE_NOT_READY; + } + return ret; +} + +/*********************************************************** + * Function: __p2p_pack_h265_rtp_and_send + * Note:IPC stream data assembly RTP and send + * Input: pData data header address, len data length, client channel number + * Output: none + * Return: + ***********************************************************/ +STATIC OPERATE_RET __p2p_pack_h265_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len) +{ + if (NULL == pData) { + PR_ERR("input error"); + return OPRT_INVALID_PARM; + } + + OPERATE_RET ret = __p2p_check_free_buffer_size(client, TUYA_VDATA_CHANNEL, len); + if (OPRT_OK != ret) { + return ret; + } + + if (NULL == sg_p2p_session->p_video_rtp_buff) { + PR_ERR("video rtp buffer is NULL"); + return OPRT_INVALID_PARM; + } + + RTP_PACK_NAL_ARG_T rtp_pack_nal_arg; + rtp_pack_nal_arg.client = client; + rtp_pack_nal_arg.channel = TUYA_VDATA_CHANNEL; + rtp_pack_nal_arg.p_rtp_buff = sg_p2p_session->p_video_rtp_buff; + memset(rtp_pack_nal_arg.ext_head_buff, 0, P2P_EXT_HEAD_MAX_LEN); + __p2p_ext_protocol_pack(client, 0, rtp_pack_nal_arg.ext_head_buff, &rtp_pack_nal_arg.fix_len); + + void *pRtpDelegate = NULL; + struct rtp_payload_t rtp_packer; + rtp_packer.alloc = rtp_alloc; + rtp_packer.free = rtp_free; + rtp_packer.packet = rtp_pack_packet_handler; + uint16_t seq = sg_p2p_session->video_seq_num; + uint32_t ssrc = 10; + uint32_t timestamp = (UINT_T)sg_p2p_session->v_pts; + pRtpDelegate = rtp_payload_encode_create(/*H265_PAY_LOAD*/ 95, "H265", seq, ssrc, &rtp_packer, &rtp_pack_nal_arg); + ret = rtp_payload_encode_input(pRtpDelegate, pData, len, timestamp); + if (OPRT_OK != ret) { + PR_ERR("rtp_payload_encode_input h264 error:%d", ret); + } + rtp_payload_encode_getinfo(pRtpDelegate, &sg_p2p_session->video_seq_num, ×tamp); + rtp_payload_encode_destroy(pRtpDelegate); + + return ret; +} + +/*********************************************************** + * Function: __p2p_pack_h264_rtp_and_send + * Note:IPC stream data assembly RTP and send + * Input: pData data header address, len data length, client channel number + * Output: none + * Return: + ***********************************************************/ +STATIC OPERATE_RET __p2p_pack_h264_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len) +{ + if (NULL == pData) { + PR_ERR("input error"); + return OPRT_INVALID_PARM; + } + + UINT_T max_frame_size = /*tuya_ipc_media_adapter_get_max_frame(0, 0, 0)*/ (300 * 1024); + if (len > max_frame_size) { + PR_ERR("frame len too big[%d]", len); + return OPRT_INVALID_PARM; + } + + OPERATE_RET ret; + ret = __p2p_check_free_buffer_size(client, TUYA_VDATA_CHANNEL, len); + if (OPRT_OK != ret) { + return ret; + } + + if (NULL == sg_p2p_session->p_video_rtp_buff) { + PR_ERR("video rtp buffer is NULL"); + return OPRT_INVALID_PARM; + } + + RTP_PACK_NAL_ARG_T rtp_pack_nal_arg; + rtp_pack_nal_arg.client = client; + rtp_pack_nal_arg.channel = TUYA_VDATA_CHANNEL; + rtp_pack_nal_arg.p_rtp_buff = sg_p2p_session->p_video_rtp_buff; + memset(rtp_pack_nal_arg.ext_head_buff, 0, P2P_EXT_HEAD_MAX_LEN); + __p2p_ext_protocol_pack(client, 0, rtp_pack_nal_arg.ext_head_buff, &rtp_pack_nal_arg.fix_len); + + void *pRtpDelegate = NULL; + struct rtp_payload_t rtp_packer; + rtp_packer.alloc = rtp_alloc; + rtp_packer.free = rtp_free; + rtp_packer.packet = rtp_pack_packet_handler; + uint16_t seq = sg_p2p_session->video_seq_num; + uint32_t ssrc = 10; + uint32_t timestamp = (UINT_T)sg_p2p_session->v_pts; + pRtpDelegate = rtp_payload_encode_create(/*H264_PAY_LOAD*/ 96, "H264", seq, ssrc, &rtp_packer, &rtp_pack_nal_arg); + ret = rtp_payload_encode_input(pRtpDelegate, pData, len, timestamp); + if (OPRT_OK != ret) { + PR_ERR("rtp_payload_encode_input h264 error:%d", ret); + } + rtp_payload_encode_getinfo(pRtpDelegate, &sg_p2p_session->video_seq_num, ×tamp); + rtp_payload_encode_destroy(pRtpDelegate); + + return ret; +} + +/*********************************************************** + * Function: __p2p_pack_aac_rtp_and_send + * Note:IPC stream data assembly RTP and send + * Input: pData data header address, len data length, client channel number + * Output: none + * Return: + ***********************************************************/ +// STATIC OPERATE_RET __p2p_pack_aac_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len) +// { +// if (NULL == pData) { +// PR_ERR("data[%p] client num [%d]",pData, client); +// return OPRT_INVALID_PARM; +// } +// //Process according to 1-n frames +// INT_T i; +// OPERATE_RET ret = OPRT_OK; +// ADTS_HEADER strAdts = {0}; +// INT_T audioRtpLen = 0; + +// PR_DEBUG("aac audio len[%d]",len); + +// ret = __p2p_check_free_buffer_size(client,TUYA_ADATA_CHANNEL,len); +// if (OPRT_OK != ret) { +// return ret; +// } + +// if (NULL == sg_p2p_session->p_audio_rtp_buff) { +// PR_ERR("audio rtp buffer is NULL"); +// return OPRT_INVALID_PARM; +// } + +// INT_T fix_len = 0; //20180428 Added header data +// CHAR_T ext_head_buff[P2P_EXT_HEAD_MAX_LEN] = {0}; //Based on extended video header protocol +// head+ext(8)+rtp_len + +// __p2p_ext_protocol_pack(client, 1, ext_head_buff, &fix_len); + +// for (i = 0; i < len;) { +// //ADTS header parsing +// if (OPRT_OK != tuya_ipc_parse_adts_header((UCHAR_T * )&pData[i], &strAdts)) { +// i++; +// continue; +// } +// tuya_ipc_show_adts_info(&strAdts); +// PR_TRACE("parse aac frame length = %d len[%d]",strAdts.aac_frame_length,len); +// //Length verification +// if (i + strAdts.aac_frame_length > len) { +// PR_ERR("calc len error parse index[%d]aac_len[%d]len[%d]",i, strAdts.aac_frame_length, len); +// return OPRT_COM_ERROR; +// } +// PR_TRACE("parse aac i[%d] data_len[%d]",i,strAdts.aac_frame_length - ADTS_HEADER_LENGTH); +// if (strAdts.aac_frame_length - ADTS_HEADER_LENGTH < P2P_RTP_PACK_LEN) { +// if (OPRT_OK == tuya_ipc_pack_aac_rtp((BYTE_T * )(pData + i + ADTS_HEADER_LENGTH), +// strAdts.aac_frame_length - ADTS_HEADER_LENGTH,\ +// &audioRtpLen, sg_p2p_session->p_audio_rtp_buff + fix_len,client)) { + +// memcpy(sg_p2p_session->p_audio_rtp_buff, ext_head_buff, fix_len); +// *(int *)&sg_p2p_session->p_audio_rtp_buff[fix_len - 4] = audioRtpLen; +// audioRtpLen += fix_len; + +// ret = __p2p_send_rtp_data(client, TUYA_ADATA_CHANNEL,sg_p2p_session->p_audio_rtp_buff,audioRtpLen); +// } +// } else { +// PR_DEBUG("aac data too big [%d] [%d]",P2P_RTP_PACK_LEN,strAdts.aac_frame_length); +// } +// i += strAdts.aac_frame_length; +// PR_DEBUG("parse aac i[%d]",i); +// } +// return ret; +// } + +/*********************************************************** + * Function: __p2p_pack_g711_rtp_and_send + * Note:IPC audio data assembly RTP and send + * Input: pData data header address, len data length, client channel number, mode g711 mode + * Output: none + * Return: + ***********************************************************/ +STATIC OPERATE_RET __p2p_pack_g711_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len, INT_T mode) +{ + if (NULL == pData) { + PR_ERR("data[%p] client num [%d]", pData, client); + return OPRT_INVALID_PARM; + } + + if (len > P2P_RTP_PACK_LEN) { + PR_ERR("data too big %d", len); + return OPRT_INVALID_PARM; + } + + OPERATE_RET ret = OPRT_OK; + ret = __p2p_check_free_buffer_size(client, TUYA_ADATA_CHANNEL, len); + if (OPRT_OK != ret) { + return ret; + } + + if (NULL == sg_p2p_session->p_audio_rtp_buff) { + PR_ERR("audio rtp buffer is NULL"); + return OPRT_INVALID_PARM; + } + + RTP_PACK_NAL_ARG_T rtp_pack_nal_arg; + rtp_pack_nal_arg.client = client; + rtp_pack_nal_arg.channel = TUYA_ADATA_CHANNEL; + rtp_pack_nal_arg.p_rtp_buff = sg_p2p_session->p_audio_rtp_buff; + memset(rtp_pack_nal_arg.ext_head_buff, 0, P2P_EXT_HEAD_MAX_LEN); + __p2p_ext_protocol_pack(client, 1, rtp_pack_nal_arg.ext_head_buff, &rtp_pack_nal_arg.fix_len); + + void *pRtpDelegate = NULL; + struct rtp_payload_t rtp_packer; + rtp_packer.alloc = rtp_alloc; + rtp_packer.free = rtp_free; + rtp_packer.packet = rtp_pack_packet_handler; + uint16_t seq = sg_p2p_session->audio_seq_num; + uint32_t ssrc = 11; + uint32_t timestamp = (UINT_T)sg_p2p_session->a_pts; + int payload = 0; + char *codec_name = NULL; + if (TY_AV_CODEC_AUDIO_G711U == mode) { + codec_name = "PCMU"; + payload = 0 /*RTP_PCMU_PAYLOAD*/; + } else if (TY_AV_CODEC_AUDIO_G711A == mode) { + codec_name = "PCMA"; + payload = 8 /*RTP_PCMA_PAYLOAD*/; + } else { + codec_name = "PCM"; + payload = 99 /*RTP_PCM_PAYLOAD*/; + } + pRtpDelegate = rtp_payload_encode_create(payload, codec_name, seq, ssrc, &rtp_packer, &rtp_pack_nal_arg); + ret = rtp_payload_encode_input(pRtpDelegate, pData, len, timestamp); + if (OPRT_OK != ret) { + PR_ERR("rtp_payload_encode_input h264 error:%d", ret); + } + rtp_payload_encode_getinfo(pRtpDelegate, &sg_p2p_session->audio_seq_num, ×tamp); + rtp_payload_encode_destroy(pRtpDelegate); + + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +OPERATE_RET tuya_ipc_delete_video_finish_v2(IN CONST UINT_T client, TUYA_DOWNLOAD_DATA_TYPE type, int success) +{ + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_p2p_set_limit_mode(BOOL_T islimit) +{ + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_init_trans_av_info(TRANS_IPC_AV_INFO_T *av_info) +{ + memcpy(&sg_p2p_session->av_Info, av_info, sizeof(TRANS_IPC_AV_INFO_T)); + return OPRT_OK; +} + +OPERATE_RET tuya_p2p_rtc_register_get_video_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback) +{ + sg_p2p_session->on_get_video_frame_callback = pCallback; + return OPRT_OK; +} + +OPERATE_RET tuya_p2p_rtc_register_get_audio_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback) +{ + sg_p2p_session->on_get_audio_frame_callback = pCallback; + return OPRT_OK; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/*********************************************************** + * Function: __p2p_session_trans_start + * Note:Start p2p transmission, request transmission resources + * Input:pSession session management interface + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_video_start(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || (P2P_VIDEO & pSession->cmd)) { + PR_ERR("param error or video started"); + return OPRT_INVALID_PARM; + } + // Wait for previous data transmission to end + PR_DEBUG("session[%d]video video_start wait_concurr_idle", pSession->session); + pSession->cmd |= P2P_VIDEO; + PR_DEBUG("session[%d] video start success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_trans_stop + * Note:Close transmission + * Input:pSession Session management + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_video_stop(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || !(P2P_VIDEO & pSession->cmd)) { + PR_ERR("param error or session cmd[%d]", pSession->cmd); + return OPRT_INVALID_PARM; + } + pSession->cmd &= ~P2P_VIDEO; + PR_DEBUG("session[%d] video stop success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_trans_audio_start + * Note:Start p2p audio transmission, apply for transmission resources + * Input:pSession session management interface + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_audio_start(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || (P2P_AUDIO & pSession->cmd)) { + PR_ERR("param error or audio started"); + return OPRT_INVALID_PARM; + } + + PR_DEBUG("session[%d] send audio start to dev", pSession->session); + pSession->cmd |= P2P_AUDIO; + PR_DEBUG("session:[%d] audio start success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_trans_audio_stop + * Note:Close transmission + * Input:pSession Session management + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_audio_stop(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || !(P2P_AUDIO & pSession->cmd)) { + PR_ERR("param error or audio not start"); + return OPRT_INVALID_PARM; + } + pSession->cmd &= ~P2P_AUDIO; + PR_DEBUG("session:[%d] audio stop success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_pack_resp + * Note:Response to app query + * Input:pSrc Received data, pPayLoad Queried payload data + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_pack_resp(P2P_SESSION_T *pSession, IN VOID *pSrc, IN VOID *pPayLoad, INT_T len) +{ + CHAR_T *sendBuff = NULL; + INT_T packLen = 0; + INT_T ret = 0; + + if (NULL == pSrc || NULL == pPayLoad || NULL == pSession) { + PR_ERR("param error"); + return OPRT_INVALID_PARM; + } + + packLen = P2P_CMD_HEAD_LEN + len; + sendBuff = (CHAR_T *)Malloc(packLen); + if (NULL == sendBuff) { + PR_ERR("malloc failed len[%d]", len); + return OPRT_MALLOC_FAILED; + } + + memcpy(sendBuff, pSrc, P2P_CMD_HEAD_LEN); + ((P2P_CMD_PARSE_T *)sendBuff)->str_header.type = 1; + ((P2P_CMD_PARSE_T *)sendBuff)->str_header.length = len; + memcpy(sendBuff + P2P_CMD_HEAD_LEN, pPayLoad, len); + + // Send data + // PR_DEBUG("p2p Write data session[%d] chn[%d] len[%d]",pSession->session, TUYA_CMD_CHANNEL, packLen); + ret = tuya_p2p_rtc_send_data(pSession->session, TUYA_CMD_CHANNEL, sendBuff, packLen, -1); + if (ret < 0) { + PR_ERR("p2p Write failed ret = %d", ret); + } + Free(sendBuff); + sendBuff = NULL; + return ret; +} + +STATIC INT_T __p2p_session_cmd_parse_server(P2P_SESSION_T *pSession, VOID *pData) +{ + P2P_CMD_PARSE_T *pCmd = NULL; + C2C_CMD_FIXED_HEADER_T *pFixedHead = NULL; + CHAR_T *pPayload = NULL; + + if (NULL == pSession || NULL == pData) { + PR_ERR("param error"); + return OPRT_INVALID_PARM; + } + + pPayload = pData + P2P_CMD_HEAD_LEN; + pCmd = (P2P_CMD_PARSE_T *)pData; + pFixedHead = &pCmd->str_header; + + switch (pFixedHead->high_cmd) { + case TY_C2C_CMD_QUERY_AUDIO_PARAMS: { + // Query audio parameters (reused for app to query audio types needed for intercom) + // Send query results to client + // PR_DEBUG("recv session[%d] query audio params",pSession->session); + C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E *pAudioResp = NULL; + C2C_TRANS_QUERY_AUDIO_PARAM_REQ_T *pAudioReq; + pAudioReq = (C2C_TRANS_QUERY_AUDIO_PARAM_REQ_T *)pPayload; + INT_T respLen = sizeof(C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E) + sizeof(AUDIO_PARAM_T); + pAudioResp = (C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E *)Malloc(respLen); + if (NULL != pAudioResp) { + pAudioResp->channel = pAudioReq->channel; + pAudioResp->count = 1; + // pAudioResp->audioParams[0].type = sg_p2p_ctl.rev_audio_codec; + // pAudioResp->audioParams[0].sample_rate = sg_p2p_ctl.audio_sample; + // pAudioResp->audioParams[0].bitwidth = sg_p2p_ctl.audio_databits; + // pAudioResp->audioParams[0].channel_num = sg_p2p_ctl.audio_channel; + __p2p_session_pack_resp(pSession, pData, pAudioResp, respLen); + Free(pAudioResp); + } else { + // Send failure message to app + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = pAudioReq->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_FAILED; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + } + break; + } + case TY_C2C_CMD_QUERY_VIDEO_STREAM_PARAMS: { + // Query video parameters + // Send query results to client + // PR_DEBUG("recv session[%d] query video params",pSession->session); + C2C_TRANS_QUERY_VIDEO_PARAM_RESP_T *pVideoResp = NULL; + C2C_TRANS_QUERY_VIDEO_PARAM_REQ_T *pVideoReq; + pVideoReq = (C2C_TRANS_QUERY_VIDEO_PARAM_REQ_T *)pPayload; + + if (NULL != pVideoResp) { + __p2p_session_pack_resp(pSession, pData, pVideoResp, + sizeof(C2C_TRANS_QUERY_VIDEO_PARAM_RESP_T) + + pVideoResp->count * sizeof(VIDEO_PARAM_T)); + Free(pVideoResp); + } else { + // Send failure message to app + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = pVideoReq->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_FAILED; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + } + break; + } + case TY_C2C_CMD_QUERY_VIDEO_CLARITY: { + // Video clarity query + // Video clarity feedback + PR_DEBUG("recv session[%d] query video clarity", pSession->session); + C2C_TRANS_QUERY_VIDEO_CLARITY_RESP_T ClarityResp = {0}; + C2C_TRANS_QUERY_VIDEO_CLARITY_REQ_T *clarityReq; + clarityReq = (C2C_TRANS_QUERY_VIDEO_CLARITY_REQ_T *)pPayload; + + ClarityResp.channel = clarityReq->channel; + ClarityResp.sp_mode = + TY_VIDEO_CLARITY_INNER_STANDARD | TY_VIDEO_CLARITY_INNER_HIGH; // Currently SDK supports fixed format + // p2p_get_clarity(&ClarityResp.sp_mode); + PR_DEBUG("get support clarity[%u]", ClarityResp.sp_mode); + ClarityResp.cur_mode = pSession->cur_clarity; + __p2p_session_pack_resp(pSession, pData, &ClarityResp, sizeof(C2C_TRANS_QUERY_VIDEO_CLARITY_RESP_T)); + break; + } + case TY_C2C_CMD_IO_CTRL_VIDEO: { + C2C_TRANS_CTRL_VIDEO_REQ_T *parm = (C2C_TRANS_CTRL_VIDEO_REQ_T *)pPayload; + PR_DEBUG("CTRL VIDEO session[%d] chn[%d] op[%d]", pSession->session, parm->channel, parm->operation); + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = parm->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_RECV; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + switch (parm->operation) { + case TY_CMD_IO_CTRL_VIDEO_PLAY: { + // When requesting video, save reqId for response + pSession->video_req_id = pCmd->reqId; + // PR_DEBUG("CTRL VIDEO START session[%d] chn[%d] + // op[%d]",pSession->session,parm->channel,parm->operation); + // 20190416add + if (0 != parm->channel) { + PR_DEBUG("session [%d] recv chn[%d]", pSession->session, parm->channel); + pSession->cur_clarity = parm->channel; + } + if (OPRT_OK != __p2p_session_trans_video_start(pSession)) { + PR_ERR("CTRL VIDEO START failed"); + } + break; + } + case TY_CMD_IO_CTRL_VIDEO_STOP: { + // PR_DEBUG("CTRL VIDEO STOP session[%d] chn[%d] op[%d]",pSession->session,parm->channel,parm->operation); + if (OPRT_OK != __p2p_session_trans_video_stop(pSession)) { + PR_ERR("CTRL VIDEO STOP failed"); + } + break; + } + case TY_CMD_IO_CTRL_AUDIO_MIC_START: { + // PR_DEBUG("CTRL AUDIO START session[%d]",pSession->session); + pSession->audio_req_id = pCmd->reqId; + __p2p_session_trans_audio_start(pSession); + break; + } + case TY_CMD_IO_CTRL_AUDIO_MIC_STOP: { + // PR_DEBUG("CTRL AUDIO STOP session[%d]",pSession->session); + __p2p_session_trans_audio_stop(pSession); + break; + } + default: + PR_ERR("CTRL ERROR chn[%d] op[%d]", parm->channel, parm->operation); + break; + } + break; + } + case TY_C2C_CMD_IO_CTRL_VIDEO_CLARITY: { + C2C_TRANS_CTRL_VIDEO_CLARITY_T *parm = (C2C_TRANS_CTRL_VIDEO_CLARITY_T *)pPayload; + // Send to device for processing + C2C_TRANS_LIVE_CLARITY_PARAM_S outParm = {0}; + outParm.clarity = + (parm->mode == TY_VIDEO_CLARITY_INNER_HIGH) ? TY_VIDEO_CLARITY_HIGH : TY_VIDEO_CLARITY_STANDARD; + // outParm.clarity = __p2p_clarity_trans(parm->mode); + PR_DEBUG("set video clarity session[%d]chn[%d] op[%d] clarity[%d]", pSession->session, parm->channel, + parm->mode, outParm.clarity); +// tuya_ipc_media_stream_event_call(0, 0, MEDIA_STREAM_LIVE_VIDEO_CLARITY_SET, (VOID *)&outParm); +#if 0 + //Update reqId for app to distinguish different video files + pSession->video_req_id = pCmd->reqId; + pSession->cur_clarity = parm->mode; +#endif + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = parm->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_SUCCESS; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + break; + } + case TY_C2C_CMD_PROTOCOL_VERSION: { + // C2C_CMD_PROTOCOL_VERSION_T *parm = (C2C_CMD_PROTOCOL_VERSION_T *)pPayload; + // PR_DEBUG("session[%d] recv pro_ver[%d][%d]",pSession->session,parm->version >> 16,parm->version&0xff); + // Version verification processing to be improved later + C2C_CMD_PROTOCOL_VERSION_T proVerRsp = {0}; + proVerRsp.version = (C2C_MAJOR_VERSION << 16) | C2C_MINOR_VERSION; + __p2p_session_pack_resp(pSession, pData, &proVerRsp, sizeof(C2C_CMD_PROTOCOL_VERSION_T)); + break; + } + default: { + PR_ERR("CTRL CMD ERROR[%d]", pFixedHead->high_cmd); + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = 0; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_INVALID; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + break; + } + } + + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_cmd_parse + * Note:Session command parsing + * Input: + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_cmd_parse(P2P_SESSION_T *pSession, VOID *pData) +{ + P2P_CMD_PARSE_T *pCmd = NULL; + C2C_CMD_FIXED_HEADER_T *pFixedHead = NULL; + + if (NULL == pSession || NULL == pData) { + PR_ERR("param error"); + return OPRT_INVALID_PARM; + } + + pCmd = (P2P_CMD_PARSE_T *)pData; + pFixedHead = &pCmd->str_header; + + if (0 == pFixedHead->type) { + // Receive command request, this machine acts as server + return __p2p_session_cmd_parse_server(pSession, pData); + } else if (1 == pFixedHead->type) { + // Receive response packet, this machine acts as client + // return __p2p_session_cmd_parse_client(pSession, pData); + } else { + PR_ERR("pFixedHead->type error %d", pFixedHead->type); + } + + return OPRT_COM_ERROR; +} + +STATIC INT_T __p2p_read_cmd(P2P_SESSION_T *pSession) +{ + INT_T ret = 0; + C2C_CMD_FIXED_HEADER_T *pFixedHeader = NULL; + P2P_DATA_PARSE_T *pDataParse = &pSession->proto_parse; + P2P_CMD_PARSE_T *pReadBuff = (P2P_CMD_PARSE_T *)(pDataParse->read_buff); + ret = tuya_p2p_rtc_recv_data(pSession->session, TUYA_CMD_CHANNEL, pDataParse->read_buff + pDataParse->cur_read, + &pDataParse->read_size, P2P_RECV_TIMEOUT); + if ((ret < 0) && (ERROR_P2P_TIME_OUT != ret)) { + // Exception handling + if (ERROR_P2P_SESSION_CLOSED_REMOTE == ret || ERROR_P2P_SESSION_CLOSED_TIMEOUT == ret || + ERROR_P2P_SESSION_CLOSED_CALLED == ret || ERROR_P2P_NOT_INITIALIZED == ret || + ERROR_P2P_INVALID_SESSION_HANDLE == ret || ERROR_P2P_INVALID_PARAMETER == ret) { + // Session was disconnected by client, need to close session + PR_ERR("session[%d] was close by client ret[%d]", pSession->session, ret); + return -1; + } else { + // Other exceptions to be added later + PR_ERR("session[%d] ###### error ret = [%d]", pSession->session, ret); + return -2; + } + } else { + // PR_DEBUG("recv cmd size[%d] cur_read[%d] + // flag[%d]",pDataParse->read_size,pDataParse->cur_read,pDataParse->flag); Receive data parsing, confirm data + // integrity + if (READ_HEADER_PART == pDataParse->flag) { + if (P2P_CMD_HEAD_LEN == (pDataParse->read_size + pDataParse->cur_read)) { + // Header information read successfully, simple parsing + if (P2P_CMD_MARK != pReadBuff->mark) { + // Header parsing exception, exception handling to be completed later (unlikely to reach this + // condition) + PR_ERR("session[%d] read data error mark[0x%x]", pSession->session, pReadBuff->mark); + } + // Extract data portion + pFixedHeader = &(pReadBuff->str_header); + pDataParse->read_size = pFixedHeader->length; + pDataParse->cur_read = P2P_CMD_HEAD_LEN; + pDataParse->flag = READ_PAYLOAD_PART; + // PR_DEBUG("recv session[%d] cmd size[%d]",pSession->session,pDataParse->read_size); + } else { + // Continue extracting data to ensure header information is complete + if (P2P_CMD_HEAD_LEN < (pDataParse->read_size + pDataParse->cur_read)) { + PR_ERR("session[%d] read data error", pSession->session); + // note Exception handling + // end + return -3; + } + pDataParse->cur_read = pDataParse->read_size; + pDataParse->read_size = P2P_CMD_HEAD_LEN - pDataParse->cur_read; + } + } else { + pFixedHeader = &(pReadBuff->str_header); + if (pDataParse->read_size + pDataParse->cur_read == pFixedHeader->length + P2P_CMD_HEAD_LEN) { + // Data reception complete, enter parsing entry + // PR_DEBUG("session[%d] read data succsess len[%d]",pSession->session,read_size + cur_read); + __p2p_session_cmd_parse(pSession, pDataParse->read_buff); + memset(pDataParse->read_buff, 0x00, SIZEOF(pDataParse->read_buff)); + pDataParse->read_size = P2P_CMD_HEAD_LEN; + pDataParse->cur_read = 0; + pDataParse->flag = READ_HEADER_PART; + } else if (pDataParse->read_size + pDataParse->cur_read < pFixedHeader->length + P2P_CMD_HEAD_LEN) { + pDataParse->cur_read += pDataParse->read_size; + pDataParse->read_size = pFixedHeader->length + P2P_CMD_HEAD_LEN - pDataParse->cur_read; + } else { + PR_ERR("session[%d] read data error", pSession->session); + // note Exception handling + // end + return -4; + } + } + } + + return 0; +} + +STATIC void __p2p_cmd_recv_proc(PVOID_T pArg) +{ + P2P_SESSION_T *pSession = NULL; + INT_T ret; + + memset(&sg_p2p_session->proto_parse, 0x00, sizeof(sg_p2p_session->proto_parse)); + sg_p2p_session->proto_parse.read_size = P2P_CMD_HEAD_LEN; + sg_p2p_session->proto_parse.flag = READ_HEADER_PART; + while (tal_thread_get_state(sg_p2p_session->cmd_recv_proc_thread) == THREAD_STATE_RUNNING) { + if (P2P_SESSION_IDLE == sg_p2p_session->status) { + tal_system_sleep(5); + continue; + } + pSession = sg_p2p_session; + tal_mutex_lock(pSession->cmutex); + if (P2P_SESSION_CLOSING == pSession->status) { + tal_mutex_unlock(pSession->cmutex); + tal_system_sleep(5); + continue; + } + if (P2P_SESSION_RUNNING != pSession->status) { + tal_mutex_unlock(pSession->cmutex); + continue; + } + tal_mutex_unlock(pSession->cmutex); + + ret = __p2p_read_cmd(pSession); + if (0 != ret) { + PR_ERR("session[%d] read cmd failed [%d]", pSession->session, ret); + __p2p_session_clear(pSession); + //__p2p_wait_concurr_idle(pSession, WAIT_ALL_BUF); + __p2p_session_release_va(pSession); + tuya_p2p_rtc_notify_exit(); + printf("pSession->cmd: %d\n", sg_p2p_session->cmd); + } + } + + PR_DEBUG("session cmd proc exit"); + + return; +} + +/*********************************************************** + * Function: __p2p_video_send_proc + * Note:Video data transmission thread + * Input: + * Output: none + * Return: + ***********************************************************/ +STATIC void __p2p_media_send_proc(PVOID_T pArg) +{ + INT_T index = 0; + UINT_T runCnt = 0; + P2P_SESSION_T *pSession = NULL; + OPERATE_RET op_ret = -1; + TY_AV_CODEC_ID type; + type = sg_p2p_session->av_Info.audio_codec; + // type = TY_AV_CODEC_AUDIO_PCM; + + PR_DEBUG("into p2p video send"); + + while (tal_thread_get_state(sg_p2p_session->video_send_proc_thread) == THREAD_STATE_RUNNING) { + if (runCnt % 2000 == 0) { + PR_DEBUG("media send proc alive [%d]", runCnt); + } + runCnt++; + + if (P2P_SESSION_IDLE == sg_p2p_session->status) { + tal_system_sleep(5); + continue; + } + + pSession = sg_p2p_session; + tal_mutex_lock(pSession->cmutex); + INT_T status = pSession->status; + P2P_CMD_E cmd = pSession->cmd; + + if (P2P_SESSION_CLOSING == pSession->status) { + tal_mutex_unlock(pSession->cmutex); + tal_system_sleep(5); + continue; + } + if (P2P_SESSION_RUNNING != status) { + tal_mutex_unlock(pSession->cmutex); + continue; + } + + // The judgment when both are not opened should be placed at the end, otherwise it will appear: users close + // audio and video at the same time, but do not release resources This judgment cannot be omitted, otherwise + // thread idle running will occur + if (!(P2P_VIDEO & cmd) && !(P2P_AUDIO & cmd)) { + // pSession->p2p_buff_stat.live_video = P2P_BUFF_IDLE; + // pSession->p2p_buff_stat.live_audio = P2P_BUFF_IDLE; + tal_mutex_unlock(pSession->cmutex); + tal_system_sleep(5); + continue; + } + tal_mutex_unlock(pSession->cmutex); + + if (P2P_VIDEO & cmd) { + if (sg_p2p_session->on_get_video_frame_callback == NULL) { + tal_system_sleep(10); + continue; + } + MEDIA_FRAME *pMediaFrame = &sg_p2p_session->media_frame; + op_ret = sg_p2p_session->on_get_video_frame_callback(pMediaFrame); // OnGetVideoFrameCallback(pMediaFrame) + if (op_ret == OPRT_OK) { + pSession->v_pts = (pMediaFrame->pts == 0) ? pMediaFrame->timestamp * 1000 : pMediaFrame->pts; + pSession->v_timestamp = pMediaFrame->timestamp; + if (eVideoIFrame == pMediaFrame->type) { + pSession->key_frame = TRUE; + } else { + pSession->key_frame = FALSE; + } + if (TY_AV_CODEC_VIDEO_H265 != sg_p2p_session->av_Info.video_codec[0]) { + op_ret = __p2p_pack_h264_rtp_and_send(index, (CHAR_T *)pMediaFrame->data, pMediaFrame->size); + } else { + op_ret = __p2p_pack_h265_rtp_and_send(index, (CHAR_T *)pMediaFrame->data, pMediaFrame->size); + } + } else { + // Buffer has no data yet + tal_system_sleep(10); + } + } + if (P2P_AUDIO & cmd) { + if (sg_p2p_session->on_get_audio_frame_callback == NULL) { + tal_system_sleep(10); + continue; + } + MEDIA_FRAME *pMediaFrame = &sg_p2p_session->media_audio_frame; + op_ret = sg_p2p_session->on_get_audio_frame_callback(pMediaFrame); // OnGetAudioFrameCallback(pMediaFrame) + if (op_ret == OPRT_OK) { + pSession->a_pts = (pMediaFrame->pts == 0) ? pMediaFrame->timestamp * 1000 : pMediaFrame->pts; + pSession->a_timestamp = pMediaFrame->timestamp; + if (TY_AV_CODEC_AUDIO_AAC_ADTS == type) { + // op_ret = __p2p_pack_aac_rtp_and_send((CHAR_T *)node_a.data, node_a.size,index); + } else if (TY_AV_CODEC_AUDIO_G711A == type || TY_AV_CODEC_AUDIO_G711U == type || + TY_AV_CODEC_AUDIO_PCM == type) { + op_ret = __p2p_pack_g711_rtp_and_send(index, (CHAR_T *)pMediaFrame->data, pMediaFrame->size, type); + } + } else { + // Buffer has no data yet + tal_system_sleep(10); + } + } + } // while + + PR_ERR("video send task exit"); + return; +} + +INT_T __p2p_session_clear(P2P_SESSION_T *pSession) +{ + __p2p_session_all_stop(pSession); + return 0; +} + +/*********************************************************** + * Function: __p2p_session_all_stop + * Note:Close all enabled functions + * Input:pSession Session management + * Output: none + * Return: + ***********************************************************/ +INT_T __p2p_session_all_stop(P2P_SESSION_T *pSession) +{ + tal_mutex_lock(pSession->cmutex); + if (NULL == pSession) { + PR_ERR("param error"); + tal_mutex_unlock(pSession->cmutex); + return OPRT_INVALID_PARM; + } + if (P2P_VIDEO & pSession->cmd) { + pSession->cmd &= ~P2P_VIDEO; + } + if (P2P_AUDIO & pSession->cmd) { + pSession->cmd &= ~P2P_AUDIO; + } + if ((P2P_PB_VIDEO & pSession->cmd) || (P2P_PB_PAUSE & pSession->cmd)) { + pSession->cmd &= ~P2P_PB_VIDEO; + } + tal_mutex_unlock(pSession->cmutex); + return OPRT_OK; +} + +INT_T __p2p_session_release_va(P2P_SESSION_T *pSession) +{ + // All functions closed + PR_DEBUG("release va session[%d]", pSession->session); + tal_mutex_lock(pSession->cmutex); + if (pSession->p_video_rtp_buff) { + Free(pSession->p_video_rtp_buff); + pSession->p_video_rtp_buff = NULL; + } + if (pSession->p_audio_rtp_buff) { + Free(pSession->p_audio_rtp_buff); + pSession->p_audio_rtp_buff = NULL; + } + // memset(&pSession->session, 0x00, sizeof(P2P_SESSION_T) - OFFSET(P2P_SESSION_T, session));//Clear variables + // outside the lock memset(&pSession->str_P2p_auth, 0, sizeof(pSession->str_P2p_auth)); + pSession->cur_clarity = TY_VIDEO_CLARITY_INNER_HIGH; + pSession->status = P2P_SESSION_IDLE; + pSession->cmd = P2P_IDLE; + memset(&pSession->pb_resp_head, 0, sizeof(pSession->pb_resp_head)); + pSession->video_seq_num = 0; + pSession->audio_seq_num = 0; + pSession->key_frame = false; + pSession->v_pts = 0; + pSession->v_timestamp = 0; + pSession->a_pts = 0; + pSession->a_timestamp = 0; + pSession->video_req_id = 0; + pSession->audio_req_id = 0; + // if (pSession->media_frame.data != NULL) { + // free(pSession->media_frame.data); + // pSession->media_frame.data = NULL; + // } + // memset(&pSession->media_frame, 0, sizeof(pSession->media_frame)); + // if (pSession->media_audio_frame.data != NULL) { + // free(pSession->media_audio_frame.data); + // pSession->media_audio_frame.data = NULL; + // } + // memset(&pSession->media_audio_frame, 0, sizeof(pSession->media_audio_frame)); + memset(&pSession->proto_parse, 0, sizeof(pSession->proto_parse)); + memset(&pSession->av_Info, 0, sizeof(pSession->av_Info)); + if (pSession->on_disconnect_callback) + pSession->on_disconnect_callback(); // Notify upper layer when receiving disconnect signal from cloud + tal_mutex_unlock(pSession->cmutex); + return 0; +} + +OPERATE_RET p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var) +{ + OPERATE_RET ret = OPRT_OK; + + // Initialize session information + sg_p2p_session = (P2P_SESSION_T *)Malloc(sizeof(P2P_SESSION_T)); + if (NULL == sg_p2p_session) { + PR_ERR("malloc p2p session failed"); + return OPRT_MALLOC_FAILED; + } + memset(sg_p2p_session, 0, sizeof(P2P_SESSION_T)); + tal_mutex_create_init(sg_p2p_session->cmutex); + // Get password and other verification information + memset(&(sg_p2p_session->str_P2p_auth), 0x00, sizeof(TUYA_IPC_P2P_AUTH_T)); + tuya_ipc_get_p2p_auth(&(sg_p2p_session->str_P2p_auth)); + tuya_ipc_check_p2p_auth_update(); + + sg_p2p_session->cur_clarity = TY_VIDEO_CLARITY_INNER_HIGH; + + // Start media-related threads + THREAD_CFG_T thrd_param = {STACK_SIZE_P2P_MEDIA_RECV, THREAD_PRIO_2, NULL}; + thrd_param.stackDepth = STACK_SIZE_P2P_CMD_RECV; + thrd_param.thrdname = (char *)"p2p_cmd_recv"; + ret = tal_thread_create_and_start(&(sg_p2p_session->cmd_recv_proc_thread), NULL, NULL, __p2p_cmd_recv_proc, NULL, + &thrd_param); + if (ret != OPRT_OK) { + PR_ERR("create p2p_cmd_recv task failed"); + goto RET; + } + thrd_param.stackDepth = STACK_SIZE_P2P_MEDIA_SEND; + thrd_param.thrdname = (char *)"p2p_media_send"; + ret = tal_thread_create_and_start(&(sg_p2p_session->video_send_proc_thread), NULL, NULL, __p2p_media_send_proc, + NULL, &thrd_param); + if (ret != OPRT_OK) { + PR_ERR("create p2p_media_send task failed"); + goto RET; + } + + // Initialize + int bufSize = 300 * 1024; // MAX_MEDIA_FRAME_SIZE + // memset(&sg_p2p_session->tal_video_frame, 0, sizeof(sg_p2p_session->tal_video_frame)); + // sg_p2p_session->tal_video_frame.pbuf = (char*)malloc(bufSize); + // sg_p2p_session->tal_video_frame.buf_size = bufSize; + + memset(&sg_p2p_session->media_frame, 0, sizeof(sg_p2p_session->media_frame)); + sg_p2p_session->media_frame.data = (UCHAR_T *)malloc(bufSize); + sg_p2p_session->media_frame.size = bufSize; + + bufSize = 1280; + // memset(&sg_p2p_session->tal_audio_frame, 0, sizeof(sg_p2p_session->tal_audio_frame)); + // sg_p2p_session->tal_audio_frame.pbuf = (char*)malloc(bufSize); + // sg_p2p_session->tal_audio_frame.buf_size = bufSize; + + memset(&sg_p2p_session->media_audio_frame, 0, sizeof(sg_p2p_session->media_audio_frame)); + sg_p2p_session->media_audio_frame.data = (UCHAR_T *)malloc(bufSize); + sg_p2p_session->media_audio_frame.size = bufSize; + + memcpy(&sg_p2p_session->av_Info, &p_var->av_info, sizeof(TRANS_IPC_AV_INFO_T)); + sg_p2p_session->on_disconnect_callback = p_var->on_disconnect_callback; + sg_p2p_session->on_get_video_frame_callback = p_var->on_get_video_frame_callback; + sg_p2p_session->on_get_audio_frame_callback = p_var->on_get_audio_frame_callback; + + return OPRT_OK; + +RET: + if (NULL != sg_p2p_session->p_video_rtp_buff) { + p2p_release_video_send_resource(sg_p2p_session); + } + if (NULL != sg_p2p_session->p_audio_rtp_buff) { + p2p_release_audio_send_resource(sg_p2p_session); + } + __p2p_thread_exit(sg_p2p_session->cmd_recv_proc_thread); + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +OPERATE_RET tuya_imm_p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var) +{ + return p2p_init(p_var); +} + +OPERATE_RET tuya_imm_p2p_all_stream_close(INT_T close_reason) +{ + // return tuya_ipc_p2p_stream_close(close_reason); + return 0; +} + +OPERATE_RET tuya_imm_p2p_close(VOID) +{ + // return tuya_ipc_tranfser_close(); + return 0; +} + +OPERATE_RET tuya_imm_p2p_alive_cnt() +{ + // return tuya_ipc_p2p_alive_cnt(); + return 0; +} + +OPERATE_RET tuya_imm_p2p_delete_video_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, int success) +{ + // return tuya_ipc_delete_video_finish_v2(client, type, success); + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_download_status(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, IN CONST UINT_T percent) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_download_is_send_over(IN CONST CHAR_T *dev_id, IN CONST UINT_T client) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_download_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, IN CONST void *pHead, IN CONST CHAR_T *pData) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_album_play_send_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST TUYA_ALBUM_PLAY_FRAME_T *p_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_video_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_VIDEO_FRAME_T *p_video_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_audio_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_AUDIO_FRAME_T *p_audio_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_fragment_end(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST PLAYBACK_TIME_S *fgmt) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client) +{ + return 0; +} + +OPERATE_RET +tuya_imm_p2p_playback_send_video_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_video_frame) +{ + return 0; +} + +OPERATE_RET +tuya_imm_p2p_playback_send_audio_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_audio_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_album_play_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +void *rtp_alloc(void *param, int bytes) +{ + int nBufferSize = bytes; + unsigned char *pBuffer = (unsigned char *)malloc(nBufferSize); + memset(pBuffer, 0, nBufferSize); + return pBuffer; +} + +void rtp_free(void *param, void *packet) +{ + free(packet); + packet = NULL; + return; +} + +int rtp_pack_packet_handler(void *param, const void *packet, int bytes, uint32_t timestamp, int flags) +{ + //return 0; + CHAR_T *buf = (CHAR_T *)packet; + INT_T len = bytes; + RTP_PACK_NAL_ARG_T *nal_arg = (RTP_PACK_NAL_ARG_T *)param; + memcpy(nal_arg->p_rtp_buff, nal_arg->ext_head_buff, nal_arg->fix_len); + *(INT_T *)&nal_arg->p_rtp_buff[nal_arg->fix_len - 4] = len; + memcpy(nal_arg->p_rtp_buff + nal_arg->fix_len, buf, len); + return p2p_send_rtp_data(nal_arg->client, nal_arg->channel, nal_arg->p_rtp_buff, len + nal_arg->fix_len); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +INT_T OnGetVideoFrameCallback(MEDIA_FRAME *pMediaFrame) +{ + // TAL_VENC_FRAME_T *pTalVideoFrame = &sg_p2p_session->tal_video_frame; + // if (tal_venc_get_frame(0, 0, pTalVideoFrame) != 0) + // { + // return -1; + // } + // memcpy(pMediaFrame->data, pTalVideoFrame->pbuf, pTalVideoFrame->used_size); + // pMediaFrame->size = pTalVideoFrame->used_size; + // pMediaFrame->pts = pTalVideoFrame->pts; + // pMediaFrame->timestamp = pTalVideoFrame->timestamp; + // pMediaFrame->type = (MEDIA_FRAME_TYPE)pTalVideoFrame->frametype; + return 0; +} + +INT_T OnGetAudioFrameCallback(MEDIA_FRAME *pMediaFrame) +{ + // TAL_AUDIO_FRAME_INFO_T *pTalAudioFrame = &sg_p2p_session->tal_audio_frame; + // if (tal_ai_get_frame(0, 0, pTalAudioFrame) != 0) + // { + // return -1; + // } + // memcpy(pMediaFrame->data, pTalAudioFrame->pbuf, pTalAudioFrame->used_size); + // pMediaFrame->size = pTalAudioFrame->used_size; + // pMediaFrame->pts = pTalAudioFrame->pts; + // pMediaFrame->timestamp = pTalAudioFrame->timestamp; + // pMediaFrame->type = (MEDIA_FRAME_TYPE)pTalAudioFrame->type; + return 0; +} diff --git a/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c new file mode 100755 index 000000000..6f8ffb474 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c @@ -0,0 +1,468 @@ +#include +#include +#include +//#include "uni_log.h" +//#include "gw_intf.h" +#include "cJSON.h" +#include "tuya_ipc_p2p_common.h" +//#include "cloud_httpc.h" +//#include "tuya_ws_db.h" +//#include "mqc_app.h" +//#include "tuya_tls.h" +//#include "iot_httpc.h" +#include "tal_memory.h" +#include "tal_system.h" +#include "tal_time_service.h" + +#include "tal_kv.h" +#include "tuya_iot.h" +#include "atop_service.h" + +#define P2P_AUTH_INFO_UPDATE_RETRY_CNT (20) + +// Force HTTPS POST 2.0 +#define TI_IPC_P2P_CONFIG_GET "tuya.device.ipc.p2p.config.get" +// Force HTTPS POST 1.0 +#define TI_IPC_PASSWORD_UPDATE "tuya.device.ipc.password.update" + +STATIC BOOL_T sg_p2p_passwd_update_flag = FALSE; + +OPERATE_RET httpc_ipc_p2p_cfg_get_v20(IN CONST CHAR_T *gw_id, IN CONST INT_T p2p_type, OUT cJSON **result) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(result); + + TIME_T timestamp = 0; + timestamp = tal_time_get_posix(); + + CHAR_T *post_data = malloc(64); + if (post_data == NULL) { + printf("Malloc Fail.\n"); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, 64); + snprintf(post_data, 64, "{\"type\":%d,\"t\":\"%d\"}", p2p_type, timestamp); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = iot_httpc_common_post_no_remalloc( + // TI_IPC_P2P_CONFIG_GET, "2.0", + // NULL, gw_id, + // post_data, 64, NULL, + // result); + + tuya_iot_client_t *iot_client = tuya_iot_client_get(); + op_ret = atop_service_comm_post_simple(TI_IPC_P2P_CONFIG_GET, "2.0", post_data, NULL, result); + + free(post_data); + return op_ret; +} + +OPERATE_RET httpc_ipc_p2p_passwd_update_v10(IN CONST CHAR_T *gw_id, IN CONST CHAR_T *p2p_passwd, OUT cJSON **result) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(p2p_passwd); + // HTTPC_NULL_CHECK(result); + + TIME_T timestamp = 0; + timestamp = tal_time_get_posix(); + + CHAR_T *post_data = Malloc(128); + if (post_data == NULL) { + PR_ERR("Malloc Fail."); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, 128); + snprintf(post_data, 128, "{\"password\":\"%s\",\"t\":\"%d\"}", p2p_passwd, timestamp); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = httpc_common_post_no_remalloc( + // TI_IPC_PASSWORD_UPDATE, "1.0", + // NULL, gw_id, + // post_data, 128, NULL, + // result); + + op_ret = atop_service_comm_post_simple(TI_IPC_PASSWORD_UPDATE, "1.0", post_data, NULL, result); + + Free(post_data); + return op_ret; +} + +/* tutk:1 ppcs:2 */ +OPERATE_RET httpc_ipc_p2p_cfg_get(IN CONST INT_T p2p_type, OUT cJSON **result) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_ipc_p2p_cfg_get_v20(NULL /*gw_cntl->gw_if.id*/, p2p_type, result); + return op_ret; +} + +OPERATE_RET httpc_ipc_p2p_passwd_update(IN CONST CHAR_T *p2p_passwd, OUT cJSON **result) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_ipc_p2p_passwd_update_v10(NULL /*gw_cntl->gw_if.id*/, p2p_passwd, result); + return op_ret; +} + +/*********************************************************** + * Function: tuya_ipc_p2p_update_pw + * Note:Force update passwd to server + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_update_pw(INOUT CHAR_T p2p_pw[]) +{ + OPERATE_RET ret = OPRT_OK; + cJSON *result = NULL; + + // PR_DEBUG("p2p passwd report %s", p2p_pw); + ret = httpc_ipc_p2p_passwd_update(p2p_pw, &result); + if (OPRT_OK != ret) { + PR_DEBUG("passwd update failed"); + } + cJSON_Delete(result); + + return ret; +} + +/*********************************************************** + * Function: tuya_ipc_p2p_get_pw + * Note:Get p2p_pw + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_get_pw(INOUT CHAR_T p2p_pw[]) +{ + BYTE_T *old_pwd = NULL; + UINT_T old_pwd_len = 0; + cJSON *result = NULL; + BYTE_T new_pwd[P2P_PASSWD_LEN + 1] = {0}; + INT_T rtyCnt = 0; + + OPERATE_RET ret = tal_kv_get("p2p_pwd", &(old_pwd), (size_t *)&old_pwd_len); + if ((OPRT_OK != ret) || (0 == old_pwd[0])) { + if (sg_p2p_passwd_update_flag == FALSE) { + sg_p2p_passwd_update_flag = TRUE; + + // Loop to get pw + while (rtyCnt < P2P_AUTH_INFO_UPDATE_RETRY_CNT) { + TIME_T curtime = tal_time_get_posix(); + memset(new_pwd, 0x00, P2P_PASSWD_LEN + 1); + + snprintf((CHAR_T *)new_pwd, P2P_PASSWD_LEN + 1, "ad%06x", (INT_T)curtime & 0xFFFFFF); + // PR_DEBUG("p2p passwd change to %s", new_pwd); + if (OPRT_OK != httpc_ipc_p2p_passwd_update((CONST CHAR_T *)new_pwd, &result)) { + cJSON_Delete(result); + PR_DEBUG("passwd update failed [%d]", rtyCnt); + rtyCnt++; + tal_system_sleep(500); + continue; + } + cJSON_Delete(result); + break; + } + if (rtyCnt >= P2P_AUTH_INFO_UPDATE_RETRY_CNT) { + PR_ERR("p2p passwd update failed"); + sg_p2p_passwd_update_flag = FALSE; + return OPRT_COM_ERROR; + } else { + snprintf(p2p_pw, P2P_PASSWD_LEN + 1, "%s", (CHAR_T *)new_pwd); + tal_kv_set("p2p_pwd", (CONST BYTE_T *)p2p_pw, P2P_PASSWD_LEN + 1); + } + } else { + PR_DEBUG("p2p passwd wait for passwd update\n"); + INT_T wait_times = P2P_AUTH_INFO_UPDATE_RETRY_CNT; + do { + OPERATE_RET ret = tal_kv_get("p2p_pwd", &(old_pwd), (size_t *)&old_pwd_len); + if (ret == OPRT_OK) { + // PR_DEBUG("get p2p passwd = %s",old_pwd); + snprintf(p2p_pw, P2P_PASSWD_LEN + 1, "%s", (CHAR_T *)old_pwd); + tal_kv_free(old_pwd); + break; + } else { + tal_system_sleep(500); + } + } while (--wait_times); + } + } else { + // PR_DEBUG("get p2p passwd = %s",old_pwd); + snprintf(p2p_pw, P2P_PASSWD_LEN + 1, "%s", (CHAR_T *)old_pwd); + tal_kv_free(old_pwd); + } + + return OPRT_OK; +} + +/*********************************************************** + * Function: tuya_ipc_p2p_get_lk + * Note:Get p2p local_key + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_get_lk(INOUT CHAR_T p2p_lk[]) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + UINT_T wait_lk = 10; + do { + if (strlen(tuya_iot_client_get()->activate.localkey) != 0) { + strcpy(p2p_lk, tuya_iot_client_get()->activate.localkey); + break; + } else { + PR_DEBUG("p2p get local key failed, wait: %d\n", wait_lk); + tal_system_sleep(10); + } + } while (--wait_lk); + // PR_DEBUG("get local_key = %s",p2p_lk); + return OPRT_OK; +} +VOID tuya_ipc_p2p_get_name(INOUT CHAR_T p2p_name[]) +{ + strcpy(p2p_name, "admin"); +} + +/*********************************************************** + * Function: tuya_ipc_p2p_get_id + * Note:Get p2p_id value, tutk only needs id value, separate interface for tutk + * Input: p_auth_param p2p info structure address + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_get_id(INOUT CHAR_T p2p_id[]) +{ + if (NULL == p2p_id) { + PR_ERR("input error"); + return OPRT_INVALID_PARM; + } + BYTE_T *p_auth_str = NULL; + UINT_T auth_param_len = 0; + + OPERATE_RET ret = tal_kv_get("p2p_auth_info", &p_auth_str, (size_t *)&auth_param_len); + if ((ret != OPRT_OK) || (0 == p_auth_str[0])) { + PR_ERR("read p2p_auth_info fails ..%d", ret); + return OPRT_COM_ERROR; + } + + // PR_DEBUG("load str:%s", p_auth_str); + cJSON *p_authjson = cJSON_Parse((char *)p_auth_str); + if (NULL == p_authjson) { + PR_ERR("parse json fails"); + tal_kv_free(p_auth_str); + return OPRT_CJSON_PARSE_ERR; + } + cJSON *p_child = NULL; + + p_child = cJSON_GetObjectItem(p_authjson, "p2pId"); + if (p_child != NULL) { + strcpy(p2p_id, p_child->valuestring); + } + + cJSON_Delete(p_authjson); + tal_kv_free(p_auth_str); + + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_get_p2p_auth(TUYA_IPC_P2P_AUTH_T *pAuth) +{ + if (NULL == pAuth) { + PR_ERR("Invalid Param"); + return OPRT_INVALID_PARM; + } + + memset(pAuth, 0, SIZEOF(TUYA_IPC_P2P_AUTH_T)); + tuya_ipc_p2p_get_name(pAuth->p2p_name); + tuya_ipc_p2p_get_pw(pAuth->p2p_passwd); + tuya_ipc_p2p_get_lk(pAuth->gw_local_key); + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_get_p2p_auth_proc() +{ + OPERATE_RET ret = OPRT_OK; + cJSON *result = NULL; + + ret = httpc_ipc_p2p_cfg_get(TUYA_P2P, &result); + + CHAR_T *tmp_str = cJSON_PrintUnformatted(result); + if (NULL == tmp_str) { + PR_ERR("get p2p auth failed"); + cJSON_Delete(result); + return OPRT_COM_ERROR; + } + + if (ret == OPRT_OK) { + // PR_DEBUG("SY P2P AUTH:%s",tmp_str); + tal_kv_set("p2p_auth_info", (BYTE_T *)tmp_str, strlen(tmp_str) + 1); + cJSON_free(tmp_str); + } + + cJSON_Delete(result); + + return ret; +} +/*********************************************************** + * Function: tuya_ipc_check_p2p_auth_update + * Note:Check if p2p needs update + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_check_p2p_auth_update(VOID) +{ + PR_DEBUG("check p2p auth update or not"); + // After power-on, first check if p2p info needs update, judgment condition (whether there is p2p related info in + // configuration) + + BYTE_T *p_auth_str = NULL; + ULONG_T auth_param_len = 0; + BYTE_T *p_type = NULL; + INT_T p2p_type = 0; + CHAR_T str_p2p_type[P2P_TYPE_LEN] = {0}; + ULONG_T p2p_type_len = 0; + INT_T rtyCnt = 0; + BOOL_T isNeedReLoad = FALSE; + + OPERATE_RET ret = tal_kv_get("p2p_auth_info", &p_auth_str, &auth_param_len); + OPERATE_RET ret2 = tal_kv_get("p2p_type", &p_type, &p2p_type_len); + + if ((OPRT_OK != ret) || (OPRT_OK != ret2)) { + isNeedReLoad = TRUE; + } else { + if ((0 == p_auth_str[0]) || (0 == p_type[0])) { + isNeedReLoad = TRUE; + } else { + // Compatible with old fields of TUYA_P2P + if (0 == strcmp((char *)p_type, "tutk")) { + p2p_type = 1; + } else if (0 == strcmp((char *)p_type, "ppcs")) { + p2p_type = 2; + } else if (0 == strcmp((char *)p_type, "mqtt_p2p")) { + p2p_type = 4; + } else if (0 == strcmp((char *)p_type, "ppcs+mqtt_p2p")) { + p2p_type = 6; + } else { + p2p_type = atoi((char *)p_type); + } + snprintf(str_p2p_type, 4, "%d", p2p_type); + + if (p2p_type != TUYA_P2P) { + isNeedReLoad = TRUE; + } + if (0 != strcmp(str_p2p_type, (char *)p_type)) { + isNeedReLoad = TRUE; + } + } + } + tal_kv_free(p_type); + tal_kv_free(p_auth_str); + + if (TRUE == isNeedReLoad) { + CHAR_T new_str_p2p_type[P2P_TYPE_LEN] = {0}; + snprintf(new_str_p2p_type, P2P_TYPE_LEN, "%d", TUYA_P2P); + PR_DEBUG("update p2p_auth_info from service, type %d", TUYA_P2P); + while (1) { + if (OPRT_OK == tuya_ipc_get_p2p_auth_proc()) { + PR_DEBUG("get p2p auth info from service"); + break; + } + rtyCnt++; + if (rtyCnt % 5 == 0) { + PR_ERR("get p2p auth retry cnt[%d]", rtyCnt); + } + tal_system_sleep(1000); + } + tal_kv_set("p2p_type", (BYTE_T *)new_str_p2p_type, strlen(new_str_p2p_type) + 1); + } else { + PR_DEBUG("no need update p2p_auth_info from service"); + } + + return OPRT_OK; +} + +INT_T iot_gw_reset_cb(VOID *rst_tp) +{ + PR_DEBUG("__begin"); + // Clear p2p_auth_info information + + CHAR_T new_auth[P2P_ID_LEN + 1]; + memset(new_auth, 0x00, sizeof(new_auth)); + if (OPRT_OK != tal_kv_set("p2p_auth_info", (BYTE_T *)new_auth, strlen(new_auth) + 1)) { + PR_ERR("reset p2p_auth_info failed"); + } + CHAR_T new_pwd[P2P_PASSWD_LEN + 1]; + memset(new_pwd, 0x00, sizeof(new_pwd)); + if (OPRT_OK != tal_kv_set("p2p_pwd", (BYTE_T *)new_pwd, strlen(new_pwd) + 1)) { + PR_ERR("reset p2p_pwd failed"); + } + + BYTE_T new_type[P2P_TYPE_LEN + 1]; + memset(new_type, 0x00, sizeof(new_type)); + if (OPRT_OK != tal_kv_set("p2p_type", new_type, strlen((char *)new_type) + 1)) { + PR_ERR("reset p2p_type failed"); + } + return OPRT_OK; +} + +// Called frequently, add variable control +STATIC BOOL_T sg_p2p_passwd_flag = FALSE; +BOOL_T iot_permit_mqtt_connect_cb(VOID) +{ + BYTE_T *old_pwd = NULL; + ULONG_T old_pwd_len = 0; + BYTE_T new_pwd[P2P_PASSWD_LEN + 1] = {0}; + cJSON *result = NULL; + OPERATE_RET ret = 0; + STATIC UINT_T fail_cnt = 0; + + if (TRUE == sg_p2p_passwd_flag) { + return TRUE; + } + ret = tal_kv_get("p2p_pwd", &(old_pwd), &old_pwd_len); + if ((OPRT_OK != ret) || (0 == old_pwd[0])) { + if (sg_p2p_passwd_update_flag == FALSE) { + sg_p2p_passwd_update_flag = TRUE; + + TIME_T curtime = tal_time_get_posix(); + memset(new_pwd, 0x00, P2P_PASSWD_LEN + 1); + + snprintf((CHAR_T *)new_pwd, P2P_PASSWD_LEN + 1, "ad%06x", (INT_T)curtime & 0xFFFFFF); + // PR_DEBUG("p2p passwd change to %s", new_pwd); + if (OPRT_OK != httpc_ipc_p2p_passwd_update((char *)new_pwd, &result)) { + PR_DEBUG("passwd update failed %d\n", fail_cnt); + sg_p2p_passwd_flag = FALSE; + sg_p2p_passwd_update_flag = FALSE; + ret = -1; + if (fail_cnt++ > 20) { + // set_gw_ext_stat(EXT_NET_FAIL); + } + } else { + tal_kv_set("p2p_pwd", (BYTE_T *)new_pwd, P2P_PASSWD_LEN + 1); + sg_p2p_passwd_flag = TRUE; + ret = 0; + fail_cnt = 0; + // set_gw_ext_stat(EXT_NORMAL_S); + } + if (result) { + cJSON_Delete(result); + } + } + } else { + // PR_DEBUG("get p2p passwd = %s",old_pwd); + tal_kv_free(old_pwd); + sg_p2p_passwd_flag = TRUE; + ret = 0; + } + return ret == 0 ? TRUE : FALSE; +} + +// OPERATE_RET mqc_p2p_data_rept_v41(IN CONST CHAR_T *devid,IN CONST CHAR_T * pData, IN CONST INT_T len) +// { +// if (NULL == pData || NULL == devid) { +// PR_ERR("input failed"); +// return OPRT_COM_ERROR; +// } +// return mqc_prot_data_rept_seq(PRO_RTC_REQ, pData, 0, 0, NULL, NULL); +// } From ce1866934aca73f97a15f0014601ba59487e1b98 Mon Sep 17 00:00:00 2001 From: chenyisong Date: Fri, 15 Aug 2025 17:46:26 +0800 Subject: [PATCH 2/5] add camera-demo --- apps/tuya_cloud/camera_demo/CMakeLists.txt | 30 ++ apps/tuya_cloud/camera_demo/Kconfig | 4 + .../tuya_cloud/camera_demo/app_default.config | 0 .../camera_demo/config/Ubuntu.config | 0 apps/tuya_cloud/camera_demo/demo_video.264 | Bin 0 -> 4465130 bytes .../camera_demo/include/reset_netcfg.h | 60 +++ .../camera_demo/include/tuya_config.h | 43 +++ .../camera_demo/include/tuya_ipc_demo.h | 59 +++ .../tuya_cloud/camera_demo/src/reset_netcfg.c | 128 +++++++ .../camera_demo/src/tuya_ipc_demo.c | 232 ++++++++++++ apps/tuya_cloud/camera_demo/src/tuya_main.c | 356 ++++++++++++++++++ 11 files changed, 912 insertions(+) create mode 100755 apps/tuya_cloud/camera_demo/CMakeLists.txt create mode 100755 apps/tuya_cloud/camera_demo/Kconfig create mode 100755 apps/tuya_cloud/camera_demo/app_default.config create mode 100755 apps/tuya_cloud/camera_demo/config/Ubuntu.config create mode 100755 apps/tuya_cloud/camera_demo/demo_video.264 create mode 100755 apps/tuya_cloud/camera_demo/include/reset_netcfg.h create mode 100755 apps/tuya_cloud/camera_demo/include/tuya_config.h create mode 100755 apps/tuya_cloud/camera_demo/include/tuya_ipc_demo.h create mode 100755 apps/tuya_cloud/camera_demo/src/reset_netcfg.c create mode 100755 apps/tuya_cloud/camera_demo/src/tuya_ipc_demo.c create mode 100755 apps/tuya_cloud/camera_demo/src/tuya_main.c diff --git a/apps/tuya_cloud/camera_demo/CMakeLists.txt b/apps/tuya_cloud/camera_demo/CMakeLists.txt new file mode 100755 index 000000000..73787f386 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/CMakeLists.txt @@ -0,0 +1,30 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# APP_PATH +set(APP_PATH ${CMAKE_CURRENT_LIST_DIR}) + +# APP_NAME +get_filename_component(APP_NAME ${APP_PATH} NAME) + +# APP_SRCS +aux_source_directory(${APP_PATH}/src APP_SRCS) + +set(APP_INC ${APP_PATH}/include) + +######################################## +# Target Configure +######################################## +add_library(${EXAMPLE_LIB}) + +target_sources(${EXAMPLE_LIB} + PRIVATE + ${APP_SRCS} + ) + +target_include_directories(${EXAMPLE_LIB} + PRIVATE + ${APP_INC} + ) diff --git a/apps/tuya_cloud/camera_demo/Kconfig b/apps/tuya_cloud/camera_demo/Kconfig new file mode 100755 index 000000000..e29e7dc13 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/Kconfig @@ -0,0 +1,4 @@ +config CAMERA_DEMO + bool + default y + select ENABLE_TUYA_P2P \ No newline at end of file diff --git a/apps/tuya_cloud/camera_demo/app_default.config b/apps/tuya_cloud/camera_demo/app_default.config new file mode 100755 index 000000000..e69de29bb diff --git a/apps/tuya_cloud/camera_demo/config/Ubuntu.config b/apps/tuya_cloud/camera_demo/config/Ubuntu.config new file mode 100755 index 000000000..e69de29bb diff --git a/apps/tuya_cloud/camera_demo/demo_video.264 b/apps/tuya_cloud/camera_demo/demo_video.264 new file mode 100755 index 0000000000000000000000000000000000000000..dca5efa5fd19a284637222216ee9b5219d6aa389 GIT binary patch literal 4465130 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≠K^)Xlk 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 z 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

#E(A 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
2@RfbsUNvzxU3m-f{)X;V
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

Oiq|veTf+ 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

2FAC7U)Pk@Nj!Suu7z8~C-plq7YR$56ZqJ~n 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ΞAx6mkS(1nCOJ! z$Fj;~bcwCHXLtr1WhlN_usqi--^vC!pWSGT4OVEQ5Y*w!??#zbx4V>YZrhtcBv}1@FjRvi!Efw_dUMZxLa|btGU{RP;$`%-1({K% z;ia4=(o99TR8BFwF|U5$b$%PH$0?A(>qz8t#lCA%fmjryfk2(KRt4GTmWH-~C zWa3O{hM&Zr4#vGc|G=K3OCW8!f0bR@ti4gZIV8Y(k(0OzFjViHHyK}-aDXzVNGq;n zw`MEG;*vpum!<@?0`!5XPuN^(al-)>N$6*0C*+z=caJmo`Can_z4 z*#gkjlLy4`7!t&Qj8eGIRho>3m4`**BhZDo`clCw4ZB~4`z|Jh%I^YqgD)96G%QJo z|M6{P{4aJO%ihP5Ajk>O(&>Ma`z$*o3?ideQ>GUH<9}-!k*Zn?TeeWCVLZfH5$)#% zs0ct?B8tE9KwSz2+3mZ$q+W&%{fDmE%vgcg<_HpiU(j%#dP}Y2&r)TiRab=;4rKH%s3oL28N1j(ai4 zV+8R`hQM4O(u;a|9utkwllKe<`+khwE+Ma`!Am4^7HvsUx;`Qa@Hq~Lj*+XF=W&!!7o^Z6 zLDQ+*Gmwm5D@FV&3Zk;#PU;UoAc5Ajw&g_--Dzw3Mt3tII>!-56fSLO@@V(n@J1AA z!wh+3Ft3x;;uaE_0#0?G`}jaBB}c&42EnErNu*k7Auf$WNApy|Z|E6KQIH zB>MFIuvT9$ngKnn;{0?iftRprPajU88G9bePDS7Z-q2A84blQJ9Ujp&Oxxp#x*8R% ze*m&Wn|LGlf0#L^g%&s7!9KoKti^HuHoTKDv{;Oqe?Lhr&9Z~}28rjaX&Qs!@URp9 zv{6_%sxnkaQ6NkO?hSqQn3pOT1jxA28vzJb@m_bd!mhMK^Xb)kW2piFCDJzT)++k* z>Z!2pGxyg_E`7+oeVs7 z@|Hf#44yx1JlZ?MMtV2)#OAXnnn}!&;cxP6-}{TIh4{EwcrPN`OvsC-g97C}HbNig5fVDeOK8C6U2VwF zMM;tEn1^5micqeF)%!Ukkj7Q3{yFf9M_`JwEb6e?o&rqpdkSYZBWP|^@=hvQ^L>X-Pss~F5qcLYO6Z|W>zZ|a zEbRMk=dgxH7;ZmSSsyz7)Z)te-3qPn>~9Gh|Zmzy!5wrU+k;%L!(Nv4$Nk34vV+uSS)&oM-cYm`uX(!_CS>K4VET=oQG5ctJ>Y}jW! z;q_O2mq_#gl)fott@PE;2RCyg-W}>es|dTBjOp1_j$DUttN}UNngH6q=!f%n1*_uu zPAUhD0jW|F3l1#g|KRn``jT4Zay4^3U?Y0Gu{)GKZ(i)9}ia4_)Erq z1^M`K@GR}N|4^a-@lqGGuj~`YL;6i29kk3Crf>G$)L*+N6Yl`}Obn`kyW@3~?oHSE zOn!C>B~gp~NV{7occw4bIVCl8&DFm?lKj|tB=yRXla_#ZiALNU#kqgf7m}%#E&zqH zF-dZ5JiL98MqndtnrbE-%yGrdE;g&sK6-s#vi_rJjeg*A^q+-% zVd!GRe>|O6=t3D}l?L6a#{2C7nm3Na-_FZF7)7Eli;6D=o~h7jPDe2M5}GvZ~x6UuX6YL6b(vAse{(FnA2sdU2Y7b85EO6)%1 zV8XDW$AP;&51{8{QRZinXlxwhEK{6u*T#rcb%11arqf7_FGdW$&2T3GQ914doj19n zN_hW;%1$q)>lIy|{FDa66VN}=*hyZ97$+4p2f@odVYv!{esYl#l4t`lphBbS$Wy*;qmP?~Tb}jEF3z zeuWPFB1U8Dw7-SicZ-R1a86!o5 zU%H}C&cgAAlyt-)Yu~)XLK|&h8L9`LAqt-Ie&5;OGTbQ0>mI>M-4(#Zc%=LgFeWVS ziSbpDi}c7cdCwZ!57di7qT_OirV7R zxggZF+N|Oy=CXw+N;qAy$OvTZ`*?BAn3jBOP7`w9;&af};x(sHV|v@vD>;FFuqsqx z!H=UTjkO^hK_C3L*^L0u!ZqGjD2oABM^mUOj0`$FR1PwDPBYEq&bMSgTrOhXPcuPW)z2z=_3OTJ|cFG5Lyu`O!C zGK;GH^r}dcp*26mwvbR1Nebv5qes*?!m#X00TskCfMl_bnY{UENXH5=Ut>&AhtY_ZmqY()G^SeLq@#r_8QGCpIhPtUncYQGeUH zeAv0wOATNVbu`QL5Ekq-r+9%+KD5;T@;yZE-DLO3{r)gjIp4NeAGMk4aO}(O8{!}I zD$$;Z$rPkBR`D?UpgwW9Vg0bK|%1_?WNS>uDD{w`F((m}`+I>k&(7=^b?;8EznaDXkX3(D9+b-oCc_ z*isNz?{pG~<(L`_l+@n}T^y?07h#{W`D3U$DQ0Qz1k1zqnj_~~*v1lS#PcP--UZ*g zE=x@c>z^YwVo7BVlj1s1DEz24?VdRYdajBX&X$NpQ>t(?mKUfI7-iXetti2+SQknD zWjp7+`Af8iT}1d5K4Ge~j0hR>6w<{Hfk~okPwVP0lWO8f5%tz#Nj33(^75bmL^mh=)|vt?dd1jhF8lmL7gP2!IcPkaAbLf*&{pJ&W)Uk%`q3`{&}&DDqR z&0WCU1#{~&>1dn|KmcsY#hhW{l?_3uEpvG z#{d8T0YTungg*ggAM-Pd`|b0^DXs{1 zh9oK4>RHk`IIv`d#v>!Je%_Dvex@;*74JXW8Eq^EHJ*=t!*N$^QcV3eJy(%jkXVVy z#ywtOJxx`M%>-VYf}q!(&Y@s`S7!yX`Q!6fNKSob9=DfM1)kp|C#-|HB(5%u+$KGF z>Fk_l^SlIKZg^3^qP(?OO!?g`S_R^Ndc@g{L4JYHgb`o?>Y8}+H6gfTTJ zRW+XfsS|?=Bo>Pn&fY<<{lC_=H@bsGmrM3!d>MyJapqsprVN2R=y2i+Cn*olu$^W^pevP9ZD9kq?zLF(pn!Lt9{4@@f#uk%%)s_SjSBTV-5d22e_dx2 z$prh)Q(ZLT<#8SMQN!6thXP>#m z*^K%~M^R~VCQ|D0nf%ae)64W8h0#n4Ch@G zw`|Y;yP-$Vx!-7Rv4Wj=v3cA-Sb8y|AbZDis@srzJigu5Dcp+PJE!ALn*O#UKg8R5 zV}Fmsd1gi8UPB|H3{m)}5%i+9u6=(a-*XvDMf`|qA)gEX-u;2&VcpTIkGK?v(E30+ z{d{mx*B834Dsej!mG;FxA(_s*rWk`VO)ZmUzsX!223?$gVBU2y!0g#>C_*aKc7Z$2 zJL8)(HXBSYZ0epx$WmkRE!z3P0V~4|)QqhLr9YB^e?%=B-(OFViC;i}(p;zVe+4rj zI=KUX3dhqcpP)V~PtVGlhkYV&Qs*;6Ej*#HtvU!Iziita#|>oj5S43Z#b3 zPZAB?3_1Sm95$fR*|~y2ULq&jg@W9v9Hf*C)!p@5!qu2bjb@`SpkPW4(M}1K`BoL8 z*tIqO@5oQ5_DKQ~_~l?-ASg7q9&E#6gVnoSEG}23tS*Y+=sDtkEPjuGvVhIe+WDa} ze*`lYm+Bqnm{6+FTL1*_vppJ{E@JJaeVOn&tIT0W#V?w}d_RbVSG* zlxVr29zR0ve`^mqq??m+6*SeKs9mBqX%uFqnHwE|qn?0Pr7b|%iJztM63UPubSL3xsQ-jpJheTH*fiz+ zT7nMb{ov4Wo8#v&tU!7Szgpw{Sny9XsUh|u(hVGnm+<17(MV9?{)^^L>?zlgA-r^0 zFNF{PrG6kWcIN}YA^bk&T@?t_13g&a<&a>g^hTSzIu}`Q@8{XGQ8?yYCq9Z;3e5?( zFoJ4hN)vK0M^)R&MNaMWr;7>)i|FBkUjMG)jWbz2tqC2;ttf+>mZ)`Y>1{R~OTKT@~cwuxB2JxPwDTP(l%z4CIA(H%^ zbF>wo>goI4SWYseytZSKYb;PYAPa?+H@W^g= zquBri-lYJ~vaQI%b$w|{W4yKFVwq|W*YabY@EpsD&LN-0q!ALHj+L9*Qn4~O4B!O75B*)|5ENBv_61I7AQ)RCQSU^g&|us+T!B>bG#*6qmA^r zVs0XW4Z7sA8ow)knAZIZ9sN3`E14^skTL#HF+2l!JBV9Q<8f$gVEH5Bdug40h-LTG z?Vto$LCWQ1@XV6=ze4}^&-f+cbL=XzonNA_@J`!eH*_K#AB8ZWUZC%#HAyt9OYLZ$ zLN}cWO9(uMLl~H<7#lUg*-5^)7Dm72Jh42G5D?Bs4>O8iCl$Y2+PHTX98Ct+0}563 z?Y~i=%N?E$DbFac4c7m?p0rZJ%nkn~-}8?T{F@tblHTG`{$x2S{F6lSLTG8^)+!3$ zxP?*bnrp>?Y4o#fXZ=_BYj*5)aK^X+?S_1w*sr>9cV-?BF-SWfsU(L4i5z1C$WapJ zI|Si=(@i{C35dY!n9IApJ=hClY0KO6-BQ!CtcVZ@%L5?eK6#@-0Q)D?5|mWkW1H-$$}DwvJOW|4Q=hvi^*R;DfBJ6~&Rh6LDj z1agYu&9;|O$pwC0@n=gxjsc6Q_J6PisVK#qqZLV^_Hf;DH2ebbMsz>-}73! zrPL!q+v#IEut!vTD&Imwh!+CpKAh&^NyR_DX$ODRWBT6{MN;!n@Nu9=-oA|K3!ql} z4xRm99Nm-$#*vzr^llssG5)n++EId& zwaEA#*!Df$QXOn_KgD?JWT!!~J`4zSL-A`=k$y28-lI%kAXrx($zBy}qSM_^Uk*;{ zC8U9>F~5O*%c`sC+Y|BZ>XL#~o+=X|z8EuxgYva_6G@*gq`ieBo)h|zoAi+#U+k#P))j8&uRoTmbAFgMMHOZ`+0GD-umYP9$wn{D-)c>J5>maO#I&D>ap}T zzmTf^>m>K@BmNyhy^}j9U`So_bfp`)JV11wcZh`Kxmzbx*M+kwI7N2V=jkRMQ%N8g z6h;PaufA6$RtQZGhZS5Z=8gREe#z1O^1fAL;Rjy0oKxDI3o5$X;!YD@T5-^%P)IIh z))XD_r_cp07AX)U;}Lb^-JFsXumNw@5!LS+mNbD+80$hH#1D$w z+}2S1x)2+;x<@V+%Nu3BVU+hHv@EfDx%aJOsS2_R@Vktr&lg|2;qP@Hy0x&Gr3u2} zCt#CRy`Qr>Z{Xb+NZ2V)5e>?1>*l6c-dWSS4yDnseCiAn7})ADg1qM1p zaL`bc88mqhN)GmgouYKKdvr451%6tRkywcpUVH<%@C#twVub_C5O|(#T4l*zd)a!7 zC5;I1Ql$G6h>CbNCzBxJ2(mnQ64Y7e-`hDw7fYZXhIDOI3TJcm(cq-iiN+J^*qX6< zAJqoFN3l<|S5H(PojW&xh>>2cf$uG;MCi1?NNMAKbpP&Mfr7Ji^CaaR#M1sBTPbd= z_K50wi0ZA<;t|JyS&w6XQ@jFUb(GuB@obd<&FUQ*?y=^8Evn!tHy30@(>!~)LB zRxBjMPfwb(!YG@-eS7{94nuOZ)t889>c1jfAq zM?d&sqsrWVEoCqa5qJ7TK!C(bdfWCwvyqhL3?b`#?h7z~mOhM4Y88d1`eGxCkHha5 zvBdowLAgWJz%U> z0CViy&kp$hz7@FI_czDDvy8^c>6b8tCS{)LpEQJ_<#kD?s#wR)8ba{;Hmiz<*uP$z zMYd*TX{dr7R@Sh}Cbmdq?UzQXhf#87vFNBl-yCEe7~>_ueo$Agm79PYu7ItMCUi|` zqenY-MsH$SXAqkb692=ZMW zOL;HK0>C;vSj4?2cB|G7Kzv%WhW=Wa3Ev2-1Y@o?6pM+DRr&& z=9@%MHV3Wca|U1#=AIJ_TDtL>iSmX*jcXhhy)N}uJqQSP*_8rj>sYUM7E* z^lYg?5z6;iAYwYW*lQ1t3!A}8NlFz9&l9@BqgK>QuHe_K>ae-AhD1EMl74CL0^51+ zp(!wYSZ!(og_M(o|LT)sm2`bEDsM70y8B~iEv3rpZ6A2!tpjHC?8AJ(A+Q7{L>J8E zFK2k2Y8ZUzj%}Iwrq=dL>I565*D{?%&6#V65>5HtQ*W;n2KT}b4^(?@nL6F08eB~A zRCi}W8#bc73dXQ_{BasN#7ts5yiz`tBm|}*3Mz=vw3Lf>u}7sxmFgvaf}Hzyf&egWaO%#jpa#w;BNN3cObxXgy`IKU^{y&k|NvhI};8D+1bL* zui_-OugPcvlg`552fL8>SJ}>lyw)8Z42@j%@zWr9#Wmk*j6$!siF~yo8VaEhUVTZS z7_5h!yxPP8qC}?|`&L2MO{>mzxPFZ!COdwieihXobXOAyCNGU!gL?*`NClh5$tR!l zmxcoPh=N1rvTo%g;+eLIBuUr)?iJY_euN4AKdjA#o#&{(0b;6VP-anq`(tb#wxeoK zbEfV-TR`JNH=_m?q1mBy$a=n`I9Eb_(vR*Pj@bi#(ziXQ@iswDs1@j1aXb6Mll+m9 zf^P2%WepodcL%4_P#8>u(BhEHthdg&R2K-tv`)30i=^jXi0PRAqB*V=)v9rr>pd2s@Ob7ZgTbfl8Qn3`|S>oB3D(0-pv3pU6%7 zn)Ql^`QI2@7w59MG+)1*YD0o;t1t=4?+0)I0003&;J}1G)it1|&h_+{K>>nR0v@CM z>Bbbns^0j{Tw_7-Z$)hHnJ%~rsUpl8?j-@QC<44Y21!3)GV*!?R<)wIxTJz7M6b+Y z*hywCZMTdIZeLc5GcuF@IH-^ire8Qqy7<>w?h|{Z=w&7hfKR4&?#lC)#Z4bGs?Hp#Iw^A4rvP9c^4e z{zHd(PqHos6$X;-%;i1HefebXW&#`3ugISFyS}|A-Q;_RrG;MsvoA_&j9HGm`U!~<&p;hD{iET7p*$&3Uz{eeBQp~!$=kt(ryVb z#n!$TbyH6P#~7;qG*G)ZAN^GXm8}aJ%luG$WE5huke?9R_Am1mcWyJ`s*LTli7$zk zQ=<+zByMpWe!ymcH|@eUFe1X?T-Hne`Z=;>4nu!U$mz7CU;P)9!_-wJZWm)Va(26h zaTE_uQ^V!Al7eG8WkBBGr{FB%IiUH5ckRWj7m?W^R_ORX&8K?~2#O>jJzHPKxoh_C z0P7DB6<>w2p|9V+$Qt!hH=nx@!#I^iUe(^M{uBB=6M(6sNdVqp4Q!k+vDKNHf&SdX zm!3Fm@(A5&of+XV+tM!m;Fhs4V!Aa;sM5Hxl0Bij@08}-zFQZk?m z3MZx(IDUie3Go|uMp?_zwM1Ux`xD8(6iRmF(C7k0c8qMwo0p4hpq82i%VNE| zoPq|%aMi8;9EjyWgZA7?f>|Icj@68^!u2<|zGi?aT>vRBv`L$gSA;f44cfw0%1y{2 zjKr7;^yVbfeC*>q5@n+#8p|UWKEeuLAPA3=!ft9+vgD8epLlA4TjR$XuN8vE=Urf+ z9TJ!Mh>}foNd5QqXaKBR*p%7237%tAhdpULhDBvD8%E96r>43}l@A6wG56j*{xbYV zD+J$RPE-*Tgv3^?aJC;plzoCL?1=xeb~I~r;#ijTq$sP`z2tV@x-*L=z)y5`p=dkv8Nioz}?Z!Q&2{?5iOJhA&`5UozSGx$; zyQu+iD8{z~xV^l`JtuX?oDBw5)pGJh{BK2>QQXoKUC2YsTP7FTwgVqe34%?m(Z%d? zyPU4R1ta8)Hz*$G1#nW*v}f5HjIJ&uLZN8|%Z7%=gx6T4=G=0f6T1b+)*m5+9hN4= zJ)4~-M-l7)v`M&Y4s|GF57*%Lv1?lAU5K*$3q*JUVQAuRTB1i5|ALOSSxQnkc_;KE@h$sYh+;f#rc~wVz5=)< z!7Nx^!~d5Og+u7z!zREYk8yI7qp(07)`=_e*?WDgb8t0I;!=C8w0(cvxIrwf^|+4Q zD*>hMnD6?c>Q}@cdC67_{m4;@qs#F5Qmgfnto>zhiy7E%pJ#+hTG znP*uHD8kfSys)4q%OG4HT&bo$j!I$ zLctU~RlJ9k5k6pBxbh?=MKj?;`bo$ug*=f12$OLZZQ-M4I})X!XU7{d3nkW7d43Y)lFTdOAM z(}qB9@C}nd(UTw)Ed_}J*IniSSIO%B@Z*oXPee*T)QIF(I8qCfOy$)ti_qTF%5YJW zm#6*YPt%>=kMo!u9`e0;a2tcf(_95|>jRBfcj<1(OC?+SV&s9ZVi)|k9_FG5)wLti zD%&F79jSDkwo$8_5`%toqZJ}yE~FE_z9mSgp@xxjFk8B)ZdD?wVu6|B9USP+IauD)D>h%OWYxEQV9s*^h;Ps!T-L9=#2fXq~ys!z8tY#R&=b?91jj)gA8x{-xnY6Swu@R5D~9+wnt4ln?iCC zlX#lIoGh8>1PV)$s$r|&n}YW}gJ%U8u5px1Ad{k!*!T7wmt-3c4$TO9>*s9%rEh?v z`~{bW=_ZE@4XP|izfL<7cUIDTN*AnmzF!z_xqA4>HmPqShlba%%^VZ8D%i!7GUKF8 z!RiV|F|FArXz|s5X+T&DLD`GQJV}ns!QVu*=qTp4t%bOKj9I3BPo`LXyK9C`B-c0B zIQ{sY#MHn=1g={?IOg7TElKHEWsH~ycm9gpZhc;-jUpo)w4<+o3WbZ+e&_IsMT(xl zshW^hM-6MMTd-txAO%77;^P99JzvG_LploJQ2ZzFdCEY{qK5PMa<^4i3NgRCxTC}V zs-vh*(MSw7J8%@ioDbX8km$_S6id!#@|5rXR%x~S)HwsrN7*^*rE`P$O)ZJfX zPQiQxG4s&eU(s6JHvykb14n0%D6b|Qe(Ka291Lr$vQ%N3-9nO}dx; zefdDspns*xQ2SlE@|3C-A{XeB7SKM<16zG?3;}<+szsefAoMDni5SYyNB|E{-wRv+RCR z8=KWEdhCYkUq6T8y}XN~+(Z2)$$A{c9&N4X@AMvSF)jaeFg>9~!>OpYg}2`t)&+e4E#e`4U>7$#*{Jw+ z^!DUPTLxpHYAIQ1eCso{uE&mbQm_*E&njeYcDiLbq6MuyD|QMp0(8L!ap_QVBcS%! zg#lEIlqH(L+-cYO%$~yJIi``l&KUfp7rz!OFAgPKf3c_AqwZ^f#u=2OffydVAQ(L9 zr_Kv!L#UCVgYIH_qIu}2X$1)Lp_{A)uUlmlT>!C+!xK6WD|2(NlZMa^I@EPHB6;eL z@&@BHsj8eUuu~+$=>#-VSBTvH(V%D<4vb^JhuCQkA(_?7&hF`(az3cz^!K1 z^-U4B+KVG7A9pZ^HElW7pT_KI*k~>VmGgO?&EmU(P=X>y7S!@nY}E*fB0i9!N3ha_ z^Mgi$cJBPiw+Tx!Pgl;9z2a)pK`;W<`41i(NMfaB1xr`4hpxk~rB{1oQ}gjE+sRs^ zaNdcs4W^iSZpCW?=C30Oh4ERAFPm#Bjt8~#^%bH2M~&UC^`hevls1Uo1B5yHJ}d>t z+dZdXWxfIMd2uG4YqD={mD@wY7z+-XGpntiz?oA5 zl$BM53U(C@>2Z1;k662@D_sKs`lS~yHQE}^8oH@!TY1Kk`RNDOg9QVTT|TQQe?$dN z+m&0V$~OtKnTHX$n&pECWRcCsLk7a6j`_l1=c!*iFAMd0m~kHEZ?nYcSH^yqCskfPl=6 zxTk6@_t<82JldpLTlX7UK5F|Tb5bTU;jN-+%BwT7gY>lv(M;*iS3RN25ICgr}G0W&W?%7YUzSdi^X|65;tt9$thx(l`JB00BYZ$b>&FWzbV^ znS?Mzg}t?_4U`}M<+DAEx9>>tKgnd7ahCVxv4XUM9V&Los)`aV*_W^*7AcZYksB#9 zl58eX^Nq#nfkTWEo9};^2Qg;^rTIed0(>}unKv6U!;SM$K+`G-{B56#1(!^_D)T_v z!^M39krG&t8yncUvBTwSX8d~m^1CvYC_cPdY#ME?-yPcGIrKl_Md$diW(JHtm)&)K zSP1j@nmZg6B#YT8p>Rj5ApdF8t!p5N1}27|fp%whC}(Yj;3q)4$v~mq4jeZyWuKTN z!JiL9X(day2_+FiC@DIL8|lse#y*C*Hq)?_qTe;WbJ3a8;6Z=B2{&Z)mh9OZ_^Hz{ zc`KdDh7pQDuwFDxMq%$lBwvDo9kTZ{^}Hn4cg(s=6|Sg+Q*l{k-hKT7%=7S_1TE`G zIH=fR*YFYZ6LoDN&5R=eRVk#lz#{1()Tz+NiW7pmVDX2qW$7x`PM7z8P*7c~qOn4i zP&QS6G~(pYoIUOP^GgoDUfm14aM3OMzDHi*7o-vx{Ls$|unuHo+K|1VM~7CT7iuwj z{D*Z_AG1LWZTkCNBGA0Xa3*UYdby@c!rHqEQh{AXE0G$!=m9M}BRj`+y-v)`6*D|H7v>z+yXMDt`l-k%4^r}DQWQ>@j0fEEYCcDECG}%xh?BENIjSR_uto-Ku%=24io#*u zA%@S1K7q+Kb{XZg6)kD~1MIuGd>vyPyJnK}T`yx!)G&qxjV`ywbUY?e4hlbFO+e`FR7Ya2zhS3THVRTvp6KMS4ZW7Q$mf{q8!u zo@(FkOEg6D=`Q0*7W5gGMx`xgBz)yZd%%=3%8IErpaPHJpL$t`kz0oA7F9KilCXa2 zrV`tB=suIH5q2;3NM#op5R!}6lx*Yqv`HWxH}Sex{6p*av2?rTBni;@k26^p`F+YC z#B01Q0XQuj^DDN`MNoFW=7o$8g2GzuA~-yvBb)vfYIL z(W1?qS_IRu1uxGib$O72+AQhyk^=yBSBi}cDbn_vDN_~IPk7Oqp+&KP=3ZOg5I~B|NYo;h#oKcZI}H{#w11xF4vW& zFZ>XZP~i;9^Z^a{0qnxPLhu$fdKm7Gi%5Zbcp?^CWGi5 zFS?Xm^CZUn+S1IR2Af%A6VP*V?}9)~{dc>3$374_ASN1r2V;{@X?T)%6i_-XK*@qu zQ_)R=GgGjS$*pHCd0!D=rLEx5%_N;iwv*03SEAC14rpw8mqvkqw&f;!9^cz1%YNWm z&Xnfgp3{k}=n8m%wU`;;LO*~p6cHJ0R+E_85P{cC=b`KkrwS~PsoFI7Mo-zSv}q@) z^&B$)otMILj8C1a1IXMzP6VYAES43MWir?ozrb*-v{1_DM_gh8v%^9;wv)&pw~f?a z2je{7ftj?~{f*wgtNXZu4DPFFfrC86Dzy?u(ypDAPqJlP?@D#P!^+X;{A@elE88l>Rcljb(+G90S>_m)yf8$$^;rGQ-y0FJQ5Me zym3IwhpOh<3TAcDAoYqa=vx$06Eg>Qe+>{#(R^9`+1sIlVUMq;A?gF_C8;00yoi)`qpZ-D%OJfXDsPwrGm2}vNhLvAyNoFN_mDg^Ja zJZ;P9^K4Z8Z(|sYvcktK2I3e&IeB(dfKWIE+bc4wH4Q36dRJ+)OeebOJ`_~l@Pn2q zPm!P8DjK=)7Ihk{ z!6oxrheu+*UhdFV5H_Ibdx-+^v9Gqb?HW)wNg;9Qm$<+j0(X`D!od3}(`#(>0|jX3 ztc-SM@5Ty@g9JqcwHx?rr1=*XefD&L#}1$i&HeE*g+QA|SA~Ww;GF++n*H6FgLN!x z)x)fRLh^%1qIS9uu; zs}Xx9g#&%V(D=a;q^6fyt1|$-5fS@#K8lzcUqwj4XA4VX$dfGP>)fvX+b)Rk*wPtf zN0S12RIDpC9kGzxUVU8j*5=nA-jemPGpB73iTZh7VO^y~i_26C0(ktLzFV9E??YOm zpS(Dj;g1jy(Rh~BEc#5YzE*fM_?*g>t$xiF|CfNV+Tk$XOPcWmqu=-Y5s^zr>fVV! zrv8+5->khSU4hVV>EfxLLc|uh&mYPp6v{M&vku+7A^TkKmo~$QwaEqJ6{FSJ6oKvD=44AKEGH}tw#0qC zxB~wwtP;_0u2=vQhpI{m;{;y!kRx4&LGuh|)J3|O-mQ{VQju;cIALz&31`X1i}L^m zhLf73&zHFKRXyl~*6ldQWnJz<#0$7XVbl?}WvM3|(})u;Mo6Cbh320@y~vshS^wE1 zc3CfZ`1UI4GH{UA8~k)01os28cmCc_5qm{Wj(0=_9)k5dQ(z{9P`HsEYwKmNS?hJa zk(oiJ6a-y$HgED4=d0yJ)4Wif3USzv!|3g#T2n{obj|HO zmwt8|cX7X2K{0pU6#>Xr!*4ak+mm!&2{niTG_ef5n7aRtjuO~ePDmaYipy)l#c3D@ zzxvYq5{To>LrGoyk}XFaN1}WDMc^@up#4(K8}SCU33)*Chn{gb#F#-Bqme)}7HNmg z+wM~7n@HM96`o0SC`(;Kch}H}4W>Q;$8W*tR1Li%`CEkfTz#Ak+xoWR1tC~s&|wPxq?i*EW8 z7-Dk16zTJcz$<#RJt*Ra`=rpsW32r;%3}*V@QBhv$14}F!`B0MT{+5)c$JD?y3@K-2lgbq-@;yECF$@{k zA;RDEbR+dPFd)ZPSY^=cXPlS|>ZS*^55+o|(;@hrt|Yx!>ZjHY>FESLHjTL6-UAe@ zdeczlVT82mZ-Ie}LIS{3w2YrKW8ngS8Jj8dPCneeb^BwR=FR+I3JIN|)q(^Tg&0x$ z39e)y*F86c*=AmwAO5ei-cIqmhud}1Rs+FGaox!3UlXoLo8h&5hEa>X?q*LIA zqrr zrTX1rps6o->Bz;wCRF`FmeN{I?hW&?CvI4xX20NH4WL-dN=lBU4FF2QYY%1Is6$RS zW`x4!)~O0-+EB&mhp+(z$mW+{)}^KfvZ}rR*X85bgL}l~bK|!+G1(c4sK}O5mD=h{ zu|Kw&9|Fj`puoa;bp0V3F_l{K6Jtcz}njy_9#zAFX$zO~Ct zh*+hDMF^9`2XRnO0k~Gw^Jw7~lek!>&*gBQH<%X-%_j28PO4tvNB*7QcacMHM76^# zH9ddvB4^JG0;ev@I>Rf^(aJdxo~83+Gb@Xjs#eMQ{|+^v7tvldj70LTs<0JMcs!Hn zN;0CPt6K>8=BK~$pghy2oVaW2{lg?6pz_GYHE5PbV7JPu{6DyCui2E)nV~C)DssiB zgkbn*vQ)@~X_5|I3jIo){oYgZ4ZArfgFCs(7~>W8k24R+{iA&xCsuY{EE;MXTsGod z1NDAXUm!T{FnjKuvJL*m!-PL$eKH`6!ug=KeN+@Xb>==80pu9Ki%n)=u2Bj3-kQ)S z^p4W@QcecXU|-ZlUmli+&TVymq40)oPEleNB|5-(GP|k0aONX3;(M@cu?x5eaTI8? z$N63sCAM(ub_4kSt^JjFjJK_=qcoL;gYn|j!S4w|4JqmK1>{x663?9Ub^S;ho{zfS z<-4CxDGtS7Gb50P8>!Ir?U->%vqd_!1k*`HMz-gF8bk{zJ<;fBn28|U*pA>N&uUUr zU2`^wn;rY5UT~Kkg6v+f+H5iF*CynjhO4{mhjeFhrHFlzS~CTd|w+c9WM}s zXCZI0YXrENKvuQ5(3FKoHUi(tV*x@0R*)Q8oB8bq)}%4oG!h^#MkFt4ieFzb<$g~I zg3%%-MF{~S2;V4{R!feS9azO!GUot}m?FQ|%BG>dZ6T= zn;?tE?pYESSjz~i^1yIe0>ue2MXYBO0-nj>U>Z5~j>&=^Mlzn!6 zccY5k2pU%5%Ld>%89a3Cs3FwcGv*`^$$i=R;27Ee4D!P}z>tpb?;}#cx|yT{b(Gdtk$+8AycThzB@;ye1$2)Pg1r$cW&<5wHV=d$`C$j9 zeRmCI-!2;g+Z7yAWLv^BQCg0OfkM`TdCaLAb(yeq9aT!00@{r114C4?q=lwC@3c){ zMAb{`*PVT+hqeoWW_fl%9nW1v4ifmw1e#%l2p|rTQSS_%BClmr*HN=4#Oo}sW6%FR!pAU zt-^*P-@bu0ZlNQgfC0HJdq&)vEz2Kupja$m2L9N~Oob;8E|{liCim~v6L z!nr0`8C%Y8C<}2R6k_BYZX{+6{lLl~3(!3tw4>pHj!JJtQM&f=B)+;iL%~^Y3>y(S z&DiPj{DAcpu=FUg;0C*c6%)*T7+Q@~z3U5*= ziru$%gQa}TPY^zpL0{dlCQkn%P-E2BU%DGmzICnFmz7!Vis_J*Q3h;|brm)-@dyrU zk_LdMxezZOPhx05d9x9>D7KHCTd ze&ct=f+5`X+C)_-iEK7$0ETcv$H8DTqOwBW8OAeLFKpscwgJpu+3Vn!(f&GUXJoy1 zO?H$NJ4oqlcG;)qkN2CSf<7-+8oWOthvFi%0KJb&&^$UO??Vs_Ky*5gaRL0vOLQz9 z9>-;MTo^PQJBFq0WJyjRG|~!*N+r??>D90}MI!VHAN5c-NkmO&M z+9om1WMM1K>1{38r{}Hc5=WwAG<8H`6sOd%X{C|IvkRk1 zrtG{U-GeE>A1?o!G;2!RjZngrLLfLlCs!bveK5XlXmU zznux?rk;hkw2^AcdyOEQs)9dS(16LTuK=LSI&fYPdTdot8IX*t-wdimJ<;%26<#1a zN(~^>pIEcq z{nd-7@w)znAJq0^Z?1vAcLObT9O|(wR*^Yq;|E~(?`jf%LuK66!ZWP_?(;LFOpjRl zs^5teq16)vyvoXr4BSwgk;7CVSlmi4K&Cy)^jBw#?(<7aBenW)Bs$^Kfy_!*90>># zm_Z>J<(Ts`1aWjXW2SB$Rs%L=gudnOl65eSw--Kj%HNpGccSH9 z{4SeXlvnY8)F+v)K;beuClD%`=0gzyj#`-@IjQ{~9c@0Uw~s<@C2T}3gV@MqeWo$Y z{9xBycBDwjyrJg%vix7=Y(HwFoJIX^xxK*ZBTMT-4_t_$a`9UC7iGNn1pdE{(NLvc zKkXv{4YHN+^E+5RcCANbkH=={gHq7?Se#`4wL+rkBWxE7IkPp)kq9wL-%A=SH+HhV z$wBjv%?3Exez2joodcF0b|3hq=|GMKdAmvDz$2R-!%A8LLCY;0Hoy-k# z)_25(-S{ofbwi$L(V#3KrQ42o*+oybm2F(66Y}tsi=@Md<_inEy!-!tsq%EM$FcM# zdVU36JeXj17T*oWUPk`bTvf-yN+}lpgrpvdsMarqH5Q9L(qyb}Cyd5{+;+-4d5jpPPM=<_m(oWS>oVR}t4;jUeBMq)KrE ziXrI>TRu0pPs*>nQyjWJ@Roi$xPHb*>^h1QmE@^5Eq6`foy#go-`qz9fl;|JYLq$0 z@>}B@V~)y6%k*n0->wrjgb=ifdXOu)W^Cb;CO|G^Wsyy*CDIqz7a141vPrLY-^3LN zW?&|VI3t+Su$x|h$BCk;7Shn2{RpaG&IEa8(^&7V__-QpxoD}(_mg4zc$fL_2GqnXeG;=(WKkHU`6T3P@z9W*djPuCb->PfHpHINw)h2$@Bhvg}PN_7c z%+SZ-e@cj)jI<}btt=L4ZXn%mZ%z}h3u5C?HnbEWtL}mkfcpGdJdfB}hnCTWLC?A6 zqez9II}8*DT4FM>_W}e~0Nmp+XA|^4nia3{Y(BU|59^&-VtQgA?OzXFLSedH0iicc zI|gGV>wg+8UZrUK|MTzjDZaw&;3DfHP?h+!9X>{Js&)$%E7Dhy4%jOUn1N(j+u#}0 zJnBh{fmy(4E`kO=I-1HuwI=-6kTv&)*s)IZ;#!ACZJJGj~o=RRf);QP(RLJgs=9LE1&hDX{`N7Hgmu;V5QM-BZr#Sfff73;dz(&OfblWG%F}in`6D;zry{3=Tzu#PTCLK$S zennIqw-W!`kDTW+^yQ#zj8U#C; zva;Uhp9oO`MDL0gFwbB>oM;wbwl+?_9%3@rb-}@<(mm#c*HVVF9|_p7RxCv^4tWp| zb@U*2-Q~Cv^DC*C4o6!})z8IHjf1!}oaJFDT8D788F!DY5SRfMwftBYtuh0go1NX% zhIRKl4ICV9omBrneMOJQWfp6Xe>m1mt;nIr-C4@%pH|XrJMP5bQwQazRBCO%q*Kt|5d&8mSER*3%~Y$()1<$-{85OW}u|D&86FOgiWn8x#x zc^v0S>k_rhWJHfuXoqz)Pk3Ce%-Q@c6`CMQSswr|^5L_JvC*6GA3wGWZ>9Ah!pr-P zEN$8j+yhjERdcdgrd5&~+_YmY!kkar5JXzrh#HMb>1aO`23cZYTh^jIHCmPTNde+{oaxePyBU&EYEhiDMy zxZB2x4~vTrTcH7zj|_@-SQ70Cx|k%}_q#BNCdT>BAt}H)3l)F?aq}a(%OMK;Lryh3#iPhWYRLJbKfMF z_;`L{X#(S;st^3!4O^r}5^mvK@%oJ{4r)jHLNpRvGS&%an{QG)Wn!?h=2VWnwVP$Y}QVXCiuWL-oLon;N*s!rVz1bIJmTmMYUaS+OyU+cd22hc;v%v5#jc4v0k!~DLUaiHa)>{Q#$=@h-ov(b~MtCYwY>+pDSTQs2_H{ zlUbEv3{_1OO>;bIidgMqrFgafL=Zgf7Ni#ZG;pY9a?rLb8*FYp;aM^K%W}|l>t8%H zvw6;3-E1Qi@%(6Ol2om(K}WQyA3l-@!_hQUe(MIZdlM2b*z~Od!hsR3FQSx8sK-tI zb&X3MMviK)Kk<$tS8Z6JrKlMN(>A5#jgHOgNj8!)1$PL%4$&$F zHaD6~@zdsP`#bzra!Ra~ocOWL_tQJ(H1k1naUDZri7qSuvJ8~$0_M?P9cPNe!m}*n zupErB*Mh*E0Di}sLgBS-!)@eXTX0Z~vgZpM31rFEp;RU3|53R}D8moAk34QOXXiU3 zl<0BcYgs~=T=Sl#F0OO&Zhu57-&4NkXTB%|h=JiAzbKhV9TSp|OS&PzDgASN`|GJg zG+d2@IC-#nqWQH|Vd9@;pkk-CRJiAE)IzG5s=L+|N`v9qp=?xMi?^_VmVN_c#`+K; z_(G)kM^=W9^OP{Vju&L!mZF)DiEZNyEMbj({MpgO_UR6VTe$dFEV$qM9avwc%RG8+rj zizbe{EVIFcm<9q|hL}A6Wtm(>t=6_$6lC_MwA*+)(%m!BKqWHDY83z4^7>|3Eq&`x2)Pnp=@ql}XI_MUCE;$s6u zqhCgXFRc`6cL%?|LMO)ETV(N)AU?(4d%BS_)-`H$ShE#WXvHdi4I&pbXnds~V3&iX zCRax(43x@||7WCguI>@Gm?-bnSYhs>5}o1)OvQYNu3&lKJYo8dc9cC8O9lM8{robW z-L$=X8>`1YUWbnCtXQf}+axZ_PzbIV4c~x3x3xK&`;o6X+^MMZP1iYI(o);hxl=D? z=n}Aog6ausrdIrbwiZHIb>FUnzr-J+bXMO6#Zg)RuPkeq8G^!gU3C8)?kN6R>&4NGb$uAe#R zfL$hvnGpx@6e8D_tuCO(R3R6Vd8&#VOTPH__{v)k!-Pw4Elr)Sxd#|lwrv321K=|0 zm<+lBhBDy39FK}d`f`t<)bM`#J7O-)Z0VwdYchGBU9a*OoB&aRvN=v|+I6~upNMH| z{dYfVjTpRBPRZNB>|Dwl79_V#67Dc76YPrw#L6H6XuEN7!S&UblaB$;RRr~oWVfem z-uEnwC>isW-YcABVhzKqgE@Hj)PuyID6%6I`{=y7SCmB`-1 zN|Zj0{C9Fw+v4x|=lAjGIqrW@A7(Ea!}W*2o&9%UaKIPd&bQxYOyW)n%(GTW;-!v? z*5NwL$iU?$gt9+B8p$Mge=%jf&GJ?aUO^PSOiNXBbfBC{VQtgl;3B+5W&nN?ZGH{K zvq%qou)Q95aTqhdW?NtAt)O1+o&i8dFX?H6$@R3Iab(Xj;L9UAk2U1urdN)1hV4@Z zoFpoD4Bh;TLuc=R1V$EXQt|jm=-tvh^-1Nh`)t#l1=9eQJ!2rtbYdu2!81gL8c#!X z5}SOTF>Jpi2V$s;7YX6IKA=35FqaMC;FQsxnI{)Zt8E#l1bJh7bDU@f2Kyoc%Ia1j zh(2f;8MxYMOV*y30o16~5|h-xl=lj0ZELwB3)jM@^wbEcsh*ODp+$w<%f;$Vb>Q^~ zIXS?{kc2q)p)Yk@S(*I9!YT_7aIMjFw!)%($70GZDEa(;fYNqSWH=_{6L(^ zPJI<>a2-#FfOeeDHAE=%>Fg;aTr1+HEluKDw|3#hS)+YcxR^ueX?XZ6BmPz;%tOIE zJxeUAfpMX`MPj<{KtinG(KZu5m%*0nD&dq6ksm;UBjcwJDuly10Ef;<4iWaRnw8Zh zF2d@y#Z@T-CoN?Y;%y%9!*WKoCKaGVDv%l~pQ8jtrA5Keb$S*x;Toa&j{_`0_A3~b!RsV zD*tlR>f+iVh-58HZujKjbRK1W7J2?xX^<%sKfq|ctktzuJY+%g5q)rdB8|Yd@7SNv$4ygP@Gy=p+~$pCQv&25=#ZTrx%Vi~_f2GT zGv@n$lwoFeHshVHxrhn@fff90PU#KX>r4Z+?0zX-r1bl&FbbNIs4Hvm&~nYo{|A*3>)LZgV>dVG^L%Fjp^)UYKkY8NggmT z$2Jh!g5R36^*p{NA>XAkv?TPRs1^Pi(@FVv)}`Qrbbteksh*-ZY1oy%nFoqE28Z*D znU$JkQitRUO0x*! z+}*GR_<1;9Ge?BAyOlUda zu5<1`DzqRu&CkhnXA>$$2<}q;EFC=h<Cg??`YDZ;&N;-?AsGSyELNYL*rtq0()t zquTVW($SmuHq~~eR29h5QS~PTkKm&Mcu4ZO2nxjpTcz zkrX!tkFF74K5tU&PSzKrOv!Oa+1K;VD*eCZPj9JSS0*s3#oHyHhj z%2&!nhgDQP7z%V-`?@ubuFdRU<*UdMZIZ&Kj7qn}o#AEK+2}$k+mg`7cIiz)ISg9k-8teP#R)gLO0lmAcS-8Xq@+7A3!0g1*J zILE`0_@wd^yJc7GhuU5_;rayl+f!ElxVh9K-$mQe3%}AX;s8!Bz-+{jd45uk844~4 zI21!{;-BuF3P4=ziK?S_im_3Da_cR-icG?3=TXADV6d)m#t^QMO9$yW59Td4970Nn z$mC&)A>6zJ^=~3D@52vyTrj1&Txe)jWCMi`xjkETDVSiHvabJXimm1(SDe>RxC~VC zbmgClZ1$iXf91$0_^gXRp(UEJJ`<6Z2_xkJr1BkD?KI)jl2mNOX7%>4nj!nGrn#56 zHp8L&mv6~HwpE@2yREqH)lG32W$1Z662x8MFJNKE zQXlc;hRRWofbcnAT+62j;4;00YK_IIrz`lOcov(~8r30^ap-+->Me7V;RzD>lnjo6 zlx-BZp~fZE7MlldWJyF+qO> zAkU1aqxtUKf(F#eT?%?72d1^7V&&+{MyGlD>~v>8$|rm537(7L`3>^0FV|z^NDp@q z2OD&1O#5RVh+xBv3aS)u2ZRBuj?4C6jT3Spx0$rFD(ytj=74$6suW zP|8_e)D@`Af4nL>KOgBKRt1Ne55e1=gmbHAI zqi2ijJT>wrD2p}wX7}X`(_H>i@oYGa9KS0}4yG0?c^w{axnUXJnTBy6kcQ!6TTgAq zW6*#DXx01|O})?+GA=`E#;l_ktyL4NI@&UrP+G2~8DY2wc*#S)IK&i@QSGRFES3LL zYBfIxC%4XmJG>MvCdsUVCQjr2wKief{k=InCM$kK(#H!)7|^B9nX0d~{ocy2mHNv; z2NbMTgh>1(z}bTqClzUj^F>Wu9t3jS(B0JD-xRJ-IEbzzP=Z>M35&~;YZ6ndH>Z_{ ziT;g6^^$IWq1&%V@@fn^z;^o?|9j@rI$?p#G^QIto;nr+EM#3?CYRX&w;DjPUpjea z3ZUOF-G7@qO)ZgSq0W;gXq<$ja%yX7&t&X(mobNomKGp_YtN z>4(Xvmh=hsay-IIeUw4VIgh^R<@lw0I>EJ`08{Qbb5ETC?1)|s*6l@89jNtRbi|?) zlK~WsPICe-a~L$RYH9D|fo|=lGHw;|wMPA)q-I<#FfkUaU})fF74U;rvJfBx&G1N5 zRTZ^LFr2D2G2UGU##4_tSPg?RVG!k>#i#_QneKvWO{Kg!Loblz30tsVp5Wk31FMiu zd-kK8d;nKKsK20;a-JO_dAmKqV(6zV{q5+Q@oXVy>(GHYS+?d&h=Y+|#pM-GNGh_1nA~it)Yc{u|-;%r2J(E|UkutTw8R zy*ec4=qnfq>!WjaGuFLNQumDm2dLh*JCt>ef=pzMPPe#jq1oV1wgDLBl{)9fQE#ufq6D|5jmw{UVkVQ}b5C8xG0YTv4gg+ZtkGyBQ=UWYM5?3u$EG$Ui`g?oVXMfg_=V>EQ zO^Mqa7TFK9LYzbZNZKU$>8~qsDJc(g8gV9Ftye;eQRg2R&>Uc17ER>r;*9jQW2(f7 zwxw=WEP}Pq?myz3P+ctCAX^cam;azvf5|SjGS|10G^tb{s8V(H6wSp9wav_T-)Te{ z{^**;8EC}KeS+HOO&YU1_uI>?@$Iu>ElZ8$qL7kUGM>&SwF?rcc*KE<9P8CM*!LF* zLfo9WHBIC!#$^ob3U;`8k8f%wFV(BDvU$gCBr+HJx@%plr@CT7b_6GYa|mC$Icfx$ zJ?N_}Ws9{%4?76zRc~O@%-Whyor4IHFP^BskevDAyl8WzeRhP1?2CL-xDwUuCm+pd z8h3`A%F)8}LiX;phe(g7!O9EdUKQlWAPRvyahQKxb>@`?r4bi7b>tjAF;2)H8o&-- zR8n;t<39Gxz95T5b?*I!02CY2QK5N9@$O1E zJ>zyx#nkVo*l$vBH)jFjJTToFs*NmL(1hX5E@)O-WqEyO49LQGh6!M~!BaRNT9z8j z(ms0`g$NWoZ?Mu?nrW~qtz4oIPjm9775|8busJS6zvFtr{SaulFtN)18rc0jZ~mDB zEJQ%r0F$5SOdzhGy|6$3w&B87xJ2|kxeTbHa)x|&BJrVQ{T7wBwvX4?LBy?B9lzJ4 z6du@kPm$(2kPig{3S0Q%4RLkRpjYaFJ-!!E;XE859}7LKI)h-B75b$$ouoaWT(F9= zT)ItuWxhKYPkVNKb&*RewYrw`qG7(Fl0B?Y(+2%;<|4`gfeIU}yL9{BIW)GuA-7MK zOST&aPHoU5;!+TW9(*gzo--mSkTo$tdjA@>a8+tcXukwsu|CA`9AoEE{lD}H0#ei& zzzbqhK0yM)1MlCM1+VVU-U{<>rD%$G{LbXCbB$iNllSp(btQHaaeye6#~!b;w|w~Hw*J2f7>=St20SkCR<9JLsOP4UT%B}ww-X(#tdV_USM*V!@* zG?c`uZHje)CZXO>KGF@Uf^R_SE490l1nP#~A5k`c0U=fH^5_wp$291Q06eCN9j_RF z+A?CYB+0Q)Ln`66k{XJ??bZv`FKWigMTI|C zQ>glPuAb+2OrNeZL~}7Uu(Nx3R7D(}_K+FC#t$%9%pHF|_cs~sGatJGInNfM$5wH z^5MOjJ`BrR1@w-E&3-rk4$da}!wE<325pX$NErCqI3jJ;&o>$puxAGA)6}aR;vWo8 zXcSZPh9~QS*ae8Y1o>G3J}*U{N(Ns(zDw zr;S%mx4UO|p}f_xqYbv@YL;nz7H^yah~nO50X7 z>*`bK6e71kTA3x!ez!JO&p91gEwHuXVi71w6POGPTvcTC*0Q=W)!{x#0=lp@qEQH= z5C$u?45=6pS?F%|5e|(vTNQ8X*X%H@Nuo$J3!0@Y7*a%|qQ=V6H%W-QJx!}wxT;Xn z!fN|!(EMvgG=cZ7aoZ(atV=A)rrBZx#q^@~>GP!t%UHX)R|*P`;J!q&Nnz$GJQ**? zs=ZB`%WyysQWl`pyc?ag;3hg+OB;Y~Hb$qI_3UK9WL`flW5QG@0$@H#RNFLe(FOtc9xN;c!1 zkhitZAh(^8t}AKLMd2vbSp|hTg(w417&U+)%!iudg1e+<3PzqKf!rYOf)XpcOG*H| zGD#YbzHM$)NJ@q%%x&yj0TN*-42#$StT5g1GwXCNM!jx#T@Pn*3LnclA58S?Wk5^S z8GrCJDW<4?=Fa47ETJfRqV3Dcdt472_F!`}sO9W@t~=)W`(E?ho>AaS2U}8ZZr{>! zB~p|S@?z&~IWyI&9ayKL+A?IKs3zlW7JpDq4W*Km$dO|@ctay>8~4PSpDj;IO|?lQ z#1CyjpE2ivKr#$l6sn7V^`+D6U}@+4kkqG53c}5sA}HiD#4bW(Y-)T27ga=l9ApO< z>i;`l+T$u0J&D)~m{=BtIH{n9-Q&y zo!lmC5P_GfxMTZGk0%*jWzo8vPq*pnh7@I+R>qxxNx9;U#Do(q{0W5md3|)=X+VjN zP}Ijs>d=`iU#zSdGuY1``z@>F_@@ZGxknTLq~l`E$?)SLh((9kSRF0iS+l+<@0zp3X}i12p%{jsw@22HqpN^!j3jY4;K{s0<} zUM(bhK7maE9To)dG}FpQ*}S4-G=B|V^W5vj@Q7Ay*OEFq&-WIA1!18va2LiPLV{$q zPUa0DV`5UN@p)<8ZA6id#RE7?3KW=go=zLhN6U^d^-V3MG0dSu9|IAOtEh(TxaPoz zkJ6yAap|yK_^70?Hg=fK9XQTysc&+d zIy6hOmKoL=CTSXr9GN@u|Cq1Gvx5 zIrTDq)s2S1GzIfhM{VEGW9gPq4s7Chr?=l@Etn--`!kx~pdkQHoZI_VVCr{gYWg9` zhvEQ5Hj;f_3@-lFbCRNohpN9Z+t{1ne%DfjUm<6KApO3Y!H*V27=&d>T>h5!Jgteq zKJ3J0kI)QjZ)ovbw#8I+f1>@*uw*Ue0$)1DeO3JuPuU-3g9;1;bo+?@3+i^*qOB7F zY5XS_5CE80BQ@@2rut-ix0zBWeWRu` zSKTU6aM|7>t|(7WWc30->zMgF5)h9xPcWCWs?@WMZjodx)Md=H6L1;Mh zNJk=cF5deAaNQnx&?0HvTSHZ<={82wEh%ODdLqHZC9^|}(U`8~WY<6e_WT$)ntWWt zwSjmU4<|zs%o9Q4FN@2KLU?`#qA|!E(bish@F>c%*L(-{D1&CBqH|p=2@#up>@3Y6 z>Rgi#=>?NFlH6L9%2BPu?)Xzd>dRg3aETixvWs~lW$6!rR&>oCR&l6e)lQvYT?@j_ zP;PFMm`6-*yKNnQ1oW*ay+WzxD(F(fc?0}I9RZ2tmR0$oSzgv#*m&!8P$yR&RQk9<+L-yl2-if1=A&KV5>t%Emt2md(O4vsO zDs*851m}QlyX@aYast9lMRM5&Z19k3V*%JED34?A`9AcIl6rM!f~T3HHvJ5 zC0x&{mQhIeFPijC5(Sb9*aJAs@??As>kaqRST333OB!+sqJ;_9NyWqe?&^Sd`>o5~ zNv!k-HVV>rs(u!Gb)c(bt}eT9re{- zsbDO}OFO-LNN^DyJMM~SbCJ(41zrum&D!V%ku>&)iFG?QNMD>pgj$Ez5{&5l@MyZn z_X$pfoilV*mi&4ksR3bNH+Nr7u?Dx!?0TUrQp@jAIRn@C7${7OSPJ0El1eRA1zRQN z7G#eWx5p-%1tgc*ZaSi=dNS9)^MU=NvQ5ZZ=xSz~aThIh$pKp2;{A@nYA> zNuEX$^y? z57soiht{nRwah)$3l$@)Ety#V)-Libo<66R(NJ+3XdKXM5=oUuLiGy;Hj)VKWoJ69JJ%*4lX|gA z;QChzyXv0($s{!XmDVLO*@S{&wVeF=0)|&qcf*epyyr0Wp`gkk;Z`d#{K?FRK)?(W4Tp)f&x8l1d9FCP(}IIgBLm^B0*5YJi|J!>%|4Kyr1}Zr>Jg zwD>2JIGgs6bKNs8bLggVwi~%v`w)KaSz*XiNHTEgdv4<3c7cWa>O+eM+6Rm?N5n1i zv2jW`7m~UJ!Sbxb%r`Pt)(TY4?!2AYKk(C9$m2qt{w$LTTYh~+zpFx0jFMW)+0{!Tb5BXMFj} zR0*qg%hK8zhKQD6yh1eUHo

2lm_xxNPcnB)N)+r*=-S0Pp^!a#q6 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%0A|K>mh{<}fh5Z2k%<)$^K)4wUMMc_$10 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

iav^1agKn_+-+ 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�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

D6G) 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

lAB6kpmKBiHf> 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

-HpfCI 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

`Z45T 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 }1tuJt>^DBoYT*ul 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ⅆLJ+mA)M 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

)ZUhC|7mU<1K&UU;!l!0Q)3P#d#F7u_Rq!w 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 S99)D8 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

o#L{Sng=_XTGBq_o{mn=YMVd$h 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�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

29R~5xk3j${c3v9Jt*l*)xi~AqNhDT_S6Iwr! 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

Y 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

`@*zU%vUPeH~0uzQln2V1uJN&qJp2(pCzMN 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