Repository: baresip/re Branch: main Commit: 3cf4aff3f2c0 Files: 501 Total size: 2.7 MB Directory structure: gitextract_s5tkatt2/ ├── .clangd ├── .github/ │ └── workflows/ │ ├── abi.yml │ ├── android.yml │ ├── build.yml │ ├── clang-analyze.yml │ ├── cmake_win.yml │ ├── codeql.yml │ ├── coverage.yml │ ├── coverity.yml │ ├── fedora.yml │ ├── freebsd.yml │ ├── ios.yml │ ├── lint.yml │ ├── mingw.yml │ ├── musl.yml │ ├── run-on-arch.yml │ ├── sanitizers.yml │ ├── sonar.yml │ ├── ssl.yml │ ├── strict-c.yml │ └── valgrind.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── cmake/ │ ├── FindMBEDTLS.cmake │ ├── libre-config.cmake │ ├── re-config.cmake │ └── sanitizer.cmake ├── docs/ │ ├── ChangeLog │ ├── TODO │ └── main.dox ├── include/ │ ├── re.h │ ├── re_aes.h │ ├── re_async.h │ ├── re_atomic.h │ ├── re_av1.h │ ├── re_base64.h │ ├── re_bfcp.h │ ├── re_btrace.h │ ├── re_conf.h │ ├── re_convert.h │ ├── re_crc32.h │ ├── re_dbg.h │ ├── re_dd.h │ ├── re_dns.h │ ├── re_fmt.h │ ├── re_h264.h │ ├── re_h265.h │ ├── re_hash.h │ ├── re_hmac.h │ ├── re_http.h │ ├── re_httpauth.h │ ├── re_ice.h │ ├── re_json.h │ ├── re_list.h │ ├── re_main.h │ ├── re_mbuf.h │ ├── re_md5.h │ ├── re_mem.h │ ├── re_mod.h │ ├── re_mqueue.h │ ├── re_msg.h │ ├── re_net.h │ ├── re_odict.h │ ├── re_pcp.h │ ├── re_rtmp.h │ ├── re_rtp.h │ ├── re_rtpext.h │ ├── re_sa.h │ ├── re_sdp.h │ ├── re_sha.h │ ├── re_shim.h │ ├── re_sip.h │ ├── re_sipevent.h │ ├── re_sipreg.h │ ├── re_sipsess.h │ ├── re_srtp.h │ ├── re_stun.h │ ├── re_sys.h │ ├── re_tcp.h │ ├── re_telev.h │ ├── re_thread.h │ ├── re_tls.h │ ├── re_tmr.h │ ├── re_trace.h │ ├── re_trice.h │ ├── re_turn.h │ ├── re_types.h │ ├── re_udp.h │ ├── re_unixsock.h │ ├── re_uri.h │ ├── re_websock.h │ ├── rem.h │ ├── rem_aac.h │ ├── rem_au.h │ ├── rem_aubuf.h │ ├── rem_auconv.h │ ├── rem_audio.h │ ├── rem_aufile.h │ ├── rem_auframe.h │ ├── rem_aulevel.h │ ├── rem_aumix.h │ ├── rem_auresamp.h │ ├── rem_autone.h │ ├── rem_avc.h │ ├── rem_dsp.h │ ├── rem_dtmf.h │ ├── rem_fir.h │ ├── rem_flv.h │ ├── rem_g711.h │ ├── rem_goertzel.h │ ├── rem_vid.h │ ├── rem_vidconv.h │ ├── rem_video.h │ └── rem_vidmix.h ├── mk/ │ └── Doxyfile ├── packaging/ │ ├── CMakeLists.txt │ └── libre.pc.in ├── rem/ │ ├── aac/ │ │ └── aac.c │ ├── au/ │ │ ├── fmt.c │ │ └── util.c │ ├── aubuf/ │ │ ├── ajb.c │ │ ├── ajb.h │ │ └── aubuf.c │ ├── auconv/ │ │ └── auconv.c │ ├── aufile/ │ │ ├── aufile.c │ │ ├── aufile.h │ │ └── wave.c │ ├── auframe/ │ │ └── auframe.c │ ├── aulevel/ │ │ └── aulevel.c │ ├── aumix/ │ │ └── aumix.c │ ├── auresamp/ │ │ └── resamp.c │ ├── autone/ │ │ └── tone.c │ ├── avc/ │ │ └── config.c │ ├── dtmf/ │ │ └── dec.c │ ├── fir/ │ │ └── fir.c │ ├── g711/ │ │ └── g711.c │ ├── goertzel/ │ │ └── goertzel.c │ ├── vid/ │ │ ├── draw.c │ │ ├── fmt.c │ │ └── frame.c │ ├── vidconv/ │ │ └── vconv.c │ └── vidmix/ │ └── vidmix.c ├── sonar-project.properties ├── src/ │ ├── aes/ │ │ ├── apple/ │ │ │ └── aes.c │ │ ├── openssl/ │ │ │ └── aes.c │ │ └── stub.c │ ├── async/ │ │ └── async.c │ ├── av1/ │ │ ├── depack.c │ │ ├── obu.c │ │ └── pkt.c │ ├── base64/ │ │ └── b64.c │ ├── bfcp/ │ │ ├── attr.c │ │ ├── bfcp.h │ │ ├── conn.c │ │ ├── msg.c │ │ ├── reply.c │ │ └── request.c │ ├── btrace/ │ │ └── btrace.c │ ├── conf/ │ │ └── conf.c │ ├── crc32/ │ │ └── crc32.c │ ├── dbg/ │ │ └── dbg.c │ ├── dd/ │ │ ├── dd.c │ │ ├── dd_enc.c │ │ └── putbit.c │ ├── dns/ │ │ ├── client.c │ │ ├── cstr.c │ │ ├── darwin/ │ │ │ └── srv.c │ │ ├── dname.c │ │ ├── dns.h │ │ ├── hdr.c │ │ ├── ns.c │ │ ├── res.c │ │ ├── rr.c │ │ ├── rrlist.c │ │ └── win32/ │ │ └── srv.c │ ├── fmt/ │ │ ├── ch.c │ │ ├── hexdump.c │ │ ├── pl.c │ │ ├── print.c │ │ ├── prm.c │ │ ├── regex.c │ │ ├── str.c │ │ ├── str_error.c │ │ ├── text2pcap.c │ │ ├── time.c │ │ └── unicode.c │ ├── h264/ │ │ ├── getbit.c │ │ ├── h264.h │ │ ├── nal.c │ │ └── sps.c │ ├── h265/ │ │ └── nal.c │ ├── hash/ │ │ ├── func.c │ │ └── hash.c │ ├── hmac/ │ │ ├── apple/ │ │ │ └── hmac.c │ │ ├── hmac.c │ │ ├── hmac_sha1.c │ │ └── openssl/ │ │ └── hmac.c │ ├── http/ │ │ ├── auth.c │ │ ├── chunk.c │ │ ├── client.c │ │ ├── http.h │ │ ├── msg.c │ │ ├── request.c │ │ └── server.c │ ├── httpauth/ │ │ ├── basic.c │ │ └── digest.c │ ├── ice/ │ │ ├── cand.c │ │ ├── candpair.c │ │ ├── chklist.c │ │ ├── comp.c │ │ ├── connchk.c │ │ ├── ice.h │ │ ├── icem.c │ │ ├── icesdp.c │ │ ├── icestr.c │ │ ├── stunsrv.c │ │ └── util.c │ ├── json/ │ │ ├── decode.c │ │ ├── decode_odict.c │ │ └── encode.c │ ├── list/ │ │ └── list.c │ ├── main/ │ │ ├── init.c │ │ ├── main.c │ │ ├── main.h │ │ ├── method.c │ │ └── openssl.c │ ├── mbuf/ │ │ └── mbuf.c │ ├── md5/ │ │ └── wrap.c │ ├── mem/ │ │ ├── mem.c │ │ ├── mem_pool.c │ │ └── secure.c │ ├── mod/ │ │ ├── dl.c │ │ ├── mod.c │ │ ├── mod_internal.h │ │ └── win32/ │ │ └── dll.c │ ├── mqueue/ │ │ ├── mqueue.c │ │ ├── mqueue.h │ │ └── win32/ │ │ └── pipe.c │ ├── msg/ │ │ ├── ctype.c │ │ └── param.c │ ├── net/ │ │ ├── bsd/ │ │ │ └── brt.c │ │ ├── if.c │ │ ├── ifaddrs.c │ │ ├── linux/ │ │ │ ├── addrs.c │ │ │ ├── macros.h │ │ │ └── rt.c │ │ ├── net.c │ │ ├── netstr.c │ │ ├── posix/ │ │ │ └── pif.c │ │ ├── rt.c │ │ ├── sock.c │ │ ├── sockopt.c │ │ └── win32/ │ │ └── wif.c │ ├── odict/ │ │ ├── entry.c │ │ ├── get.c │ │ ├── odict.c │ │ ├── odict.h │ │ └── type.c │ ├── pcp/ │ │ ├── README │ │ ├── msg.c │ │ ├── option.c │ │ ├── payload.c │ │ ├── pcp.c │ │ ├── pcp.h │ │ ├── reply.c │ │ └── request.c │ ├── rtmp/ │ │ ├── README.md │ │ ├── amf.c │ │ ├── amf_dec.c │ │ ├── amf_enc.c │ │ ├── chunk.c │ │ ├── conn.c │ │ ├── control.c │ │ ├── ctrans.c │ │ ├── dechunk.c │ │ ├── hdr.c │ │ ├── rtmp.h │ │ └── stream.c │ ├── rtp/ │ │ ├── fb.c │ │ ├── member.c │ │ ├── ntp.c │ │ ├── pkt.c │ │ ├── rr.c │ │ ├── rtcp.c │ │ ├── rtcp.h │ │ ├── rtp.c │ │ ├── sdes.c │ │ ├── sess.c │ │ └── source.c │ ├── rtpext/ │ │ └── rtpext.c │ ├── sa/ │ │ ├── printaddr.c │ │ └── sa.c │ ├── sdp/ │ │ ├── attr.c │ │ ├── format.c │ │ ├── media.c │ │ ├── msg.c │ │ ├── sdp.h │ │ ├── session.c │ │ ├── str.c │ │ └── util.c │ ├── sha/ │ │ └── wrap.c │ ├── shim/ │ │ └── shim.c │ ├── sip/ │ │ ├── addr.c │ │ ├── auth.c │ │ ├── contact.c │ │ ├── cseq.c │ │ ├── ctrans.c │ │ ├── dialog.c │ │ ├── keepalive.c │ │ ├── keepalive_udp.c │ │ ├── msg.c │ │ ├── rack.c │ │ ├── reply.c │ │ ├── request.c │ │ ├── sip.c │ │ ├── sip.h │ │ ├── strans.c │ │ ├── transp.c │ │ └── via.c │ ├── sipevent/ │ │ ├── listen.c │ │ ├── msg.c │ │ ├── notify.c │ │ ├── sipevent.h │ │ └── subscribe.c │ ├── sipreg/ │ │ └── reg.c │ ├── sipsess/ │ │ ├── accept.c │ │ ├── ack.c │ │ ├── close.c │ │ ├── connect.c │ │ ├── info.c │ │ ├── listen.c │ │ ├── modify.c │ │ ├── prack.c │ │ ├── reply.c │ │ ├── request.c │ │ ├── sess.c │ │ ├── sipsess.h │ │ └── update.c │ ├── srtp/ │ │ ├── README │ │ ├── misc.c │ │ ├── replay.c │ │ ├── srtcp.c │ │ ├── srtp.c │ │ ├── srtp.h │ │ └── stream.c │ ├── stun/ │ │ ├── addr.c │ │ ├── attr.c │ │ ├── ctrans.c │ │ ├── dnsdisc.c │ │ ├── hdr.c │ │ ├── ind.c │ │ ├── keepalive.c │ │ ├── msg.c │ │ ├── rep.c │ │ ├── req.c │ │ ├── stun.c │ │ ├── stun.h │ │ └── stunstr.c │ ├── sys/ │ │ ├── daemon.c │ │ ├── endian.c │ │ ├── fs.c │ │ ├── rand.c │ │ ├── sleep.c │ │ └── sys.c │ ├── tcp/ │ │ ├── tcp.c │ │ └── tcp_high.c │ ├── telev/ │ │ └── telev.c │ ├── thread/ │ │ ├── posix.c │ │ ├── thread.c │ │ └── win32.c │ ├── tls/ │ │ ├── openssl/ │ │ │ ├── sni.c │ │ │ ├── tls.c │ │ │ ├── tls.h │ │ │ ├── tls_tcp.c │ │ │ └── tls_udp.c │ │ └── stub.c │ ├── tmr/ │ │ └── tmr.c │ ├── trace/ │ │ └── trace.c │ ├── trice/ │ │ ├── README.md │ │ ├── cand.c │ │ ├── candpair.c │ │ ├── chklist.c │ │ ├── connchk.c │ │ ├── lcand.c │ │ ├── rcand.c │ │ ├── stunsrv.c │ │ ├── tcpconn.c │ │ ├── trice.c │ │ └── trice.h │ ├── turn/ │ │ ├── chan.c │ │ ├── perm.c │ │ ├── turnc.c │ │ └── turnc.h │ ├── udp/ │ │ ├── mcast.c │ │ └── udp.c │ ├── unixsock/ │ │ └── unixsock.c │ ├── uri/ │ │ ├── uri.c │ │ └── uric.c │ └── websock/ │ └── websock.c ├── test/ │ ├── CMakeLists.txt │ ├── aac.c │ ├── aes.c │ ├── async.c │ ├── au.c │ ├── aubuf.c │ ├── aulength.c │ ├── aulevel.c │ ├── aupos.c │ ├── auresamp.c │ ├── av1.c │ ├── base64.c │ ├── bfcp.c │ ├── btrace.c │ ├── combo/ │ │ └── dtls_turn.c │ ├── conf.c │ ├── convert.c │ ├── cplusplus.cpp │ ├── crc32.c │ ├── data/ │ │ ├── client.pem │ │ ├── client_wrongkey.pem │ │ ├── fstab.json │ │ ├── menu.json │ │ ├── rfc7159.json │ │ ├── server-ecdsa.pem │ │ ├── sni/ │ │ │ ├── client-interm.pem │ │ │ ├── root-ca.pem │ │ │ └── server-interm.pem │ │ ├── utf8.json │ │ ├── webapp.json │ │ └── widget.json │ ├── dbg.c │ ├── dd.c │ ├── dns.c │ ├── dsp.c │ ├── dtls.c │ ├── dtmf.c │ ├── fir.c │ ├── fmt.c │ ├── g711.c │ ├── h264.c │ ├── h265.c │ ├── hash.c │ ├── hmac.c │ ├── http.c │ ├── httpauth.c │ ├── ice.c │ ├── json.c │ ├── list.c │ ├── main.c │ ├── mbuf.c │ ├── md5.c │ ├── mem.c │ ├── mem_pool.c │ ├── mock/ │ │ ├── cert.c │ │ ├── dnssrv.c │ │ ├── nat.c │ │ ├── sipsrv.c │ │ ├── stunsrv.c │ │ └── turnsrv.c │ ├── mqueue.c │ ├── net.c │ ├── odict.c │ ├── pcp.c │ ├── remain.c │ ├── rtcp.c │ ├── rtmp.c │ ├── rtp.c │ ├── rtpext.c │ ├── sa.c │ ├── sdp.c │ ├── sha.c │ ├── sip.c │ ├── sipauth.c │ ├── sipevent.c │ ├── sipreg.c │ ├── sipsess.c │ ├── srtp.c │ ├── stun.c │ ├── sys.c │ ├── tcp.c │ ├── telev.c │ ├── test.c │ ├── test.h │ ├── thread.c │ ├── tls.c │ ├── tmr.c │ ├── trace.c │ ├── trice.c │ ├── turn.c │ ├── types.c │ ├── udp.c │ ├── unixsock.c │ ├── uri.c │ ├── vid.c │ ├── vidconv.c │ └── websock.c └── tools/ └── genfir.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clangd ================================================ If: PathMatch: [include/.*\.h] CompileFlags: Add: [-Wall, -DHAVE_INTTYPES_H, -include re.h] Remove: [-xobjective-c++-header] --- Diagnostics: Suppress: "-Wgnu-zero-variadic-macro-arguments" ================================================ FILE: .github/workflows/abi.yml ================================================ name: ABI Checks on: push: branches: - main pull_request: branches: - main jobs: abicheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: ref: 'v4.6.0' path: old - uses: actions/checkout@v5 with: path: current - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install abidiff run: sudo apt-get update && sudo apt-get install -y abigail-tools - name: make shared lib run: | cmake -S old -B old/build && cmake --build old/build - name: make current shared lib run: | cmake -S current -B current/build && cmake --build current/build - name: abidiff compare id: abidiff run: abidiff old/build/libre.so current/build/libre.so continue-on-error: true - name: display warning if: steps.abidiff.outcome != 'success' run: echo "::warning::ABI Check failed - bump ABI version" ================================================ FILE: .github/workflows/android.yml ================================================ name: Android on: push: branches: - main pull_request: branches: - main env: openssl: 3.2.1 toolchain: toolchains/llvm/prebuilt/linux-x86_64 api: 26 abi: x86_64 jobs: android: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - uses: actions/cache@v4 id: openssl with: path: openssl key: ${{ runner.os }}-android-${{ env.abi }}-openssl-${{ env.openssl }} - name: "build openssl" if: steps.openssl.outputs.cache-hit != 'true' run: | wget -q https://www.openssl.org/source/openssl-$openssl.tar.gz tar -xzf openssl-$openssl.tar.gz mv openssl-$openssl openssl cd openssl && ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME PATH=$ANDROID_NDK_LATEST_HOME/$toolchain/bin:$PATH ./Configure android-$abi no-shared no-tests -U__ANDROID_API__ -D__ANDROID_API__=$api && PATH=$ANDROID_NDK_LATEST_HOME/$toolchain/bin:$PATH make build_libs && cd .. - name: build # unixsock is not currently supported on Android as only abstract (not pathname) addresses are allowed run: | cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake -DANDROID_ABI="$abi" -DANDROID_PLATFORM=android-$api -DOPENSSL_ROOT_DIR=openssl -DOPENSSL_INCLUDE_DIR=openssl/include -DOPENSSL_CRYPTO_LIBRARY=openssl/libcrypto.a -DOPENSSL_SSL_LIBRARY=openssl/libssl.a -DUSE_UNIXSOCK=OFF . cmake --build . -j 4 -t retest - name: run uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ env.api }} arch: ${{ env.abi }} target: google_apis # Use test data directory as a writeable directory for I/O tests as the emulator file system is not generally writeable script: | adb push test/retest /data/local/tmp/retest adb shell chmod 775 /data/local/tmp/retest adb push test/data /data/local/tmp/retest-data adb shell chmod 775 /data/local/tmp/retest-data adb push $ANDROID_NDK_LATEST_HOME/$toolchain/sysroot/usr/lib/$abi-linux-android/libc++_shared.so /data/local/tmp/libc++_shared.so adb shell HOME=/data/local/tmp LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/retest -r -v -d /data/local/tmp/retest-data ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: build_type: [Release, Debug] compiler: [gcc, clang, gcc-14, clang-21] os: [ubuntu-22.04, ubuntu-24.04, macos-latest] exclude: - os: macos-latest compiler: gcc - os: macos-latest compiler: gcc-14 - os: macos-latest compiler: clang-21 - os: ubuntu-22.04 compiler: gcc-14 - os: ubuntu-22.04 compiler: clang-21 env: CC: ${{ matrix.compiler }} CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: Set Xcode version if: ${{ runner.os == 'macOS' }} run: sudo xcode-select -s /Applications/Xcode_16.2.app - name: openssl path macos if: ${{ runner.os == 'macOS' }} run: | echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl)" >> $GITHUB_ENV - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install packages if: ${{ runner.os == 'Linux' }} run: | sudo apt-get update && sudo apt-get install -y ninja-build - name: Install clang if: ${{ matrix.compiler == 'clang-21' }} run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main" sudo apt-get update && sudo apt-get install -y clang-21 - name: make info run: | echo "OS: ${{ matrix.os }}" echo "--- ${{ matrix.compiler }} DEBUG VERSION ---" ${{ matrix.compiler }} - --version - name: cmake run: | cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" cmake --build build -t retest - name: retest run: | ./build/test/retest -r -v ================================================ FILE: .github/workflows/clang-analyze.yml ================================================ name: clang analyze on: push: branches: - main pull_request: branches: - main jobs: clang-analyze: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: Install clang-tools run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main" sudo apt-get update && sudo apt-get install -y clang-tools-21 - name: analyze run: | cmake -B build -DCMAKE_C_COMPILER=clang-21 analyze-build-21 --cdb build/compile_commands.json --status-bugs -v ================================================ FILE: .github/workflows/cmake_win.yml ================================================ name: Windows on: push: branches: - main pull_request: branches: - main jobs: build: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - { name: "Windows Debug", os: windows-2022, environment_script: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat", generators: "Ninja", build: "Debug", openssl: true, disable_openssl: "OFF", testing: true, c11_threads: "ON" } - { name: "Windows Release", os: windows-2022, environment_script: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat", generators: "Ninja", build: "Release", openssl: true, disable_openssl: "OFF", testing: true, c11_threads: "ON" } - { name: "Windows Debug 32-bit", os: windows-2022, environment_script: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsamd64_x86.bat", generators: "Ninja", build: "Debug", openssl: false, disable_openssl: "ON", choco: "--x86", testing: true, c11_threads: "OFF" # missing vcruntime library } - { name: "Windows Debug ARM64", os: windows-2022, environment_script: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsamd64_arm64.bat", generators: "Ninja", build: "Debug", openssl: false, disable_openssl: "ON", testing: false, c11_threads: "ON" } steps: - uses: actions/checkout@v5 - name: Install OpenSSL if: ${{ matrix.config.openssl }} run: | choco install --no-progress ${{ matrix.config.choco }} openssl --version 3.6.2 - name: Build shell: cmd run: | call "${{ matrix.config.environment_script }}" cmake --version ninja --version cmake -S . -B build -G "${{ matrix.config.generators }}" -DCMAKE_C_FLAGS="/WX" -DHAVE_THREADS="${{ matrix.c11_threads }}" -DCMAKE_BUILD_TYPE="${{ matrix.config.build }}" -DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL="${{ matrix.config.disable_openssl }}" cmake --build build --parallel -t retest - name: retest if: ${{ matrix.config.testing }} shell: cmd run: | build\test\retest.exe -a -v ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL" on: push: branches: - main pull_request: branches: - main jobs: analyze: name: CodeQL Analyze runs-on: ubuntu-latest env: CMAKE_GENERATOR: Ninja steps: - name: Checkout repository uses: actions/checkout@v5 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: cpp queries: security-extended - name: install packages run: | sudo apt-get update && sudo apt-get install -y ninja-build - run: | cmake -B build && cmake --build build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 ================================================ FILE: .github/workflows/coverage.yml ================================================ name: Coverage on: push: branches: - main pull_request: branches: - main jobs: coverage: runs-on: ubuntu-22.04 env: CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install packages run: | sudo apt-get update && sudo apt-get install -y ninja-build - name: make run: | cmake -B build -DCMAKE_C_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" -DUSE_TRACE=ON cmake --build build -j -t retest - name: retest run: | ./build/test/retest -a -v ./build/test/retest -r -m select -v - name: gcov run: | gcov build/**/*.o - name: install gcovr run: | pip install gcovr==5.0 - name: coverage check run: | min_cov="70.0" mkdir html cov=$(~/.local/bin/gcovr -r . --html-details html/index.html --json-summary | jq .line_percent) echo "Coverage: ${cov}% (min $min_cov%)" exit $(echo "$cov < $min_cov" | bc -l) - name: coverage zip run: | zip -r coverage.zip html - uses: actions/upload-artifact@v4 with: name: coverage path: coverage.zip retention-days: 7 ================================================ FILE: .github/workflows/coverity.yml ================================================ name: Coverity Check on: push: tags: - "*" branches: - coverity workflow_dispatch: jobs: coverity: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Prepare run: cmake -B ${{github.workspace}}/build - uses: vapier/coverity-scan-action@v1 with: project: 'baresip%2Fre' token: ${{ secrets.COVERITY_SCAN_TOKEN }} command: make -C ${{github.workspace}}/build email: 'hallo@studio-link.de' ================================================ FILE: .github/workflows/fedora.yml ================================================ name: Fedora on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ${{ matrix.os }} container: fedora strategy: matrix: compiler: [clang] os: [ubuntu-latest] env: CC: ${{ matrix.compiler }} CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: install devel tools run: | yum -y install gcc clang cmake make openssl-devel zlib-devel ninja-build - name: make info run: | echo "OS: ${{ matrix.os }}" echo "--- ${{ matrix.compiler }} DEBUG VERSION ---" ${{ matrix.compiler }} - --version cmake --version - name: make run: | cmake -B build -DCMAKE_C_FLAGS="-Werror" && cmake --build build -j 16 -t retest - name: retest run: | ./build/test/retest -r -v ================================================ FILE: .github/workflows/freebsd.yml ================================================ name: FreeBSD on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest timeout-minutes: 20 env: CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: Test in FreeBSD id: test uses: vmactions/freebsd-vm@v1 with: release: "14.4" usesh: true prepare: | run: | pkg update && pkg install -y ninja cmake freebsd-version cmake -B build && cmake --build build -t retest ./build/test/retest -r -v ================================================ FILE: .github/workflows/ios.yml ================================================ name: iOS on: push: branches: - main pull_request: branches: - main jobs: ios: runs-on: macos-latest steps: - uses: actions/checkout@v5 - name: build Xcode run: | cmake -B build_xcode -G Xcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 -DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON -DUSE_OPENSSL=OFF -DCMAKE_C_FLAGS="-Werror" cmake --build build_xcode -- CODE_SIGNING_ALLOWED=NO - name: normal build run: | cmake -B build -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 -DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON -DUSE_OPENSSL=OFF -DCMAKE_C_FLAGS="-Werror" cmake --build build -j ================================================ FILE: .github/workflows/lint.yml ================================================ name: lint on: push: branches: - main pull_request: branches: - main jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: ccheck run: | wget "https://raw.githubusercontent.com/baresip/baresip/main/test/ccheck.py" python3 ccheck.py - name: CMakeLint run: | pip install cmakelint find . -name "CMakeLists.txt" -exec ~/.local/bin/cmakelint {} + ================================================ FILE: .github/workflows/mingw.yml ================================================ name: MinGW-w64 Test on: push: branches: - main pull_request: branches: - main jobs: MinGW-w64-build: runs-on: ubuntu-22.04 env: CMAKE_GENERATOR: Ninja steps: - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: "install packages" run: | sudo apt-get update && sudo apt-get install -y mingw-w64 ninja-build - uses: actions/checkout@v5 # needed for pr checkout - uses: sreimers/pr-dependency-action@v0.6 with: name: baresip-win32 repo: https://github.com/baresip/baresip-win32 secret: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v5 with: path: baresip-win32/re - uses: actions/cache@v4 id: openssl with: path: baresip-win32/openssl key: ${{ runner.os }}-mingw-openssl-3.5.0 - name: "build openssl" if: steps.openssl.outputs.cache-hit != 'true' run: | wget https://www.openssl.org/source/openssl-3.5.0.tar.gz tar -xzf openssl-3.5.0.tar.gz mv openssl-3.5.0 baresip-win32/openssl make -j$(nproc) -C baresip-win32 openssl - name: "build" run: | cd baresip-win32 && make retest - uses: actions/upload-artifact@v4 with: name: retest-exe path: baresip-win32/re/build/test/retest.exe retention-days: 1 wintest: runs-on: windows-latest needs: MinGW-w64-build steps: - uses: actions/checkout@v5 - uses: actions/download-artifact@v4 - uses: sreimers/pr-dependency-action@v0.6 with: name: re repo: https://github.com/baresip/re secret: ${{ secrets.GITHUB_TOKEN }} - name: "cv2pdb" run: | curl -L https://github.com/rainers/cv2pdb/releases/download/v0.52/cv2pdb-0.52.zip --output cv2pdb.zip unzip -o cv2pdb.zip shell: bash - name: "prepare retest.exe" run: mv retest-exe/retest.exe re/ && cd re && ../cv2pdb.exe ./retest.exe shell: bash - name: "run retest.exe" run: cd re && ./retest.exe -v -ri shell: bash ================================================ FILE: .github/workflows/musl.yml ================================================ name: Alpine (musl) on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest container: alpine env: CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: install devel tools run: | apk add musl-dev git cmake gcc g++ make binutils openssl-dev linux-headers zlib-dev ninja - name: make run: | cmake -B build -DCMAKE_C_FLAGS="-Werror" cmake --build build -j ================================================ FILE: .github/workflows/run-on-arch.yml ================================================ on: push: branches: - main pull_request: branches: - main jobs: build_job: # The host should always be linux runs-on: ubuntu-22.04 name: Build on ${{ matrix.distro }} ${{ matrix.arch }} strategy: matrix: include: - arch: aarch64 distro: bookworm - arch: armv7 distro: ubuntu22.04 steps: - uses: actions/checkout@v5 - uses: uraimo/run-on-arch-action@v3 name: Build artifact id: build with: arch: ${{ matrix.arch }} distro: ${{ matrix.distro }} # Not required, but speeds up builds githubToken: ${{ github.token }} install: | case "${{ matrix.distro }}" in ubuntu*|jessie|stretch|buster|bullseye|bookworm) apt-get update -q -y apt-get install -q -y cmake gcc g++ libssl-dev ninja-build ;; esac run: | cmake -G Ninja -B build -DCMAKE_C_FLAGS="-Werror" cmake --build build -j --target retest ./build/test/retest -r -v ================================================ FILE: .github/workflows/sanitizers.yml ================================================ name: Sanitizers on: push: branches: - main pull_request: branches: - main jobs: sanitizers: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-24.04] sanitizer: [thread, address, undefined] env: CC: clang-20 CXX: clang++-20 CMAKE_GENERATOR: Ninja CFLAGS: "-fsanitize=${{ matrix.sanitizer }} -fno-sanitize-recover=all -fno-sanitize=function" CXXFLAGS: "-fsanitize=${{ matrix.sanitizer }} -fno-sanitize-recover=all -fno-sanitize=function" ASAN_OPTIONS: fast_unwind_on_malloc=0 steps: - uses: actions/checkout@v5 - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install packages run: | sudo apt-get update && sudo apt-get install -y ninja-build - name: Install clang-tools run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" sudo apt-get update && sudo apt-get install -y clang-tools-20 - name: make info run: | echo "OS: ${{ matrix.os }}" clang - --version - name: cmake run: | cmake -B build -DHAVE_THREADS= && cmake --build build -j -t retest - name: retest run: | ./build/test/retest -av ================================================ FILE: .github/workflows/sonar.yml ================================================ name: Sonarcloud on: push: branches: - main workflow_dispatch: jobs: build: name: Build runs-on: ubuntu-latest env: SONAR_SCANNER_VERSION: 5.0.1.3006 SONAR_SERVER_URL: "https://sonarcloud.io" BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed steps: - uses: actions/checkout@v5 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: 17 distribution: 'oracle' - name: Download and set up sonar-scanner env: SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip run: | mkdir -p $HOME/.sonar curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH - name: Download and set up build-wrapper env: BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip run: | curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH - name: Run build-wrapper run: | cmake -S . -B build build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ - name: Run sonar-scanner env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" ================================================ FILE: .github/workflows/ssl.yml ================================================ name: OpenSSL no-deprecated and LibreSSL on: push: branches: - main pull_request: branches: - main jobs: ssl: runs-on: ubuntu-latest strategy: matrix: ssl: [libressl, openssl] env: CMAKE_GENERATOR: Ninja OPENSSL_ROOT_DIR: "assets/${{ matrix.ssl }}" steps: - uses: actions/checkout@v5 - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install packages run: | sudo apt-get update && sudo apt-get install -y ninja-build - name: Download pre-compiled OpenSSL/LibreSSL run: | wget "https://github.com/baresip/tools/releases/download/v2026.05.0/assets.tar.gz" tar -xf assets.tar.gz - name: make run: | cmake -B build -DCMAKE_C_FLAGS="-Werror" cmake --build build -j cmake --build build -t retest - name: retest run: | ./build/test/retest -r -v ================================================ FILE: .github/workflows/strict-c.yml ================================================ name: Strict C checks on: push: branches: - main pull_request: branches: - main jobs: strict-c: runs-on: ubuntu-latest env: CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install packages run: | sudo apt-get update && sudo apt-get install -y ninja-build - name: make strict C99 run: | cmake -DCMAKE_C_STANDARD=99 -DCMAKE_C_EXTENSIONS=OFF -DCMAKE_C_FLAGS="-Werror" -B build cmake --build build rm -Rf build - name: make strict C11 run: | cmake -DCMAKE_C_STANDARD=11 -DCMAKE_C_EXTENSIONS=OFF -DCMAKE_C_FLAGS="-Werror" -B build cmake --build build rm -Rf build ================================================ FILE: .github/workflows/valgrind.yml ================================================ name: valgrind leak check on: push: branches: - main pull_request: branches: - main jobs: valgrind: runs-on: ubuntu-latest env: CMAKE_GENERATOR: Ninja steps: - uses: actions/checkout@v5 - name: fix flaky azure mirrors if: ${{ runner.os == 'Linux' }} run: | sudo sed -i 's/azure\./de\./' /etc/apt/sources.list - name: install packages run: | sudo apt-get update && sudo apt-get install -y libssl-dev valgrind ninja-build - name: make run: | cmake -B build && cmake --build build -j -t retest - name: retest run: | valgrind --leak-check=full --show-reachable=yes --error-exitcode=42 ./build/test/retest -r -v ================================================ FILE: .gitignore ================================================ /build* /dist test.d test.o *stamp *.previous libre.a libre.*dylib libre.pc libre.so* *.gcov # Windows build folder Win32/* # Visual studio config mk/win32/baresip.vcxproj.user # ctags tags # Vim swp files *.swp # clangd .cache compile_commands.json # Visual studio config mk/win32/*.vcxproj.user ================================================ FILE: CHANGELOG.md ================================================ # libre Changelog All notable changes to libre will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## v4.8.0 - 2026-05-13 ## What's Changed * ci/cmake_win: fix choco openssl by @sreimers in https://github.com/baresip/re/pull/1563 * test,sipreg: enable outbound testing by @alfredh in https://github.com/baresip/re/pull/1553 * ci/windows: roll back choco openssl version 3.6.2 by @alfredh in https://github.com/baresip/re/pull/1566 * hash: safety check for string by @cspiel1 in https://github.com/baresip/re/pull/1562 * ci/freebsd: use freebsd 14.4 and pkg update fixes by @sreimers in https://github.com/baresip/re/pull/1567 * tls: upgrade usage of legacy functions by @alfredh in https://github.com/baresip/re/pull/1565 * ci/coverage: increase minimum coverage to 70% by @alfredh in https://github.com/baresip/re/pull/1564 * tls: use SSL_set1_dnsname() for OpenSSL 4.0.0 and later by @alfredh in https://github.com/baresip/re/pull/1569 * tls/openssl/sni: fix OpenSSL 4 deprecation and refactor by @sreimers in https://github.com/baresip/re/pull/1570 * http: Send HTTP request with explicit server address by @fAuernigg in https://github.com/baresip/re/pull/1571 * tls/openssl: fix OpenSSL 4.0.0 consts by @sreimers in https://github.com/baresip/re/pull/1572 * ci/ssl: bump no-deprecated OpenSSL and LibreSSL by @sreimers in https://github.com/baresip/re/pull/1573 * readme: update supported OpenSSL and LibreSSL versions by @sreimers in https://github.com/baresip/re/pull/1574 * mem: update mem_debug_tail() doxygen comment by @alfredh in https://github.com/baresip/re/pull/1576 **Full Changelog**: https://github.com/baresip/re/compare/v4.7.0...v4.8.0 ## v4.7.0 - 2026-04-07 ### What's Changed * test: use dns_rrlist_apply() in mock DNS-server by @alfredh in https://github.com/baresip/re/pull/1532 * sip: fix missing auth algorithm for known realm by @cspiel1 in https://github.com/baresip/re/pull/1535 * cmake,unixsock: always add unixsock header and functions by @sreimers in https://github.com/baresip/re/pull/1536 * test/dns: save error code from async work in DNS-test by @alfredh in https://github.com/baresip/re/pull/1538 * docs: update list of modules in README.md by @alfredh in https://github.com/baresip/re/pull/1537 * sipsess/modify: fix calling of offer handler by @maximilianfridrich in https://github.com/baresip/re/pull/1539 * http/server: cleanup of http_verify_msg_d by @cspiel1 in https://github.com/baresip/re/pull/1542 * test: add permission handler to TURN test by @alfredh in https://github.com/baresip/re/pull/1541 * sip/dialog: fix OOM scenarios is sip_dialog_update by @maximilianfridrich in https://github.com/baresip/re/pull/1543 * test/turnsrv: add LIFETIME attribute to allocation response by @alfredh in https://github.com/baresip/re/pull/1545 * test: add testing of websock_tcp() in websock testcases by @alfredh in https://github.com/baresip/re/pull/1544 * test: split turn-test into permissions/channels by @alfredh in https://github.com/baresip/re/pull/1547 * test: add negative testcases in unixsock by @alfredh in https://github.com/baresip/re/pull/1546 * trace: flush operations must not run concurrently by @sreimers in https://github.com/baresip/re/pull/1550 * test,turn: add forced error code and test more combinations by @alfredh in https://github.com/baresip/re/pull/1551 * test: use TEST_ERR() to check async errors in RTCP-test by @alfredh in https://github.com/baresip/re/pull/1549 * test,odict: add testing of more api functions by @alfredh in https://github.com/baresip/re/pull/1552 * ci/abi: bump old version by @sreimers in https://github.com/baresip/re/pull/1560 **Full Changelog**: https://github.com/baresip/re/compare/v4.6.0...v4.7.0 ## v4.6.0 - 2026-03-04 ### What's Changed * ci: bump OpenSSL version for Windows job by @alfredh in https://github.com/baresip/re/pull/1509 * async: print number of workers by @alfredh in https://github.com/baresip/re/pull/1511 * dns: remove usage of domain parameter from dns_srv_get() by @alfredh in https://github.com/baresip/re/pull/1510 * thread: with c23 once_flag is mandatory by @sreimers in https://github.com/baresip/re/pull/1516 * cmake: fix some cmakelint warnings by @alfredh in https://github.com/baresip/re/pull/1517 * test/dns: add AAAA tests by @cspiel1 in https://github.com/baresip/re/pull/1515 * test: add testcase for DNS over TCP by @alfredh in https://github.com/baresip/re/pull/1508 * mem: add mem_debug_tail by @sreimers in https://github.com/baresip/re/pull/1524 * rtp: rtp_listen_single() - try multiple ports by @cspiel1 in https://github.com/baresip/re/pull/1526 * test: add testcode for stun_server_discover() by @alfredh in https://github.com/baresip/re/pull/1522 * test: add testing of dns_rr_print() by @alfredh in https://github.com/baresip/re/pull/1518 * test: add testing of DNS query from handler by @alfredh in https://github.com/baresip/re/pull/1528 * dns: add dns_rr_dup and test_dns_rr_dup by @maximilianfridrich in https://github.com/baresip/re/pull/1512 * sip/request: duplicate RR entries from DNS by @maximilianfridrich in https://github.com/baresip/re/pull/1529 * test: add testing of DNS header code names by @alfredh in https://github.com/baresip/re/pull/1530 **Full Changelog**: https://github.com/baresip/re/compare/v4.5.0...v4.6.0 ## v4.5.0 - 2026-01-28 ### What's Changed * net: remove net_if_getaddr4() -- deprecated by @alfredh in https://github.com/baresip/re/pull/1494 * test: add testing of dtls_set_handlers() api by @alfredh in https://github.com/baresip/re/pull/1495 * h265: use h264_find_startcode() -- duplicated code by @alfredh in https://github.com/baresip/re/pull/1493 * fmt: str_bool: reuse similar logic in pl_bool() by @alfredh in https://github.com/baresip/re/pull/1496 * net: cleanup fallback return by @sreimers in https://github.com/baresip/re/pull/1497 * fmt/print: add backtrace for incompatible format arguments by @sreimers in https://github.com/baresip/re/pull/1499 * btrace: fix for linux addr2line by @cspiel1 in https://github.com/baresip/re/pull/1500 * rtp: add RTP listen on single port by @cspiel1 in https://github.com/baresip/re/pull/1498 * copyright: update for new year by @Clusters in https://github.com/baresip/re/pull/1502 * async: do not hold lock during cb call by @cspiel1 in https://github.com/baresip/re/pull/1503 * docs: fix README.md document include by @sreimers in https://github.com/baresip/re/pull/1501 * tmr: improve thread list lock handling by @sreimers in https://github.com/baresip/re/pull/1504 * aumix: add aumix_source_put_auframe and deprecate aumix_source_put by @sreimers in https://github.com/baresip/re/pull/1505 * rtp/sess: fix ts_arrive calculation by @sreimers in https://github.com/baresip/re/pull/1506 ## New Contributors * @Clusters made their first contribution in https://github.com/baresip/re/pull/1502 **Full Changelog**: https://github.com/baresip/re/compare/v4.4.0...v4.5.0 ## v4.4.0 - 2025-12-22 ### What's Changed * fmt: add pl_alloc_dup() by @cspiel1 in https://github.com/baresip/re/pull/1469 * tools: add genfir python script from librem by @alfredh in https://github.com/baresip/re/pull/1471 * test: av1 obu print by @alfredh in https://github.com/baresip/re/pull/1473 * genfir: upgrade to python 3 by @alfredh in https://github.com/baresip/re/pull/1472 * test: add negative conf tests by @alfredh in https://github.com/baresip/re/pull/1474 * turn: add channel peer mutex locking by @sreimers in https://github.com/baresip/re/pull/1478 * test: remove h265 fragment handling by @alfredh in https://github.com/baresip/re/pull/1477 * test: add testing of re_text2pcap_trace() by @alfredh in https://github.com/baresip/re/pull/1476 * dd: change dd_print() to struct re_printf *pf and add test by @alfredh in https://github.com/baresip/re/pull/1475 * rtp, stun/msg: doxygen fixes by @sreimers in https://github.com/baresip/re/pull/1479 * trace: add trace line handler by @sreimers in https://github.com/baresip/re/pull/1460 * trace: deref id after new trace handler by @sreimers in https://github.com/baresip/re/pull/1481 * test: add testing of dbg module by @alfredh in https://github.com/baresip/re/pull/1480 * aumix: implement aumix_source_set_id by @sreimers in https://github.com/baresip/re/pull/1482 * dns: check memory allocation in get_resolv_dns() by @alfredh in https://github.com/baresip/re/pull/1484 * test: check errors in turn_thread() test by @alfredh in https://github.com/baresip/re/pull/1485 * test: remove rotate from mock DNS-server by @alfredh in https://github.com/baresip/re/pull/1487 * test: check re_main_timeout() return value in test_rtp_listen_priv() by @alfredh in https://github.com/baresip/re/pull/1488 * h265,test: improve testing and usage of h265_nal_print() by @alfredh in https://github.com/baresip/re/pull/1486 * test: more testing of IPv6 protocol by @alfredh in https://github.com/baresip/re/pull/1490 * stun: remove PADDING attribute by @alfredh in https://github.com/baresip/re/pull/1491 * stun: remove natbd strings (deprecated) by @alfredh in https://github.com/baresip/re/pull/1489 **Full Changelog**: https://github.com/baresip/re/compare/v4.3.0...v4.4.0 ## v4.3.0 - 2025-11-19 ### What's Changed * cmake: remove macOS include path by @mohd-akram in https://github.com/baresip/re/pull/1449 * test: sort testcases in alphabetical order by @alfredh in https://github.com/baresip/re/pull/1447 * test: increase coverage of websock test with protocol on/off by @alfredh in https://github.com/baresip/re/pull/1446 * sdp/media: fix sdp_media_align_formats pt handling by @sreimers in https://github.com/baresip/re/pull/1450 * dns: fix AAAA address comparison in getaddr_dup() by @alfredh in https://github.com/baresip/re/pull/1452 * test: add support for IPv6 DNS testing by @alfredh in https://github.com/baresip/re/pull/1454 * ci: add clang-21 by @sreimers in https://github.com/baresip/re/pull/1453 * sys/fs: improve fs_fread error handling by @sreimers in https://github.com/baresip/re/pull/1455 * test: compare DNS RR records data in order to increase test-coverage by @alfredh in https://github.com/baresip/re/pull/1458 * dns: correct comment in dnsc_query_srv() by @alfredh in https://github.com/baresip/re/pull/1457 * h265: Fix NAL Decode nuh_layer_id by @xiaokuang95 in https://github.com/baresip/re/pull/1456 * auframe: avoid auframe_bytes_to_ms division by zero by @sreimers in https://github.com/baresip/re/pull/1459 * aumix: add aumix_latency and new defaults by @sreimers in https://github.com/baresip/re/pull/1461 * dns: remove get_android_dns() by @alfredh in https://github.com/baresip/re/pull/1464 * test: add testing of DNS nameservers by @alfredh in https://github.com/baresip/re/pull/1462 * cmake/re-config: fix HAVE_THREADS discovery by @sreimers in https://github.com/baresip/re/pull/1466 ### New Contributors * @mohd-akram made their first contribution in https://github.com/baresip/re/pull/1449 * @xiaokuang95 made their first contribution in https://github.com/baresip/re/pull/1456 **Full Changelog**: https://github.com/baresip/re/compare/v4.2.0...v4.3.0 ## v4.2.0 - 2025-10-15 ### What's Changed * test: add testcode for btrace module by @alfredh in https://github.com/baresip/re/pull/1414 * types: add ETIME fallback by @sreimers in https://github.com/baresip/re/pull/1420 * test: add testing of conf_get_bool() by @alfredh in https://github.com/baresip/re/pull/1419 * test/btrace: skip thread test by @sreimers in https://github.com/baresip/re/pull/1422 * Revert "dtls: remove dtls_set_handlers() -- unused" by @sreimers in https://github.com/baresip/re/pull/1421 * ice/icem: add icem_rcand_ready helper by @sreimers in https://github.com/baresip/re/pull/1424 * ice/sdp: remove mDNS AI_V4MAPPED and log late candidate by @sreimers in https://github.com/baresip/re/pull/1423 * tls: minor improvements to SNI and Common-name comparison by @alfredh in https://github.com/baresip/re/pull/1425 * tls: revert wrong match-checking in SNI function by @alfredh in https://github.com/baresip/re/pull/1427 * ci-windows: bump choco openssl version to 3.5.3 by @alfredh in https://github.com/baresip/re/pull/1426 * tls: sni - a null pointer check by @cspiel1 in https://github.com/baresip/re/pull/1430 * test: fix some minor typos by @alfredh in https://github.com/baresip/re/pull/1429 * dbg: remove dbg_close() -- unused by @alfredh in https://github.com/baresip/re/pull/1428 * ci,windows: bump choco openssl to 3.5.4 by @alfredh in https://github.com/baresip/re/pull/1433 * misc: fix some minor typos by @alfredh in https://github.com/baresip/re/pull/1432 * test: test both fragmented and non-fragmented H.265 packets by @alfredh in https://github.com/baresip/re/pull/1434 * test: add negative AES testcases by @alfredh in https://github.com/baresip/re/pull/1435 * test: add test for conf_apply() by @alfredh in https://github.com/baresip/re/pull/1436 * ci/android: Upgrade to API-level 29 (Android 10.0) by @alfredh in https://github.com/baresip/re/pull/1439 * ci/android: remove AVD cache by @sreimers in https://github.com/baresip/re/pull/1442 * ci/android: revert to android api level 26 by @sreimers in https://github.com/baresip/re/pull/1443 * bump version number to 4.2.0 by @alfredh in https://github.com/baresip/re/pull/1440 **Full Changelog**: https://github.com/baresip/re/compare/v4.1.0...v4.2.0 ## v4.1.0 - 2025-09-10 ### What's Changed * ci: temporary workaround for choco openssl failure by @alfredh in https://github.com/baresip/re/pull/1395 * test: add support for IPv6 on UDP-test by @alfredh in https://github.com/baresip/re/pull/1390 * ci: enable Windows testing when OpenSSL is disabled by @alfredh in https://github.com/baresip/re/pull/1392 * websock: remove unused peer member by @alfredh in https://github.com/baresip/re/pull/1396 * test: add testing of udp_rxsz_set() and udp_sockbuf_set() by @alfredh in https://github.com/baresip/re/pull/1397 * ci/build: select xcode version 16.2 by @sreimers in https://github.com/baresip/re/pull/1400 * udp: combine udp_recv_helper() and udp_recv_packet() by @alfredh in https://github.com/baresip/re/pull/1398 * test: add support for UDP multicast test by @alfredh in https://github.com/baresip/re/pull/1402 * ci: update actions/checkout@v5 by @sreimers in https://github.com/baresip/re/pull/1403 * uri: remove uri_escape_user() by @alfredh in https://github.com/baresip/re/pull/1401 * uri: remove some unused escape functions by @alfredh in https://github.com/baresip/re/pull/1404 * test: add support for IPv6 and TURN by @alfredh in https://github.com/baresip/re/pull/1405 * test: add support for testing more DTLS-SRTP suites by @alfredh in https://github.com/baresip/re/pull/1408 * dtls: remove dtls_set_handlers() -- unused by @alfredh in https://github.com/baresip/re/pull/1407 * tls: remove tls_set_certificate_der() -- unused by @alfredh in https://github.com/baresip/re/pull/1410 * test: set low MTU in DTLS-test by @alfredh in https://github.com/baresip/re/pull/1411 * test: add support for TURN mock-server authentication by @alfredh in https://github.com/baresip/re/pull/1409 * tls: tls_set_resumption() -- change const enum to enum by @alfredh in https://github.com/baresip/re/pull/1412 * ci/abi: bump old abi by @sreimers in https://github.com/baresip/re/pull/1417 * ci/coverage: bump min coverage by @sreimers in https://github.com/baresip/re/pull/1416 **Full Changelog**: https://github.com/baresip/re/compare/v4.0.0...v4.1.0 ## v4.0.0 - 2025-08-06 ### What's Changed This major release drops obsolete API functions, OpenSSL 1.1.1 support and support for old OS versions. The breaking changes are discussed here: https://github.com/baresip/re/discussions/1372 * rem: remove backwards wrapper for au_calc_nsamp() by @alfredh in https://github.com/baresip/re/pull/1366 * rem: remove local macros, include stdint.h instead by @alfredh in https://github.com/baresip/re/pull/1369 * mod: remove unused MOD_PRE macro by @alfredh in https://github.com/baresip/re/pull/1370 * tcp: remove special case for mingw32/wine by @alfredh in https://github.com/baresip/re/pull/1367 * dd: update AV1 and DD docs by @alfredh in https://github.com/baresip/re/pull/1376 * test: fix formatted string arguments in URI testcode by @alfredh in https://github.com/baresip/re/pull/1375 * tls: drop OpenSSL 1.1.1 support by @sreimers in https://github.com/baresip/re/pull/1371 * Update supported OS versions by @sreimers in https://github.com/baresip/re/pull/1373 * readme: update supported compilers by @sreimers in https://github.com/baresip/re/pull/1374 * tls: disable tls_conn_change_cert for LibreSSL by @sreimers in https://github.com/baresip/re/pull/1377 * ci/ssl: bump ssl tools assets by @sreimers in https://github.com/baresip/re/pull/1127 * aubuf: remove unused struct auframe in ajb.c by @alfredh in https://github.com/baresip/re/pull/1378 * aubuf: remove unused private function plot_underrun() by @alfredh in https://github.com/baresip/re/pull/1380 * readme: bump supported GNU C library (glibc) 2.31 by @sreimers in https://github.com/baresip/re/pull/1379 * misc: remove deprecated functions/params by @sreimers in https://github.com/baresip/re/pull/1384 * uri: remove password field by @alfredh in https://github.com/baresip/re/pull/1382 * websock: increase test coverage by @alfredh in https://github.com/baresip/re/pull/1381 * udp: remove obsolete todo in udp_local_get() by @alfredh in https://github.com/baresip/re/pull/1386 * av1: remove av1_packetize_new() -- backwards compat wrapper by @alfredh in https://github.com/baresip/re/pull/1385 * tls: remove tls_set_selfsigned_rsa() -- use Elliptic Curve instead by @alfredh in https://github.com/baresip/re/pull/1388 * test: enable dtls_set_single() for DTLS client test by @alfredh in https://github.com/baresip/re/pull/1387 **Full Changelog**: https://github.com/baresip/re/compare/v3.24.0...v4.0.0 ## v3.24.0 - 2025-07-09 ### What's Changed * list: add LIST_FOREACH_SAFE helper macro by @sreimers in https://github.com/baresip/re/pull/1343 * test: SDP interop testing by @alfredh in https://github.com/baresip/re/pull/1341 * ci/abi: bump old ref version by @sreimers in https://github.com/baresip/re/pull/1344 * list: improve already linked warning by @sreimers in https://github.com/baresip/re/pull/1345 * ci/mingw: use windows-latest by @sreimers in https://github.com/baresip/re/pull/1346 * ci/clang: use clang-20 by @sreimers in https://github.com/baresip/re/pull/1348 * av1: rename av1_packetize_new() and add wrapper by @alfredh in https://github.com/baresip/re/pull/1349 * trice: update header to match filename by @alfredh in https://github.com/baresip/re/pull/1350 * rtp/rr: fix fraction left shift promotion by @sreimers in https://github.com/baresip/re/pull/1353 * test/sipsess: cast rel100_mode by @sreimers in https://github.com/baresip/re/pull/1354 * test: print trice_debug to buffer to test debug functions by @alfredh in https://github.com/baresip/re/pull/1352 * trice: update doxygen documentation by @alfredh in https://github.com/baresip/re/pull/1351 * rtp: add rtp_seq_less inline function helper by @sreimers in https://github.com/baresip/re/pull/1355 * trice: remove trice_set_software() by @alfredh in https://github.com/baresip/re/pull/1359 * trice: always enable PRFLX candidates by @alfredh in https://github.com/baresip/re/pull/1360 * rtp: add TWCC packet definition helpers by @sreimers in https://github.com/baresip/re/pull/1357 * test: add support check for SRTP GCM test cases by @alfredh in https://github.com/baresip/re/pull/1361 * trice: add more doxygen comments by @alfredh in https://github.com/baresip/re/pull/1362 * prepare for release -- bump version to 3.24.0 by @alfredh in https://github.com/baresip/re/pull/1365 **Full Changelog**: https://github.com/baresip/re/compare/v3.23.0...v3.24.0 ## v3.23.0 - 2025-06-04 ### What's Changed * fmt/pl: optimize pl_bool return early by @sreimers in https://github.com/baresip/re/pull/1311 * ci/coverage: bump min_cov by @sreimers in https://github.com/baresip/re/pull/1312 * av1: allow OBU type 0 (RESERVED) by @alfredh in https://github.com/baresip/re/pull/1313 * ci: remove step to install ninja on macOS by @alfredh in https://github.com/baresip/re/pull/1315 * rtp: remove unused function ntp2unix() by @alfredh in https://github.com/baresip/re/pull/1314 * tls: fix compiler warning with libressl 4.1.0 by @alfredh in https://github.com/baresip/re/pull/1318 * vhprintf: change from char to int to store error number from %m by @alfredh in https://github.com/baresip/re/pull/1319 * http: cancel verify_cert_tmr and skip init by @alfredh in https://github.com/baresip/re/pull/1317 * mem: update doxygen comments in mem_pool.c by @alfredh in https://github.com/baresip/re/pull/1320 * tls: fix LibreSSL SSL_verify_cb by @sreimers in https://github.com/baresip/re/pull/1322 * cmake,http: add HAVE_TLS1_3_POST_HANDSHAKE_AUTH by @sreimers in https://github.com/baresip/re/pull/1323 * av1: remove deprecated av1_packetize_high() by @alfredh in https://github.com/baresip/re/pull/1321 * tls: fix SSL_SESSION_is_resumable LibreSSL by @sreimers in https://github.com/baresip/re/pull/1324 * sip: exposed ltag and rtag in sip dialog by @gordongrech in https://github.com/baresip/re/pull/1326 * sipsess: add cancel reason to close handler by @KillingSpark in https://github.com/baresip/re/pull/1325 * h265: add H265_NAL_RSV_IRAP_VCL{22,23} by @alfredh in https://github.com/baresip/re/pull/1327 * test/httpauth: fix debug warning for digest response by @cspiel1 in https://github.com/baresip/re/pull/1332 * sha: use PROV_RSA_AES for CryptAcquireContext by @alfredh in https://github.com/baresip/re/pull/1331 * test: Add test for thread tss by @weili-jiang in https://github.com/baresip/re/pull/1334 * 100rel improvements by @maximilianfridrich in https://github.com/baresip/re/pull/1333 * test/async: Remove unsupported AI_V4MAPPED on Android by @weili-jiang in https://github.com/baresip/re/pull/1336 * test/sys: Use writeable datapath for fs_open test by @weili-jiang in https://github.com/baresip/re/pull/1335 * net: Support ifaddrs on Android API level >= 24 by @weili-jiang in https://github.com/baresip/re/pull/1338 * ci: Enable testing on Android by @weili-jiang in https://github.com/baresip/re/pull/1337 * test/sipsess: fix test_sipsess_100rel_answer_not_allowed by @maximilianfridrich in https://github.com/baresip/re/pull/1340 ### New Contributors * @gordongrech made their first contribution in https://github.com/baresip/re/pull/1326 * @KillingSpark made their first contribution in https://github.com/baresip/re/pull/1325 **Full Changelog**: https://github.com/baresip/re/compare/v3.22.0...v3.23.0 ## v3.22.0 - 2025-04-30 ### What's Changed * rtp: remove unused int proto by @alfredh in https://github.com/baresip/re/pull/1296 * mbuf: null pointer checks for inline functions by @cspiel1 in https://github.com/baresip/re/pull/1294 * test: more coverage in rtcp_loop by @alfredh in https://github.com/baresip/re/pull/1295 * ci: remove version from choco install openssl by @alfredh in https://github.com/baresip/re/pull/1297 * net/linux/addrs: use malloc for buffer by @sreimers in https://github.com/baresip/re/pull/1298 * cmake: update cmake_minimum_required 3.18...4.0 by @sreimers in https://github.com/baresip/re/pull/1291 * ci: upgrade mingw to openssl 3.5.0 by @alfredh in https://github.com/baresip/re/pull/1299 * ci/abi: bump ref version by @sreimers in https://github.com/baresip/re/pull/1300 * rtp: RTCP Extended report by @shrim27 in https://github.com/baresip/re/pull/1302 * test: move test_rtcp_xr_rrtr to rtcp.c by @alfredh in https://github.com/baresip/re/pull/1306 * Handle "w" properly in the AV1 packetizer by @npcook in https://github.com/baresip/re/pull/1305 * test: add coverage of RTCP-XR DLRR by @alfredh in https://github.com/baresip/re/pull/1307 * av1: remove deprecated av1_packetize_one_w() by @alfredh in https://github.com/baresip/re/pull/1308 ## New Contributors * @shrim27 made their first contribution in https://github.com/baresip/re/pull/1302 * @npcook made their first contribution in https://github.com/baresip/re/pull/1305 **Full Changelog**: https://github.com/baresip/re/compare/v3.21.1...v3.22.0 ## v3.21.1 - 2025-04-04 ### What's Changed * vidconv: fix vidconv_center underflows by @sreimers in https://github.com/baresip/re/pull/1287 * mem: fix buffer overflow in mem_realloc by @maximilianfridrich in https://github.com/baresip/re/pull/1289 * mem/mem_pool: use pointer-pointer to prevent heap-use-after-free by @sreimers in https://github.com/baresip/re/pull/1290 **Full Changelog**: https://github.com/baresip/re/compare/v3.21.0...v3.21.1 ## v3.21.0 - 2025-03-26 ### What's Changed * fmt/pl: add pl_strncasecmp and tests by @sreimers in https://github.com/baresip/re/pull/1277 * ci: upgrade run-on-arch-action to fix random segfaults by @alfredh in https://github.com/baresip/re/pull/1280 * test: add testing of RTCP FIR-RFC5104 by @alfredh in https://github.com/baresip/re/pull/1281 * rtpext: add rtpext_find by @sreimers in https://github.com/baresip/re/pull/1282 * rtp: add rtp_ prefix to member functions by @alfredh in https://github.com/baresip/re/pull/1283 * rtp/rtcp: add rtcp_send_twcc and rtcp_rtpfb_twcc_encode by @sreimers in https://github.com/baresip/re/pull/1285 * list: optimize list_count by @sreimers in https://github.com/baresip/re/pull/1284 * bump version to 3.21.0 by @alfredh in https://github.com/baresip/re/pull/1286 **Full Changelog**: https://github.com/baresip/re/compare/v3.20.0...v3.21.0 ## v3.20.0 - 2025-02-18 ### What's Changed * http/server: increase BUFSIZE_MAX to 1 MB and add http_set_max_body_size by @sreimers in https://github.com/baresip/re/pull/1262 * test: init err to zero (fixes cppcheck warning) by @alfredh in https://github.com/baresip/re/pull/1265 * test: add RTCP_APP to RTCP test by @alfredh in https://github.com/baresip/re/pull/1266 * mem,aubuf: add pre-allocated memory pool management by @sreimers in https://github.com/baresip/re/pull/1255 * test: increase test_oom levels and oom fixes by @sreimers in https://github.com/baresip/re/pull/1260 * mem/mem_pool: fix mem_pool_extend new member destructor by @sreimers in https://github.com/baresip/re/pull/1267 * ci: bump version and min_cov by @sreimers in https://github.com/baresip/re/pull/1268 * av1: remove duplicate/unused getbit.c by @alfredh in https://github.com/baresip/re/pull/1272 * test/cmake: link C++ lib by @sreimers in https://github.com/baresip/re/pull/1269 * http: restart timer for each chunk by @fAuernigg in https://github.com/baresip/re/pull/1273 * ci/valgrind: use ubuntu-latest by @sreimers in https://github.com/baresip/re/pull/1274 **Full Changelog**: https://github.com/baresip/re/compare/v3.19.0...v3.20.0 ## v3.19.0 - 2025-01-15 ### What's Changed * fmt: fix pl trim methods and add tests by @maximilianfridrich in https://github.com/baresip/re/pull/1226 * sipsess: add sipsess_msg getter function by @cspiel1 in https://github.com/baresip/re/pull/1225 * rtp/sess: fix missing srate_tx locking by @sreimers in https://github.com/baresip/re/pull/1231 * rtcp: use rtcp_rtpfb_gnack_encode() function by @alfredh in https://github.com/baresip/re/pull/1233 * net/linux: add net_netlink_addrs by @sreimers in https://github.com/baresip/re/pull/1232 * tcp,udp: set TOS (TCLASS) for IPv6 sockets by @maximilianfridrich in https://github.com/baresip/re/pull/1218 * sys/fs: fix fs_fopen return null check by @sreimers in https://github.com/baresip/re/pull/1237 * test: remove mock tcp-server (unused) by @alfredh in https://github.com/baresip/re/pull/1235 * rtp: remove rtcp_psfb_sli_encode() (unused) by @alfredh in https://github.com/baresip/re/pull/1234 * ci/clang: bump clang-18 and use ubuntu 24.04 by @sreimers in https://github.com/baresip/re/pull/1236 * net/linux/addrs: fix point-to-point peer address bug by @sreimers in https://github.com/baresip/re/pull/1239 * ci/coverage: bump min_cov by @sreimers in https://github.com/baresip/re/pull/1241 * ci/sanitizers: bump clang and ubuntu by @sreimers in https://github.com/baresip/re/pull/1242 * net/linux/addrs: fix netlink kernel warnings by @sreimers in https://github.com/baresip/re/pull/1243 * rem: add au_ prefix to calc_nsamp() by @alfredh in https://github.com/baresip/re/pull/1244 * rem/vidconv: add vidconv_center and x and y source offsets by @sreimers in https://github.com/baresip/re/pull/1240 * test: add testcode for rem au-module by @alfredh in https://github.com/baresip/re/pull/1245 * mem: remove peak from memstat by @alfredh in https://github.com/baresip/re/pull/1238 * debian: replace with CPack DEB Generator by @sreimers in https://github.com/baresip/re/pull/1247 * copyright: happy new year 2025 by @sreimers in https://github.com/baresip/re/pull/1246 * test/vidconv: remove static struct test by @sreimers in https://github.com/baresip/re/pull/1248 * net/linux/addrs: use list instead of fixed array for interface up by @sreimers in https://github.com/baresip/re/pull/1251 * test: optional IPv6 for tcp/udp tos test by @alfredh in https://github.com/baresip/re/pull/1252 * cmake: update min requirement and use range by @sreimers in https://github.com/baresip/re/pull/1253 * rem/vid/frame: fix vidframe init by @sreimers in https://github.com/baresip/re/pull/1257 * atomic: fix compilation for C++ and Windows-ARM64 by @alfredh in https://github.com/baresip/re/pull/1259 * test: add test for C++ applications by @alfredh in https://github.com/baresip/re/pull/1254 * ci: use ubuntu-22.04 were needed by @sreimers in https://github.com/baresip/re/pull/1261 * cmake: enable compiler warnings for C only by @alfredh in https://github.com/baresip/re/pull/1263 **Full Changelog**: https://github.com/baresip/re/compare/v3.18.0...v3.19.0 ## v3.18.0 - 2024-12-11 ### What's Changed * odict: add odict_pl_add() by @cspiel1 in https://github.com/baresip/re/pull/1208 * ci/build: remove Ubuntu 20.04, add 24.04, use GCC 14 on 24.04 by @robert-scheck in https://github.com/baresip/re/pull/1210 * test: vertical alignment of integration test names by @alfredh in https://github.com/baresip/re/pull/1212 * sip: update doxygen comment by @alfredh in https://github.com/baresip/re/pull/1215 * test/http: decrease test runs from 20 to 3 to decrease test time by @fAuernigg in https://github.com/baresip/re/pull/1216 * sip/transp: allow requests w/o Max-Forwards header by @cspiel1 in https://github.com/baresip/re/pull/1217 * test: remove unused fuzz mock by @alfredh in https://github.com/baresip/re/pull/1220 * rtp: use rtp_pt_is_rtcp() for RTCP demultiplexing by @alfredh in https://github.com/baresip/re/pull/1221 * aes: remove 192-bits CTR-mode (looks unused) by @alfredh in https://github.com/baresip/re/pull/1219 * rtp: send all RTCP packets as compound packets by @maximilianfridrich in https://github.com/baresip/re/pull/1222 * rtp/sess.c: lock rtcp_sess in rtcp_set_srate_tx to fix data race by @maximilianfridrich in https://github.com/baresip/re/pull/1223 * Update Doxyfile by @alfredh in https://github.com/baresip/re/pull/1224 * test: remove unused packet-filter mock by @alfredh in https://github.com/baresip/re/pull/1227 * bump version to 3.18.0 by @alfredh in https://github.com/baresip/re/pull/1230 **Full Changelog**: https://github.com/baresip/re/compare/v3.17.0...v3.18.0 ## v3.17.0 - 2024-11-06 ### What's Changed * types: remove old BREAKPOINT macro by @alfredh in https://github.com/baresip/re/pull/1194 * dnsc: Fallback to getaddrinfo without any DNS servers by @weili-jiang in https://github.com/baresip/re/pull/1195 * dns/client: return ENOTSUP if no server or not getaddrinfo by @sreimers in https://github.com/baresip/re/pull/1196 * conf: add conf_get_float by @juha-h in https://github.com/baresip/re/pull/1198 * ci/run-on-arch: use ubuntu 22.04 by @sreimers in https://github.com/baresip/re/pull/1204 * thread: fix thrd_equal win32 handle by @sreimers in https://github.com/baresip/re/pull/1203 * test: add pktsize to test_h264_packet_base() by @alfredh in https://github.com/baresip/re/pull/1205 * tls: make tls_verify_handler() static by @alfredh in https://github.com/baresip/re/pull/1201 * types: fix clang-tidy warning (gcc bit fields workaround) by @sreimers in https://github.com/baresip/re/pull/1206 ### New Contributors * @weili-jiang made their first contribution in https://github.com/baresip/re/pull/1195 **Full Changelog**: https://github.com/baresip/re/compare/v3.16.0...v3.17.0 ## v3.16.0 - 2024-10-02 ### What's Changed * thread: fix pthread_setname_np NetBSD by @leleliu008 in https://github.com/baresip/re/pull/1182 * ice: AI_V4MAPPED macro is missing on some BSD systems by @leleliu008 in https://github.com/baresip/re/pull/1181 * rtp/rtcp: add RTCP Generic NACK packet send (RFC 4585 6.2.1) by @sreimers in https://github.com/baresip/re/pull/1186 * main/fd_listen: return EMFILE if maxfds is reached by @sreimers in https://github.com/baresip/re/pull/1185 * ci: build retest for android by @alfredh in https://github.com/baresip/re/pull/1187 * test: minor cmake cleanup by @alfredh in https://github.com/baresip/re/pull/1188 * test: fix re_printf format string for multithread test by @alfredh in https://github.com/baresip/re/pull/1190 * ci: run retest on Fedora by @alfredh in https://github.com/baresip/re/pull/1191 ### New Contributors * @leleliu008 made their first contribution in https://github.com/baresip/re/pull/1182 **Full Changelog**: https://github.com/baresip/re/compare/v3.15.0...v3.16.0 ## v3.15.0 - 2024-08-28 ### What's Changed * misc: remove HAVE_INET6 by @sreimers in https://github.com/baresip/re/pull/1159 * dns/rr: fix dns_rr_print underflow by @sreimers in https://github.com/baresip/re/pull/1162 * test/async: remove AI_ADDRCONFIG by @sreimers in https://github.com/baresip/re/pull/1165 * retest: update usage message by @robert-scheck in https://github.com/baresip/re/pull/1166 * add filter_registrar option by @maximilianfridrich in https://github.com/baresip/re/pull/1160 * sa: add utility function to check if address is multicast by @cmfitch1 in https://github.com/baresip/re/pull/1168 * tls/sni: skip SNI check if we are client or server_name absent by @maximilianfridrich in https://github.com/baresip/re/pull/1169 * tls/sni: do not enable client verification when SNI matching is done by @maximilianfridrich in https://github.com/baresip/re/pull/1172 * dd: Dependency Descriptor RTP header extension by @alfredh in https://github.com/baresip/re/pull/1170 * aubuf: add AUBUF_TRACE mode with id by @sreimers in https://github.com/baresip/re/pull/1174 * sip/transp: add client certificate to all TLS transports by @maximilianfridrich in https://github.com/baresip/re/pull/1173 * tmr: add TMR_INIT by @sreimers in https://github.com/baresip/re/pull/1177 * sipsess/reply: fix heap-use-after-free bug by @sreimers in https://github.com/baresip/re/pull/1179 * version 3.15.0 by @alfredh in https://github.com/baresip/re/pull/1180 ### New Contributors * @cmfitch1 made their first contribution in https://github.com/baresip/re/pull/1168 **Full Changelog**: https://github.com/baresip/re/compare/v3.14.0...v3.15.0 ## [v3.14.0] - 2024-07-23 ### What's Changed * aumix: use mutex_alloc() by @alfredh in https://github.com/baresip/re/pull/1142 * sipreg/reg.c: stop retrying registers early after 401/407 by @maximilianfridrich in https://github.com/baresip/re/pull/1143 * aumix: add locking in aumix_source_count() by @alfredh in https://github.com/baresip/re/pull/1145 * test: init err in test_sip_auth_encode() by @alfredh in https://github.com/baresip/re/pull/1146 * sipreg: refactor response_handler else optimization by @sreimers in https://github.com/baresip/re/pull/1147 * vidmix: improve mutex usage by @alfredh in https://github.com/baresip/re/pull/1148 * udp/mcast: use group scopeid as interface for IPv6 by @maximilianfridrich in https://github.com/baresip/re/pull/1149 * .clangd: suppress -Wgnu-zero-variadic-macro-arguments by @maximilianfridrich in https://github.com/baresip/re/pull/1150 * ci/build: use only macos-latest by @sreimers in https://github.com/baresip/re/pull/1153 * cmake: fix resolv on FreeBSD by @sreimers in https://github.com/baresip/re/pull/1152 * test: use h264_stap_decode_annexb() by @alfredh in https://github.com/baresip/re/pull/1151 * sipsess/reply: terminate session if no (PR)ACK received after 64*T1 by @maximilianfridrich in https://github.com/baresip/re/pull/1155 * rtcp: send BYE manually by @alfredh in https://github.com/baresip/re/pull/1154 * cmake: check accept4 only on linux by @sreimers in https://github.com/baresip/re/pull/1157 * cmake: fix iOS HAVE_ROUTE_LIST and darwin dns by @sreimers in https://github.com/baresip/re/pull/1158 * test: check if header and payload is set by @alfredh in https://github.com/baresip/re/pull/1161 **Full Changelog**: https://github.com/baresip/re/compare/v3.13.0...v3.14.0 ## [v3.13.0] - 2024-06-19 ### What's Changed * http/client: use dynamically sized buffers for PEM setters by @maximilianfridrich in https://github.com/baresip/re/pull/1117 * tls: allow secure TLS renegotiation by @maximilianfridrich in https://github.com/baresip/re/pull/1121 * tls: always enable USE_OPENSSL_SRTP by @alfredh in https://github.com/baresip/re/pull/1122 * main: remove call to openssl init by @alfredh in https://github.com/baresip/re/pull/1120 * sip/transp: Allow ACK w/o Max-Forwards header by @juha-h in https://github.com/baresip/re/pull/1124 * net: remove NET_ADDRSTRLEN by @alfredh in https://github.com/baresip/re/pull/1123 * ci/ios: increase min deployment target by @sreimers in https://github.com/baresip/re/pull/1126 * tls/http: add certificate chain setters by @maximilianfridrich in https://github.com/baresip/re/pull/1125 * sipsess/connect: set sess->established immediately on 200 receival by @maximilianfridrich in https://github.com/baresip/re/pull/1128 * test/cmake: add crypt32 linking for WIN32 by @sreimers in https://github.com/baresip/re/pull/1130 * ci/sanitizers: use clang-17 by @sreimers in https://github.com/baresip/re/pull/1131 * ci/sanitizer: add undefined behavior sanitizer by @sreimers in https://github.com/baresip/re/pull/1132 * sip: verify call-id, to-tag, cseq of INVITE response by @maximilianfridrich in https://github.com/baresip/re/pull/1129 * ci: remove one unneeded directory change by @alfredh in https://github.com/baresip/re/pull/1134 * test: change GENERATOR_SSRC from define to type by @alfredh in https://github.com/baresip/re/pull/1133 * tls: refactoring SNI ctx usage for libressl support by @sreimers in https://github.com/baresip/re/pull/1136 * test: add test_rtcp_loop() by @alfredh in https://github.com/baresip/re/pull/1137 * ci/coverage: increase min coverage by @sreimers in https://github.com/baresip/re/pull/1138 * ci/coverage: use json summary and upload html details by @sreimers in https://github.com/baresip/re/pull/1139 * sip: add host param to sip_send_conn by @sreimers in https://github.com/baresip/re/pull/1141 **Full Changelog**: https://github.com/baresip/re/compare/v3.12.0...v3.13.0 ## [v3.12.0] - 2024-05-15 ### What's Changed * cmake: fix static library build (vcpkg) by @alfredh in https://github.com/baresip/re/pull/1096 * h264: add STAP-A decode with long startcodes by @alfredh in https://github.com/baresip/re/pull/1101 * sess,request: deref request and ctrans immediately by @maximilianfridrich in https://github.com/baresip/re/pull/1099 * ua: enforce magic cookie in Via branch by @maximilianfridrich in https://github.com/baresip/re/pull/1102 * sip/auth: SHA-256 digest algorithm support by @sreimers in https://github.com/baresip/re/pull/1103 * ci/coverage: increase min. coverage by @sreimers in https://github.com/baresip/re/pull/1106 * rtp: fix correct logging text by @alfredh in https://github.com/baresip/re/pull/1109 * types: fix RE_ARG_SIZE gcc bit fields by @sreimers in https://github.com/baresip/re/pull/1110 * fmt: use re_fprintf instead of DEBUG_WARNING to avoid deadlock by @alfredh in https://github.com/baresip/re/pull/1112 * dbg: remove support for logfile by @alfredh in https://github.com/baresip/re/pull/1111 * test: add usage of rtcp_msg_print() by @alfredh in https://github.com/baresip/re/pull/1105 * http/client: add setter to disable tls server verification by @maximilianfridrich in https://github.com/baresip/re/pull/1114 * dbg: mutex should be unlocked while calling print handler by @alfredh in https://github.com/baresip/re/pull/1113 * Update README.md by @alfredh in https://github.com/baresip/re/pull/1115 * http/request: reset body mbuf pos on re-sending by @maximilianfridrich in https://github.com/baresip/re/pull/1116 * bump version by @alfredh in https://github.com/baresip/re/pull/1118 * cmake: bump soversion by @alfredh in https://github.com/baresip/re/pull/1119 **Full Changelog**: https://github.com/baresip/re/compare/v3.11.0...v3.12.0 ## [v3.11.0] - 2024-04-09 ### What's Changed * ci/clang-analyze: bump clang version and fix status-bugs by @sreimers in https://github.com/baresip/re/pull/1079 * main: Flush list of deleted fhs on `fd_poll` errors by @Lastique in https://github.com/baresip/re/pull/1081 * main: Use slist for fhs delete list. by @Lastique in https://github.com/baresip/re/pull/1082 * http/server: fix wrong sizeof in verify_msg by @akscf in https://github.com/baresip/re/pull/1083 * ci/sanitizers: add mmap rnd_bits workaround by @sreimers in https://github.com/baresip/re/pull/1086 * rtcp: add printing of TWCC packet by @alfredh in https://github.com/baresip/re/pull/1084 * include: add re_h264.h to re.h by @alfredh in https://github.com/baresip/re/pull/1087 * sdp: add sdp media lattr apply function the same way as for rattr by @cHuberCoffee in https://github.com/baresip/re/pull/1089 * av1: improve packetizer by @alfredh in https://github.com/baresip/re/pull/1088 * test: minor H.264 improvements by @alfredh in https://github.com/baresip/re/pull/1090 * tls: add session resumption setter by @maximilianfridrich in https://github.com/baresip/re/pull/1091 * thread/posix: optimize handler and fix gcc arm32 warning by @sreimers in https://github.com/baresip/re/pull/1093 * h264: fix for Annex-B bitstreams with 4-byte startcode by @alfredh in https://github.com/baresip/re/pull/1092 * ci/arch: add armv7 check by @sreimers in https://github.com/baresip/re/pull/1085 * main,httpauth: fix different from the declaration by @jobo-zt in https://github.com/baresip/re/pull/1095 * httpauth: fix doxygen comment by @alfredh in https://github.com/baresip/re/pull/1097 ### New Contributors * @akscf made their first contribution in https://github.com/baresip/re/pull/1083 **Full Changelog**: https://github.com/baresip/re/compare/v3.10.0...v3.11.0 ## [v3.10.0] - 2024-03-06 ## What's Changed * transp: deref qent only if qentp is not set by @maximilianfridrich in https://github.com/baresip/re/pull/1061 * sipsess: fix doxygen comments by @alfredh in https://github.com/baresip/re/pull/1062 * aufile: fix doxygen comment by @alfredh in https://github.com/baresip/re/pull/1063 * ci/codeql: bump action v3 by @sreimers in https://github.com/baresip/re/pull/1064 * misc: text2pcap helpers (RTP/RTCP capturing) by @sreimers in https://github.com/baresip/re/pull/1065 * ci/mingw: bump upload/download-artifact and cache versions by @sreimers in https://github.com/baresip/re/pull/1066 * transp,tls: add TLS client verification by @maximilianfridrich in https://github.com/baresip/re/pull/1059 * fmt/text2pcap: cleanup by @sreimers in https://github.com/baresip/re/pull/1067 * ci/android: cache openssl build by @sreimers in https://github.com/baresip/re/pull/1068 * ci/misc: fix double push/pull runs by @sreimers in https://github.com/baresip/re/pull/1069 * fmt/text2pcap: fix coverity return value warning by @sreimers in https://github.com/baresip/re/pull/1070 * sipsess/listen: improve glare handling by @maximilianfridrich in https://github.com/baresip/re/pull/1071 * conf: add conf_get_i32 by @sreimers in https://github.com/baresip/re/pull/1072 **Full Changelog**: https://github.com/baresip/re/compare/v3.9.0...v3.10.0 ## [v3.9.0] - 2024-01-31 ## What's Changed * http: fix doxygen by @cspiel1 in https://github.com/baresip/re/pull/1033 * types: remove old ARRAY_SIZE macro by @alfredh in https://github.com/baresip/re/pull/1034 * cmake: bump minimum to version 3.14 by @alfredh in https://github.com/baresip/re/pull/1030 * test: use re_is_aligned() by @alfredh in https://github.com/baresip/re/pull/1035 * sipsess: refactor and simplify SDP negotiation state by @maximilianfridrich in https://github.com/baresip/re/pull/1016 * bump year by @sreimers in https://github.com/baresip/re/pull/1038 * cmake,pc: fix static library build by @alfredh in https://github.com/baresip/re/pull/1036 * rx thread activate by @cspiel1 in https://github.com/baresip/re/pull/1037 * test: fix cppcheck warnings by @alfredh in https://github.com/baresip/re/pull/1040 * test: move test_rtcp_decode_badmsg() to separate testcase by @alfredh in https://github.com/baresip/re/pull/1041 * rtp: lock more fields from rtcp_sess by @cspiel1 in https://github.com/baresip/re/pull/1039 * rtp: lock rtcp_set_srate() by @cspiel1 in https://github.com/baresip/re/pull/1043 * test: HAVE_INET6 is always defined by @alfredh in https://github.com/baresip/re/pull/1046 * ci: add run-on-arch for ARM64 linux by @alfredh in https://github.com/baresip/re/pull/1045 * httpauth: digest verification rfc 7616 by @cHuberCoffee in https://github.com/baresip/re/pull/1044 * tmr: prevent race condition on cancel by @sreimers in https://github.com/baresip/re/pull/1048 * aubuf: fix coverity defect by @alfredh in https://github.com/baresip/re/pull/1051 * btrace: fix coverity warning by @alfredh in https://github.com/baresip/re/pull/1049 * ci/win: downgrade openssl by @sreimers in https://github.com/baresip/re/pull/1054 * docs: update README by @alfredh in https://github.com/baresip/re/pull/1053 * http: client - set scopeid fixes HTTP requests for IPv6ll by @cspiel1 in https://github.com/baresip/re/pull/1055 * rtp: add rtp_source_ prefix to RTP source api by @alfredh in https://github.com/baresip/re/pull/1052 * rtp: make struct rtp_source public by @alfredh in https://github.com/baresip/re/pull/1057 * rtp: sess - fix coverity warning by @cspiel1 in https://github.com/baresip/re/pull/1058 * mk: bump version to 3.9.0 by @alfredh in https://github.com/baresip/re/pull/1060 **Full Changelog**: https://github.com/baresip/re/compare/v3.8.0...v3.9.0 ## [v3.8.0] - 2023-12-27 ## What's Changed * Update README.md by @alfredh in https://github.com/baresip/re/pull/1013 * rem/aufile: aufile_get_length use aufmt_sample_size by @larsimmisch in https://github.com/baresip/re/pull/1011 * rem/aufile: test and fix aufile_set_position nread by @larsimmisch in https://github.com/baresip/re/pull/1010 * ci/ssl: bump assets release by @sreimers in https://github.com/baresip/re/pull/1014 * readme: update supported openssl versions by @sreimers in https://github.com/baresip/re/pull/1015 * ci: upgrade android to openssl 3.2.0 by @alfredh in https://github.com/baresip/re/pull/1017 * sipsess/connect: don't create a dialog for 100 responses by @maximilianfridrich in https://github.com/baresip/re/pull/1018 * aubuf: fix build with re_trace_event by @cspiel1 in https://github.com/baresip/re/pull/1019 * trace: fix coverity warnings by @alfredh in https://github.com/baresip/re/pull/1024 * aumix: fix coverity defect in destructor by @alfredh in https://github.com/baresip/re/pull/1025 * main: fix doxygen comment by @alfredh in https://github.com/baresip/re/pull/1026 * connect: do not enforce Contact header in 1XX responses with To tag by @maximilianfridrich in https://github.com/baresip/re/pull/1028 * test/sipsess: test re-INVITE with wait for ACK by @cspiel1 in https://github.com/baresip/re/pull/1027 * dialog: fix rtags of forking INVITE by @maximilianfridrich in https://github.com/baresip/re/pull/1023 * cmake: add RE_LIBS config and add atomic check by @sreimers in https://github.com/baresip/re/pull/1029 * ci: use actions/checkout@v4 by @robert-scheck in https://github.com/baresip/re/pull/1031 **Full Changelog**: https://github.com/baresip/re/compare/v3.7.0...v3.8.0 ## [v3.7.0] - 2023-11-06 ## What's Changed * trace: add id handling by @sreimers in https://github.com/baresip/re/pull/981 * fmt/pl: add pl_alloc_str by @sreimers in https://github.com/baresip/re/pull/983 * ci/freebsd: limit runtime to 20 mins by @sreimers in https://github.com/baresip/re/pull/985 * Httpauth digest response by @cHuberCoffee in https://github.com/baresip/re/pull/944 * dialog: REVERT fix rtags of forking INVITE with 100rel (#947) by @juha-h in https://github.com/baresip/re/pull/986 * ice: AI_V4MAPPED doesn't exist on OpenBSD by @landryb in https://github.com/baresip/re/pull/989 * test: call - add call on-hold/resume test by @cspiel1 in https://github.com/baresip/re/pull/990 * async: fix re_async_cancel mqueue handling by @sreimers in https://github.com/baresip/re/pull/995 * async: clear callback function pointer after use (#992) by @cspiel1 in https://github.com/baresip/re/pull/993 * Update README.md: Fix link in section Examples. by @Wolf-SO in https://github.com/baresip/re/pull/991 * ci/abi: bump version by @sreimers in https://github.com/baresip/re/pull/1000 * rtp: make flag rtcp_mux atomic by @cspiel1 in https://github.com/baresip/re/pull/997 * cmake,udp: improve QOS_FLOWID and PQOS_FLOWID detection by @sreimers in https://github.com/baresip/re/pull/1002 * types: extend RE_ARG to 32 by @sreimers in https://github.com/baresip/re/pull/1003 * sip/transp: add win32 local transport addr fallback by @sreimers in https://github.com/baresip/re/pull/1001 * cmake/config: set HAVE_THREADS only if threads.h by @sreimers in https://github.com/baresip/re/pull/1005 * ci/freebsd: update vmactions/freebsd-vm@v1 by @sreimers in https://github.com/baresip/re/pull/1006 * Coverity httpauth fixes by @sreimers in https://github.com/baresip/re/pull/1007 * rem/aufile: fix aufile_get_length calculations by @larsimmisch in https://github.com/baresip/re/pull/1008 ## New Contributors * @Wolf-SO made their first contribution in https://github.com/baresip/re/pull/991 **Full Changelog**: https://github.com/baresip/re/compare/v3.6.0...v3.7.0 ## [v3.6.2] - 2023-11-06 ## What's Changed sip/transp: add win32 local transport addr fallback (fixes TCP/TLS register) ## [v3.6.1] - 2023-11-03 ## What's Changed ice: AI_V4MAPPED doesn't exist on OpenBSD #989 dialog: REVERT fix rtags of forking INVITE with 100rel (#947) #986 debian: fix version number ## [v3.6.0] - 2023-10-17 ## What's Changed * ci/coverage: increase min. coverage by @sreimers in https://github.com/baresip/re/pull/958 * Implement aufile_set_position by @larsimmisch in https://github.com/baresip/re/pull/943 * dialog: fix rtags of forking INVITE with 100rel by @maximilianfridrich in https://github.com/baresip/re/pull/947 * tls/alloc: set default min proto TLS 1.2 by @sreimers in https://github.com/baresip/re/pull/948 * test: init err to 0 in sdp test (cppcheck) by @alfredh in https://github.com/baresip/re/pull/959 * main: fd_listen fhs alloc rewrite by @sreimers in https://github.com/baresip/re/pull/805 * Expand RE_BREAKPOINT macro on ARM64 by @larsimmisch in https://github.com/baresip/re/pull/961 * jbuf: trace data for plot by @cspiel1 in https://github.com/baresip/re/pull/964 * trace: use global trace log by @sreimers in https://github.com/baresip/re/pull/965 * main: use ifdef for RE_TRACE_ENABLED by @sreimers in https://github.com/baresip/re/pull/966 * test/hexdump: hide output by @sreimers in https://github.com/baresip/re/pull/968 * trace: remove global default trace json by @sreimers in https://github.com/baresip/re/pull/969 * ci/ssl: use tools repo and new assets by @sreimers in https://github.com/baresip/re/pull/972 * fmt: doxygen correction in print.c by @cspiel1 in https://github.com/baresip/re/pull/973 * trace: use only explicit RE_TRACE_ENABLED by cmake by @sreimers in https://github.com/baresip/re/pull/974 * cmake: enable C11 for Windows (not MINGW) by @alfredh in https://github.com/baresip/re/pull/970 * ci/coverage: lower min. coverage by @sreimers in https://github.com/baresip/re/pull/975 * jbuf: move jbuf to baresip by @cspiel1 in https://github.com/baresip/re/pull/971 * ci/coverage: improve coverage (enable trace) by @sreimers in https://github.com/baresip/re/pull/976 * ci: bump pr-dependency-action@v0.6 by @sreimers in https://github.com/baresip/re/pull/977 * ice: mDNS refactoring by @sreimers in https://github.com/baresip/re/pull/934 * trace: add flush worker and optimize memory usage by @sreimers in https://github.com/baresip/re/pull/967 * rtp: fix video jitter calculation and add arrival time rtp header by @sreimers in https://github.com/baresip/re/pull/978 * ci: remove DARWIN compile flag from iOS build by @alfredh in https://github.com/baresip/re/pull/979 * thread: add trace thread name logging by @sreimers in https://github.com/baresip/re/pull/980 * ci/coverage: reduce min. coverage by @sreimers in https://github.com/baresip/re/pull/982 **Full Changelog**: https://github.com/baresip/re/compare/v3.5.1...v3.6.0 ## [v3.5.1] - 2023-09-12 ## What's Changed * cmake: fix RELEASE definition for older cmake releases by @sreimers in https://github.com/baresip/re/pull/953 * ci/build: add release build check by @sreimers in https://github.com/baresip/re/pull/954 * cmake: fix definitions for older cmake by @sreimers in https://github.com/baresip/re/pull/955 **Full Changelog**: https://github.com/baresip/re/compare/v3.5.0...v3.5.1 ## [v3.5.0] - 2023-09-12 ## What's Changed * ci/sonar: update scanner and java version by @sreimers in https://github.com/baresip/re/pull/895 * ci/sonar: fix java distribution by @sreimers in https://github.com/baresip/re/pull/897 * udp: add doxygen comments by @alfredh in https://github.com/baresip/re/pull/896 * tls: fix some doxygen warnings by @alfredh in https://github.com/baresip/re/pull/894 * mk: add release target by @sreimers in https://github.com/baresip/re/pull/901 * types: add re_assert and re_assert_se definition by @sreimers in https://github.com/baresip/re/pull/900 * btrace improvements by @sreimers in https://github.com/baresip/re/pull/902 * Safe RE_VA_ARG helpers by @sreimers in https://github.com/baresip/re/pull/758 * mbuf: add safe mbuf_printf by @sreimers in https://github.com/baresip/re/pull/899 * auth: cast time_t timestamp by @sreimers in https://github.com/baresip/re/pull/903 * mbuf: add mbuf_write_ptr and mbuf_read_ptr by @sreimers in https://github.com/baresip/re/pull/898 * ci/mingw: remove cmake workaround by @sreimers in https://github.com/baresip/re/pull/906 * tls: assume OpenSSL version 1.1.1 or later by @alfredh in https://github.com/baresip/re/pull/907 * cmake: cleanup, remove unused define USE_OPENSSL_DTLS by @alfredh in https://github.com/baresip/re/pull/908 * test/turn: use mutex instead atomic by @sreimers in https://github.com/baresip/re/pull/909 * stun: remove unused struct members by @alfredh in https://github.com/baresip/re/pull/910 * stun: complete doxygen for struct by @alfredh in https://github.com/baresip/re/pull/912 * tcp,udp: full IPv6 dual-stack socket support by @sreimers in https://github.com/baresip/re/pull/911 * aufile: add methods to get size in bytes/length in ms by @larsimmisch in https://github.com/baresip/re/pull/913 * async: signal ESHUTDOWN to all open worker callbacks by @sreimers in https://github.com/baresip/re/pull/915 * dns/client: fix async getaddr query abort (not thread safe) by @sreimers in https://github.com/baresip/re/pull/914 * async,dns/client: replace ESHUTDOWN with ECANCELED and optimize err handling by @sreimers in https://github.com/baresip/re/pull/918 * sip: remove unused local variable by @cspiel1 in https://github.com/baresip/re/pull/920 * dns/client: optimize udp timeout by @sreimers in https://github.com/baresip/re/pull/916 * ice: add candidate sdp mdns support by @sreimers in https://github.com/baresip/re/pull/917 * ice/icesdp: fix freeaddrinfo by @sreimers in https://github.com/baresip/re/pull/923 * retest: fix format string in test_listcases for size_t argument by @cHuberCoffee in https://github.com/baresip/re/pull/922 * httpauth: http digest challenge request using RFC 7616 by @cHuberCoffee in https://github.com/baresip/re/pull/919 * types: fix RE_ARG_SIZE default argument promotions by @sreimers in https://github.com/baresip/re/pull/924 * sip: fix TCP source port by @cspiel1 in https://github.com/baresip/re/pull/921 * fmt/print: add 64-bit length modifier %Li, %Ld and %Lu by @sreimers in https://github.com/baresip/re/pull/905 * fmt/print: improve print RE_VA_ARG debugging by @sreimers in https://github.com/baresip/re/pull/925 * sip/request: fix check return code (found by coverity) by @sreimers in https://github.com/baresip/re/pull/926 * httpauth/digest: use %L instead of PRI*64 macros by @sreimers in https://github.com/baresip/re/pull/927 * types: add RE_ARG_SIZE struct pl (avoids wrong print fmt %r usage) by @sreimers in https://github.com/baresip/re/pull/928 * dns/client: fix getaddrinfo err handling and mem_ref dnsc by @sreimers in https://github.com/baresip/re/pull/929 * rtp/rtp_debug: fix printf size format by @sreimers in https://github.com/baresip/re/pull/933 * main: optimize re_lock and re_unlock by @alfredh in https://github.com/baresip/re/pull/935 * hexdump: fix format and add test by @alfredh in https://github.com/baresip/re/pull/936 * test: fix bug in performance test format by @alfredh in https://github.com/baresip/re/pull/937 * types: remove some duplicated error codes by @alfredh in https://github.com/baresip/re/pull/939 * test: minor improvements in remain test by @alfredh in https://github.com/baresip/re/pull/931 * dbg: remove unused functions by @sreimers in https://github.com/baresip/re/pull/941 * cmake/re-config: add default CMAKE_BUILD_TYPE and fix RELEASE definition by @sreimers in https://github.com/baresip/re/pull/945 ## New Contributors * @larsimmisch made their first contribution in https://github.com/baresip/re/pull/913 **Full Changelog**: https://github.com/baresip/re/compare/v3.4.0...v3.5.0 ## [v3.4.0] - 2023-08-09 ## What's Changed * rtpext: uniform parameter name fixes doxygen warning by @cspiel1 in https://github.com/baresip/re/pull/868 * mk: add rem to doxygen inputs by @cspiel1 in https://github.com/baresip/re/pull/869 * vidmix: allow different pixel format by @sreimers in https://github.com/baresip/re/pull/864 * ajb doxygen by @cspiel1 in https://github.com/baresip/re/pull/870 * aes: correct parameters for stub by @cspiel1 in https://github.com/baresip/re/pull/872 * ci/build: fail on cmake and compile warnings by @sreimers in https://github.com/baresip/re/pull/873 * fmt: fix format string in fmt_timestamp() by @alfredh in https://github.com/baresip/re/pull/874 * hmac,md5,sha: add mbedtls backend by @cspiel1 in https://github.com/baresip/re/pull/871 * test: no need to rewind freshly allocated mbuf by @alfredh in https://github.com/baresip/re/pull/876 * httpauth: basic challenge creation and verification functions by @cHuberCoffee in https://github.com/baresip/re/pull/875 * Fix include of re_thread.h in re_tmr.h by @nielsavonds in https://github.com/baresip/re/pull/879 * btrace: fix WIN32_LEAN_AND_MEAN macro redefine by @jobo-zt in https://github.com/baresip/re/pull/880 * aumix: add record sum handler by @sreimers in https://github.com/baresip/re/pull/877 * ci/win: disable x86 testing by @sreimers in https://github.com/baresip/re/pull/883 * sipsess: allow UPDATE and INFO in early dialog by @maximilianfridrich in https://github.com/baresip/re/pull/878 * prefix macro VERSION by @cspiel1 in https://github.com/baresip/re/pull/882 * main: use HAVE_SIGNAL in init.c by @cspiel1 in https://github.com/baresip/re/pull/881 * test: change to ASSERT_XXX macros, remove EXPECT_XXX macros by @alfredh in https://github.com/baresip/re/pull/885 * fmt: handy functions for pointer-length objects by @cHuberCoffee in https://github.com/baresip/re/pull/884 * test: add TWCC test from Chrome 114 packet by @alfredh in https://github.com/baresip/re/pull/886 * sipsess/listen: Fix target_refresh_handler by @maximilianfridrich in https://github.com/baresip/re/pull/888 * ci/mingw: downgrade cmake by @sreimers in https://github.com/baresip/re/pull/890 * cmake: fix target include path for subdir projects by @sreimers in https://github.com/baresip/re/pull/891 ## New Contributors * @nielsavonds made their first contribution in https://github.com/baresip/re/pull/879 * @jobo-zt made their first contribution in https://github.com/baresip/re/pull/880 **Full Changelog**: https://github.com/baresip/re/compare/v3.3.0...v3.4.0 ## [v3.3.0] - 2023-07-05 ## What's Changed * jbuf: use float ratio by @sreimers in https://github.com/baresip/re/pull/817 * src: fix some typos by @alfredh in https://github.com/baresip/re/pull/828 * jbuf: constant jbuf_put() by @cspiel1 in https://github.com/baresip/re/pull/821 * ci/coverity: split up prepare and make steps by @sreimers in https://github.com/baresip/re/pull/832 * vidmix: coverity fix by @alfredh in https://github.com/baresip/re/pull/830 * sys/fs: fix fs_stdio_hide resource leak by @sreimers in https://github.com/baresip/re/pull/833 * http: coverity fix by @alfredh in https://github.com/baresip/re/pull/834 * avc: fix coverity by @alfredh in https://github.com/baresip/re/pull/835 * sys: fix return value by @alfredh in https://github.com/baresip/re/pull/836 * Do not automatically make new call when 3xx response is received by @juha-h in https://github.com/baresip/re/pull/829 * sipreg: supports PNS custom contact URI by @codyit in https://github.com/baresip/re/pull/837 * ci: add iOS platform by @alfredh in https://github.com/baresip/re/pull/838 * ci/mingw: use cv2pdb for debug info conversion by @sreimers in https://github.com/baresip/re/pull/839 * main: fix warning on Windows by @alfredh in https://github.com/baresip/re/pull/842 * http: add compile-time check for USE_TLS by @alfredh in https://github.com/baresip/re/pull/841 * test: check if USE_TLS is defined by @alfredh in https://github.com/baresip/re/pull/843 * thread: fix WIN32 mingw warning by @sreimers in https://github.com/baresip/re/pull/844 * src: fix doxygen warnings by @alfredh in https://github.com/baresip/re/pull/847 * rtpext: add doxygen comments by @alfredh in https://github.com/baresip/re/pull/846 * jbuf: enable old frame drop warnings by @sreimers in https://github.com/baresip/re/pull/848 * md5: add support for native Windows Wincrypt API by @alfredh in https://github.com/baresip/re/pull/850 * rtpext: add support for Two-Byte headers by @alfredh in https://github.com/baresip/re/pull/849 * sha: add support for Windows native API by @alfredh in https://github.com/baresip/re/pull/851 * cmake: change '-lm' to 'm' in LINKLIBS by @alfredh in https://github.com/baresip/re/pull/854 * hmac: add support for native Windows API by @alfredh in https://github.com/baresip/re/pull/853 * CI: Add support for building Windows ARM by @alfredh in https://github.com/baresip/re/pull/855 * async: add work mutex handling by @sreimers in https://github.com/baresip/re/pull/857 * SIP/TCP use source port for Via header by @cspiel1 in https://github.com/baresip/re/pull/824 * sip: use TCP source port for Contact header by @cspiel1 in https://github.com/baresip/re/pull/858 * cmake: remove obsolete $Rip (not available on all platforms) by @sreimers in https://github.com/baresip/re/pull/790 * sipsess: fix RSeq header and rel\_seq numbering by @maximilianfridrich in https://github.com/baresip/re/pull/796 * websock: add proto support by @sreimers in https://github.com/baresip/re/pull/798 * sip/transp: fix websock\_accept proto by @sreimers in https://github.com/baresip/re/pull/800 * sip/transp: remove unneeded websocket tcp tmr by @sreimers in https://github.com/baresip/re/pull/801 * aubuf: activate overrun/underrun statistics by @cspiel1 in https://github.com/baresip/re/pull/803 * cmake: add libre namespace export by @sreimers in https://github.com/baresip/re/pull/786 * revert uri escaping commits by @maximilianfridrich in https://github.com/baresip/re/pull/802 * jbuf: refactor frame calculation by @sreimers in https://github.com/baresip/re/pull/788 * jbuf: frame fixes by @sreimers in https://github.com/baresip/re/pull/806 * sip: add missing WS and WSS transport decoder for VIA headers by @pitti98 in https://github.com/baresip/re/pull/809 * ice: Fix conncheck callback called multiple times by @pitti98 in https://github.com/baresip/re/pull/807 * jbuf: JBUF\_FIXED should also keep min wish size by @sreimers in https://github.com/baresip/re/pull/813 * ci: compile choco openssl with --x86 for 32-bits by @alfredh in https://github.com/baresip/re/pull/814 * jbuf: fix possible division by zero by @sreimers in https://github.com/baresip/re/pull/815 * fix some cppcheck warnings by @alfredh in https://github.com/baresip/re/pull/816 * test/ice: fix cppcheck by @sreimers in https://github.com/baresip/re/pull/818 * tls/openssl: fix cppcheck warnings by @sreimers in https://github.com/baresip/re/pull/820 * base64: fix cppcheck warnings by @sreimers in https://github.com/baresip/re/pull/819 * main/init: fix upper signal handling by @sreimers in https://github.com/baresip/re/pull/822 * include: fix some typos by @alfredh in https://github.com/baresip/re/pull/825 ## New Contributors * @landryb made their first contribution in https://github.com/baresip/re/pull/773 * @pitti98 made their first contribution in https://github.com/baresip/re/pull/809 **Full Changelog**: https://github.com/baresip/re/compare/v3.1.0...v3.2.0 ## [v3.1.0] - 2023-04-27 ## What's Changed * ci: bump mingw openssl to 3.1.0 by @alfredh in https://github.com/baresip/re/pull/738 * thread: add cnd_timedwait() by @sreimers in https://github.com/baresip/re/pull/736 * Add tls and http apis for post handshake by @fAuernigg in https://github.com/baresip/re/pull/713 * ci/sanitizers: add multi thread testing by @sreimers in https://github.com/baresip/re/pull/741 * ci/win: use separate retest step by @sreimers in https://github.com/baresip/re/pull/742 * thread: fix pthread_setname_np thread pointer deref by @sreimers in https://github.com/baresip/re/pull/744 * ci: add FreeBSD test by @sreimers in https://github.com/baresip/re/pull/745 * cmake: bump minimum version of OpenSSL to 1.1.1 by @alfredh in https://github.com/baresip/re/pull/746 * ci: avoid hardcoded OpenSSL path on macOS by @robert-scheck in https://github.com/baresip/re/pull/747 * sip,uri,test: Escape SIP URIs by @maximilianfridrich in https://github.com/baresip/re/pull/740 * udp: add a lock for the helpers list by @cspiel1 in https://github.com/baresip/re/pull/732 * rem/vidmix: add position index handling by @sreimers in https://github.com/baresip/re/pull/749 * aubuf: set auframe fields correct in read_auframe loop by @cspiel1 in https://github.com/baresip/re/pull/750 * list: refactor/optimize list_insert_sorted by @sreimers in https://github.com/baresip/re/pull/748 * ci/freebsd: remove openssl-devel by @sreimers in https://github.com/baresip/re/pull/755 * tmr: add tmr_continue() by @vanrein in https://github.com/baresip/re/pull/754 * ci,cmake: replace C99 check by strict C99 and C11 checks by @sreimers in https://github.com/baresip/re/pull/759 * atomic: Fix missing memory order arguments in MSVC atomic functions by @Lastique in https://github.com/baresip/re/pull/766 * thread: remove win32 SetThreadDescription by @sreimers in https://github.com/baresip/re/pull/768 **Full Changelog**: https://github.com/baresip/re/compare/v3.0.0...v3.1.0 --- ## [v3.0.0] - 2023-03-20 ## What's Changed * main: allow poll_method change only before setup by @sreimers in https://github.com/baresip/re/pull/681 * main: add more details to re_debug() by @alfredh in https://github.com/baresip/re/pull/689 * merge rem into re by @alfredh in https://github.com/baresip/re/pull/683 * tmr,main: thread safe tmr handling by @sreimers in https://github.com/baresip/re/pull/690 * tmr,main: add tmrl_count by @sreimers in https://github.com/baresip/re/pull/694 * main: add re_thread_async_main_id and re_thread_async_main_cancel by @sreimers in https://github.com/baresip/re/pull/697 * merge retest into re by @alfredh in https://github.com/baresip/re/pull/695 * tls: add doxygen comment to dtls_recv_packet() by @alfredh in https://github.com/baresip/re/pull/699 * test: use TEST_ERR by @sreimers in https://github.com/baresip/re/pull/700 * test: use TEST_ERR by @sreimers in https://github.com/baresip/re/pull/701 * hmac: remove unused SHA_BLOCKSIZE by @alfredh in https://github.com/baresip/re/pull/703 * async: fix cancel memory leaks by @sreimers in https://github.com/baresip/re/pull/705 * ci: windows Debug/Release by @alfredh in https://github.com/baresip/re/pull/704 * cmake: add extra source files for aes and hmac by @alfredh in https://github.com/baresip/re/pull/708 * test: remove libpthread from LINKLIBS by @alfredh in https://github.com/baresip/re/pull/710 * hmac: add stateless HMAC-SHA256 wrapper by @alfredh in https://github.com/baresip/re/pull/706 * thread: add thread name handling by @sreimers in https://github.com/baresip/re/pull/709 * ci: add support for Android by @alfredh in https://github.com/baresip/re/pull/707 * test: fix convert C99 by @sreimers in https://github.com/baresip/re/pull/717 * dbg: remove pre-C99 fallbacks by @sreimers in https://github.com/baresip/re/pull/718 * test: remove CMAKE_C_STANDARD by @alfredh in https://github.com/baresip/re/pull/714 * sdp/media: fix ccheck list_unlink warning by @sreimers in https://github.com/baresip/re/pull/715 * jbuf: allocate mutex and lock also jbuf_debug() by @cspiel1 in https://github.com/baresip/re/pull/693 * sys/fs: fix fs_fopen read only mode (should not create file) by @sreimers in https://github.com/baresip/re/pull/719 * ci/ssl: update OpenSSL/LibreSSL by @sreimers in https://github.com/baresip/re/pull/720 * http: fix read_file on win32 (wrong filesize) and use mbuf by @fAuernigg in https://github.com/baresip/re/pull/711 * sys: add sys_getenv() by @sreimers in https://github.com/baresip/re/pull/721 * rtp: Don't check RTCP socket if rtcp-mux is enabled by @Lastique in https://github.com/baresip/re/pull/723 * tls: remove return statement that is not needed by @alfredh in https://github.com/baresip/re/pull/724 * sha: add sha256_printf() by @alfredh in https://github.com/baresip/re/pull/725 * cmake: add rem headers to install by @sreimers in https://github.com/baresip/re/pull/727 * cmake: merge REM_HEADERS by @sreimers in https://github.com/baresip/re/pull/728 * tls: set mbuf pos and end at the same time by @alfredh in https://github.com/baresip/re/pull/729 * misc: add Makefile helpers and exclude retest from all target by @sreimers in https://github.com/baresip/re/pull/726 * sa: add sa_struct_get_size() to check size by @alfredh in https://github.com/baresip/re/pull/730 * rtcp: make rtcp_calc_rtt() public by @alfredh in https://github.com/baresip/re/pull/731 * test: add HAVE_UNIXSOCK=0 support by @sreimers in https://github.com/baresip/re/pull/734 * aubuf: set sample format when frame is read by @cspiel1 in https://github.com/baresip/re/pull/737 **Full Changelog**: https://github.com/baresip/re/compare/v2.12.0...v3.0.0 --- ## [v2.12.0] - 2023-02-15 ## What's Changed * tls: remove ifdef DTLS_CTRL_HANDLE_TIMEOUT by @alfredh in https://github.com/baresip/re/pull/634 * cmake: increment required version by @cspiel1 in https://github.com/baresip/re/pull/642 * dtls: add logging of DTLS packet content-type by @alfredh in https://github.com/baresip/re/pull/641 * dtls: add single connection mode by @alfredh in https://github.com/baresip/re/pull/643 * ice: reduce conncheck start timer by @alfredh in https://github.com/baresip/re/pull/640 * async,main: make re_thread_async itself thread safe by @sreimers in https://github.com/baresip/re/pull/644 * av1: remove old packetizer by @alfredh in https://github.com/baresip/re/pull/645 * av1: fix chrome interop by @alfredh in https://github.com/baresip/re/pull/646 * av1: minor cleanups by @alfredh in https://github.com/baresip/re/pull/649 * trace: fix new json start by @sreimers in https://github.com/baresip/re/pull/648 * make rtcp interval configureable by @sreimers in https://github.com/baresip/re/pull/650 * sa: proposal to always enable struct sockaddr_in6 by @alfredh in https://github.com/baresip/re/pull/651 * ci: rename ccheck to lint by @alfredh in https://github.com/baresip/re/pull/653 * ci: extend coverage test with retest+select by @alfredh in https://github.com/baresip/re/pull/652 * main: remove poll support by @sreimers in https://github.com/baresip/re/pull/654 * ci: use Ninja as CMake generator by @alfredh in https://github.com/baresip/re/pull/656 * ci/abi: fix abidiff paths by @sreimers in https://github.com/baresip/re/pull/657 * PRACK refactoring by @maximilianfridrich in https://github.com/baresip/re/pull/630 * types: add RE_ prefix to ARRAY_SIZE() by @alfredh in https://github.com/baresip/re/pull/658 * cmake: add USE_TRACE option (default OFF) by @sreimers in https://github.com/baresip/re/pull/660 * add re prefix by @alfredh in https://github.com/baresip/re/pull/659 * tcp: add RE_TCP_BACKLOG by @sreimers in https://github.com/baresip/re/pull/661 * Fix doxygen warnings by @alfredh in https://github.com/baresip/re/pull/662 * mbuf: docs and setters/getters by @alfredh in https://github.com/baresip/re/pull/663 * tcp,cmake: use accept4 if supported by @sreimers in https://github.com/baresip/re/pull/665 * tcp: remove SO_LINGER socket option by @sreimers in https://github.com/baresip/re/pull/664 * rtcp: update documentation by @alfredh in https://github.com/baresip/re/pull/666 * tcp: check SO_ERROR only for active connections by @sreimers in https://github.com/baresip/re/pull/667 * cmake: add HAVE_RESOLV by @sreimers in https://github.com/baresip/re/pull/668 * hash: add hash_debug by @sreimers in https://github.com/baresip/re/pull/670 * list: improve list_apply performance by @sreimers in https://github.com/baresip/re/pull/669 * rtp: add doxygen comments by @alfredh in https://github.com/baresip/re/pull/671 * rtp: extra dox for rtcp_encode by @alfredh in https://github.com/baresip/re/pull/672 * ci: add thread and address sanitizer by @sreimers in https://github.com/baresip/re/pull/673 * Do not change glibc feature selection macros in unsupported ways by @fweimer-rh in https://github.com/baresip/re/pull/674 * auth: replace ETIME with ETIMEDOUT by @sreimers in https://github.com/baresip/re/pull/675 * cmake: add min. OpenSSL 1.1.0 version requirement by @sreimers in https://github.com/baresip/re/pull/680 * ci: fix flaky azure mirrors by @sreimers in https://github.com/baresip/re/pull/682 * tls: remove obsolete openssl version check and fix libressl build by @cspiel1 in https://github.com/baresip/re/pull/679 * ci/ssl: fix openssl root dir by @sreimers in https://github.com/baresip/re/pull/677 * main: add re_thread_async_main for re_global only by @sreimers in https://github.com/baresip/re/pull/685 * atomic: fix win32 atomic load const warnings by @sreimers in https://github.com/baresip/re/pull/688 * atomic: fix __iso_volatile_load64 deref by @sreimers in https://github.com/baresip/re/pull/691 * bump version numbers to 2.12.0 by @alfredh in https://github.com/baresip/re/pull/692 ## New Contributors * @fweimer-rh made their first contribution in https://github.com/baresip/re/pull/674 **Full Changelog**: https://github.com/baresip/re/compare/v2.11.0...v2.12.0 --- ## [v2.11.0] - 2023-01-11 ## What's Changed * net/types: move socket helpers and rename RE_ERRNO_SOCK and RE_BAD_SOCK by @sreimers in https://github.com/baresip/re/pull/608 * sys: fix fileno warning by @alfredh in https://github.com/baresip/re/pull/612 * tls: clear session callbacks in destructor by @cspiel1 in https://github.com/baresip/re/pull/611 * tls: use long SSL state strings for logging by @cspiel1 in https://github.com/baresip/re/pull/613 * tls: Set session only once before Client Hello by @cspiel1 in https://github.com/baresip/re/pull/607 * udp: add optional send/recv handler by @alfredh in https://github.com/baresip/re/pull/602 * tls: remove deprecated tls_set_selfsigned() by @alfredh in https://github.com/baresip/re/pull/614 * main: allow for init twice by @alfredh in https://github.com/baresip/re/pull/615 * cmake: add check_c_compiler_flag for atomic-implicit-seq-cst warning by @sreimers in https://github.com/baresip/re/pull/617 * http,tcp: add http_listen_fd and tcp_sock_alloc_fd by @sreimers in https://github.com/baresip/re/pull/618 * tcp_sock_alloc_fd: fix fdc initializing by @sreimers in https://github.com/baresip/re/pull/619 * sa,unixsock: add unix domain socket support by @sreimers in https://github.com/baresip/re/pull/600 * mk: remove makefiles by @sreimers in https://github.com/baresip/re/pull/620 * RTP Resend by @sreimers in https://github.com/baresip/re/pull/626 * TLS server support SNI based certificate selection by @cspiel1 in https://github.com/baresip/re/pull/596 * sipsess/request.c: return error code in sipsess_request_alloc by @maximilianfridrich in https://github.com/baresip/re/pull/631 * ice: add ANSI output with Green and Red colors by @alfredh in https://github.com/baresip/re/pull/632 * docs: update reference to TLS 1.2 by @alfredh in https://github.com/baresip/re/pull/633 * cmake, sa: enable unix sockets, if HAVE_UNIXSOCK is undefined by @fAuernigg in https://github.com/baresip/re/pull/636 * trice: refresh doxygen comments by @alfredh in https://github.com/baresip/re/pull/635 * tls: add error handling for BIO_reset by @cspiel1 in https://github.com/baresip/re/pull/638 * dns/client: fix rrlv reference cache handling by @sreimers in https://github.com/baresip/re/pull/637 **Full Changelog**: https://github.com/baresip/re/compare/v2.10.0...v2.11.0 --- ## [v2.10.0] - 2022-12-06 ## What's Changed * h264: add STAP-A by @alfredh in https://github.com/baresip/re/pull/584 * tls: SSL_get_peer_certificate is deprecated by @sreimers in https://github.com/baresip/re/pull/585 * sipreg fix contact handler `expires` evaluation by @cspiel1 in https://github.com/baresip/re/pull/581 * ice: local candidate policy config by @sreimers in https://github.com/baresip/re/pull/589 * h265: add missing NAL types by @alfredh in https://github.com/baresip/re/pull/590 * rtpext: move from baresip to re by @alfredh in https://github.com/baresip/re/pull/591 * mk: add rtpext to Makefile build by @cspiel1 in https://github.com/baresip/re/pull/594 * mk: add makefile deprecation warning by @sreimers in https://github.com/baresip/re/pull/595 * fs: use dup/dup2 for stdio hide and restore by @sreimers in https://github.com/baresip/re/pull/597 * dns: fix dnsc_conf_set memory leak by @alfredh in https://github.com/baresip/re/pull/598 * cmake: add TRACE_SSL compile definition by @cspiel1 in https://github.com/baresip/re/pull/599 * cmake: add ZLIB_INCLUDE_DIRS by @sreimers in https://github.com/baresip/re/pull/601 * cmake/pkgconfig: fix prefix variable by @cspiel1 in https://github.com/baresip/re/pull/603 * ci/valgrind: use ubuntu-20.04 by @sreimers in https://github.com/baresip/re/pull/606 **Full Changelog**: https://github.com/baresip/re/compare/v2.9.0...v2.10.0 --- ## [v2.9.0] - 2022-11-01 ## What's Changed * cmake,make: bump version and set dev identifier by @cspiel1 in https://github.com/baresip/re/pull/553 * udp: remove udp_send_anon() by @alfredh in https://github.com/baresip/re/pull/550 * cmake: enable export symbols for backtrace by @sreimers in https://github.com/baresip/re/pull/554 * README.md: Update build instructions for cmake by @robert-scheck in https://github.com/baresip/re/pull/556 * cmake: improve kqueue and epoll detection by @sreimers in https://github.com/baresip/re/pull/558 * fs: add fs_stdio_hide() and fs_stdio_restore() helpers by @sreimers in https://github.com/baresip/re/pull/559 * json: remove unknown type warning by @alfredh in https://github.com/baresip/re/pull/560 * http: fix warning arguments by @alfredh in https://github.com/baresip/re/pull/561 * net_if_getlinklocal: use AF from input parameter by @alfredh in https://github.com/baresip/re/pull/565 * fmt: add str_itoa by @sreimers in https://github.com/baresip/re/pull/569 * SDP support for udp by @vanrein in https://github.com/baresip/re/pull/538 * tls: remove some warnings by @alfredh in https://github.com/baresip/re/pull/567 * fmt: add pl_trim functions by @cspiel1 in https://github.com/baresip/re/pull/557 * aes/openssl: remove obsolete version check by @alfredh in https://github.com/baresip/re/pull/572 * http: use str_dup() instead of unsafe strcpy() by @alfredh in https://github.com/baresip/re/pull/574 * doxygen: update comments by @alfredh in https://github.com/baresip/re/pull/577 * reg: remove obsolete void cast by @cspiel1 in https://github.com/baresip/re/pull/576 * Tls connect debug by @alfredh in https://github.com/baresip/re/pull/573 * mk: update doxygen file by @alfredh in https://github.com/baresip/re/pull/578 * ci: use actions/checkout@v3 by @sreimers in https://github.com/baresip/re/pull/579 * tls: remove ifdef from public API by @alfredh in https://github.com/baresip/re/pull/580 * sip: sip_conncfg_set pass by reference by @alfredh in https://github.com/baresip/re/pull/582 * dnsc get conf and skip hash alloc without hash size changes by @fAuernigg in https://github.com/baresip/re/pull/575 * sdp/media: fix reorder codecs (restore old behavior) by @juha-h in https://github.com/baresip/re/pull/583 * list: fix list_flush head and tail by @sreimers in https://github.com/baresip/re/pull/586 * prepare 2.9.0 by @alfredh in https://github.com/baresip/re/pull/587 ## New Contributors * @vanrein made their first contribution in https://github.com/baresip/re/pull/538 **Full Changelog**: https://github.com/baresip/re/compare/v2.8.0...v2.9.0 --- ## [v2.8.0] - 2022-10-01 * Update README.md by @alfredh in https://github.com/baresip/re/pull/503 * thread: fix win32 thrd\_create return values by @sreimers in https://github.com/baresip/re/pull/506 * cmake: bump min. version 3.10 by @sreimers in https://github.com/baresip/re/pull/504 * cmake: add USE\_JBUF option by @alfredh in https://github.com/baresip/re/pull/507 * http/https requests with large body by @fAuernigg in https://github.com/baresip/re/pull/485 * http/client: fix possible null pointer dereference by @sreimers in https://github.com/baresip/re/pull/509 * ci: test choco install no-progress by @alfredh in https://github.com/baresip/re/pull/510 * bitv: remove deprecated module by @alfredh in https://github.com/baresip/re/pull/513 * types,fmt: use re\_restrict by @sreimers in https://github.com/baresip/re/pull/514 * refer out of dialog by @cspiel1 in https://github.com/baresip/re/pull/508 * UPDATE bugfix by @maximilianfridrich in https://github.com/baresip/re/pull/516 * sip/auth: fix mem\_zalloc return check by @sreimers in https://github.com/baresip/re/pull/518 * Update media fixes by @cspiel1 in https://github.com/baresip/re/pull/515 * dns, http: add dnsc\_getaddrinfo\_enabled. prevent reset of getaddrinfo enabled by @fAuernigg in https://github.com/baresip/re/pull/519 * rtp: Improve media synchronization by @Lastique in https://github.com/baresip/re/pull/418 * conf: check if returned size is larger than buffer by @alfredh in https://github.com/baresip/re/pull/523 * udp: remove very old iOS hack by @alfredh in https://github.com/baresip/re/pull/524 * tcp: remove very old iOS hack by @alfredh in https://github.com/baresip/re/pull/525 * Use CMake for debian packages by @sreimers in https://github.com/baresip/re/pull/522 * crc32: add re wrapper by @alfredh in https://github.com/baresip/re/pull/526 * ci: convert valgrind to cmake by @alfredh in https://github.com/baresip/re/pull/529 * ci: convert ssl build to cmake by @alfredh in https://github.com/baresip/re/pull/530 * ci: convert fedora to cmake by @alfredh in https://github.com/baresip/re/pull/531 * ci: convert coverage to cmake by @alfredh in https://github.com/baresip/re/pull/532 * ci: migrate to cmake by @alfredh in https://github.com/baresip/re/pull/533 * cmake: add LINKLIBS and make backtrace and zlib optional by @sreimers in https://github.com/baresip/re/pull/534 * C99 compatibility by @sreimers in https://github.com/baresip/re/pull/536 * pcp: fix cppcheck warning by @alfredh in https://github.com/baresip/re/pull/540 * fmt/print: fix cppcheck overflow warning by @sreimers in https://github.com/baresip/re/pull/542 * tls: remove SHA1 fingerprint (deprecated) by @alfredh in https://github.com/baresip/re/pull/527 * send DTMF via hidden call by @cspiel1 in https://github.com/baresip/re/pull/537 * sipreg: avoid sending un-REGISTER periodically by @cspiel1 in https://github.com/baresip/re/pull/543 * cmake,mk: bump the tentative next release with pre-release identifier by @sreimers in https://github.com/baresip/re/pull/546 * sipsess/update: Add Contact header to UPDATE by @maximilianfridrich in https://github.com/baresip/re/pull/545 * cmake: fix shared API soversion (aligned with make) by @sreimers in https://github.com/baresip/re/pull/549 --- ## [v2.7.0] - 2022-09-01 * async: add re_thread_async by @sreimers in https://github.com/baresip/re/pull/462 * atomic: Add support for gcc __sync intrinsics by @Lastique in https://github.com/baresip/re/pull/467 * btrace: fix gcc 4.3.5 warnings by @cspiel1 in https://github.com/baresip/re/pull/468 * h264: fix gcc 4.3.5 warnings by @cspiel1 in https://github.com/baresip/re/pull/469 * async: add guard by @sreimers in https://github.com/baresip/re/pull/474 * dns/client: add async getaddrinfo usage by @sreimers in https://github.com/baresip/re/pull/470 * async: make work handler and callback optional by @sreimers in https://github.com/baresip/re/pull/481 * BareSip. Add a state update action to the main loop to unblock pollin… by @viordash in https://github.com/baresip/re/pull/480 * dns,net: fix build of asyn_getaddrinfo on gcc 4.3.5 (#482) by @cspiel1 in https://github.com/baresip/re/pull/483 * dns/client: fix getaddrinfo duplicates by @sreimers in https://github.com/baresip/re/pull/486 * http/client: fix dnsc_conf initialization by @sreimers in https://github.com/baresip/re/pull/487 * tmr: tmr_start_dbg use const char for file arg by @sreimers in https://github.com/baresip/re/pull/488 * base64: Encoding/Decoding with URL and Filename Safe Alphabet by @sreimers in https://github.com/baresip/re/pull/471 * misc: fix c11 err handling by @sreimers in https://github.com/baresip/re/pull/476 * cmake: move definitions to re-config.cmake by @sreimers in https://github.com/baresip/re/pull/491 * ci/mingw: fix make retest by @sreimers in https://github.com/baresip/re/pull/492 * cmake: add pkgconfig by @sreimers in https://github.com/baresip/re/pull/493 * Fix error: ‘NI_MAXSERV’ undeclared by @widgetii in https://github.com/baresip/re/pull/495 * Fix error: storage size of ‘ifrr’ isn’t known by @widgetii in https://github.com/baresip/re/pull/496 * ci/musl: add alpine/musl build by @sreimers in https://github.com/baresip/re/pull/499 * Correctly update local media format ids to match those in the offer by @juha-h in https://github.com/baresip/re/pull/498 * debian: fix prefix by @juha-h in https://github.com/baresip/re/pull/501 --- ## [v2.6.0] - 2022-08-01 * ice: change one warning to notice by @alfredh in https://github.com/baresip/re/pull/421 * Fix compilation error on musl: __GNUC_PREREQ macro defined only for libc library by @widgetii in https://github.com/baresip/re/pull/422 * sip: add RFC 3262 support by @maximilianfridrich in https://github.com/baresip/re/pull/419 * bfcp: Add support for TCP transport for BFCP by @Lastique in https://github.com/baresip/re/pull/411 * strans/accept: fix cancel/rejection by @maximilianfridrich in https://github.com/baresip/re/pull/423 * hash: add hash_list_idx() by @sreimers in https://github.com/baresip/re/pull/427 * tls: Add a method to set OpenSSL certificate by @Lastique in https://github.com/baresip/re/pull/426 * sipsess: fix PRACK offer/answer behavior by @maximilianfridrich in https://github.com/baresip/re/pull/430 * thread: thrd_error fixes by @sreimers in https://github.com/baresip/re/pull/431 * sipsess: fix coverity warnings by @maximilianfridrich in https://github.com/baresip/re/pull/433 * main: add re_nfds() and poll_method_get() getters by @sreimers in https://github.com/baresip/re/pull/435 * fmt/print: fix local_itoa casting by @sreimers in https://github.com/baresip/re/pull/437 * leb128: switch to uint64_t by @alfredh in https://github.com/baresip/re/pull/436 * types,mk: remove HAVE_STDBOOL_H by @sreimers in https://github.com/baresip/re/pull/439 * fmt/print: snprintf restrict declarations by @sreimers in https://github.com/baresip/re/pull/438 * net: minor cleanup in linux route code by @alfredh in https://github.com/baresip/re/pull/440 * sip: add RFC 3311 support by @maximilianfridrich in https://github.com/baresip/re/pull/425 * rtmp: check upper bound for amf array by @alfredh in https://github.com/baresip/re/pull/441 * rtcp: check TWCC count range (Coverity fix) by @alfredh in https://github.com/baresip/re/pull/442 * mem: Align data to natural alignment by @Lastique in https://github.com/baresip/re/pull/416 * ci/misc: bump pr-dependency-action@v0.5 by @sreimers in https://github.com/baresip/re/pull/444 * net: linux/rt: init gw to correct af by @alfredh in https://github.com/baresip/re/pull/447 * rtp: Add `rtcp_send` declaration to the public header by @Lastique in https://github.com/baresip/re/pull/448 * Main method best by @alfredh in https://github.com/baresip/re/pull/449 * cmake: add explicit /volatile:ms (required for arm) by @sreimers in https://github.com/baresip/re/pull/451 * mem: Make nrefs atomic by @Lastique in https://github.com/baresip/re/pull/446 * atomic: add some short atomic alias helpers by @sreimers in https://github.com/baresip/re/pull/452 * ci/build: replace deprecated macos-10.15 by @sreimers in https://github.com/baresip/re/pull/454 * Improve RFC 3262 by @maximilianfridrich in https://github.com/baresip/re/pull/450 * atomic: rename helpers by @sreimers in https://github.com/baresip/re/pull/455 * cmake,make: add clang atomic-implicit-seq-cst warning by @sreimers in https://github.com/baresip/re/pull/453 * cmake: add missing includes to install by @paresy in https://github.com/baresip/re/pull/456 * Fix prack handling by @maximilianfridrich in https://github.com/baresip/re/pull/457 * mem: Correct memory clobbering size by @Lastique in https://github.com/baresip/re/pull/458 * mem: Correct calculation of total mem size in mem_status by @Lastique in https://github.com/baresip/re/pull/459 * tls: Securely clear memory from private key material by @Lastique in https://github.com/baresip/re/pull/460 * fmt/str_error: always print error number by @sreimers in https://github.com/baresip/re/pull/461 * thread: add cnd_broadcast posix/win32 fallbacks by @sreimers in https://github.com/baresip/re/pull/463 * list: add list_move() helper by @sreimers in https://github.com/baresip/re/pull/464 * thread: fix thread_create_name ENOMEM by @sreimers in https://github.com/baresip/re/pull/465 --- ## [v2.5.0] - 2022-07-01 * av1: add doxygen comments by @alfredh in https://github.com/baresip/re/pull/384 * rtp: add function to calc sequence number diff by @alfredh in https://github.com/baresip/re/pull/385 * CI fixes by @sreimers in https://github.com/baresip/re/pull/387 * trace: C11 mutex by @alfredh in https://github.com/baresip/re/pull/390 * trace: init refactor by @sreimers in https://github.com/baresip/re/pull/391 * jbuf: use C11 mutex by @alfredh in https://github.com/baresip/re/pull/392 * av1: define and make AV1_AGGR_HDR_SIZE public by @alfredh in https://github.com/baresip/re/pull/393 * main: add re_thread_check() for NON-RE thread calls by @sreimers in https://github.com/baresip/re/pull/389 * cmake: add HAVE_SIGNAL on UNIX by @sreimers in https://github.com/baresip/re/pull/394 * av1: add av1_obu_count() by @alfredh in https://github.com/baresip/re/pull/395 * thread: add mtx_alloc by @sreimers in https://github.com/baresip/re/pull/396 * rtp: C11 mutex by @alfredh in https://github.com/baresip/re/pull/397 * lock: remove deprecated module by @alfredh in https://github.com/baresip/re/pull/398 * Added sippreg_unregister API function by @juha-h in https://github.com/baresip/re/pull/400 * av1 work by @alfredh in https://github.com/baresip/re/pull/402 * rtp: add rtp_is_rtcp_packet() by @alfredh in https://github.com/baresip/re/pull/405 * Fix mutex alloc destroy by @sreimers in https://github.com/baresip/re/pull/406 * av1: minor fixes and doxygen comments by @alfredh in https://github.com/baresip/re/pull/407 * rtp: Add support for RFC5104 PSFB FIR by @Lastique in https://github.com/baresip/re/pull/408 * jbuf: Add drain method by @Lastique in https://github.com/baresip/re/pull/409 * uag: add timestamps to SIP trace by @cspiel1 in https://github.com/baresip/re/pull/412 * fmt/fmt_timestamp: some cleanup by @sreimers in https://github.com/baresip/re/pull/413 * main: refactor libre_init and re_global handling by @sreimers in https://github.com/baresip/re/pull/404 * main: Add support for external threads attaching/detaching re context by @Lastique in https://github.com/baresip/re/pull/414 * mem: Fix formatting for nrefs and size. by @Lastique in https://github.com/baresip/re/pull/415 --- ## [v2.4.0] - 2022-06-01 ## What's Changed * ci: test centos -> fedora by @alfredh in https://github.com/baresip/re/pull/340 * Tls bio opaque by @alfredh in https://github.com/baresip/re/pull/341 * main: remove usage of crypto_set_id_callback() by @alfredh in https://github.com/baresip/re/pull/342 * jbuf: in adaptive mode do not manipulate min buffer size by @cspiel1 in https://github.com/baresip/re/pull/343 * av1 obu by @alfredh in https://github.com/baresip/re/pull/345 * jbuf: improve adaptive mode by @cspiel1 in https://github.com/baresip/re/pull/344 * av1 packetizer by @alfredh in https://github.com/baresip/re/pull/346 * av1: depacketizer by @alfredh in https://github.com/baresip/re/pull/347 * h265: move from rem to re by @alfredh in https://github.com/baresip/re/pull/348 * jbuf: avoid reducing of wish size too early by @cspiel1 in https://github.com/baresip/re/pull/349 * ci/build: add ubuntu 22.04 (beta) by @sreimers in https://github.com/baresip/re/pull/351 * h264: move from rem to re by @alfredh in https://github.com/baresip/re/pull/350 * add C11 thread, mutex and condition API by @sreimers in https://github.com/baresip/re/pull/249 * thread: use pthread as default fallback by @sreimers in https://github.com/baresip/re/pull/354 * mem: use new C11 mutex locking by @sreimers in https://github.com/baresip/re/pull/352 * dbg: use C11 thread mutex by @sreimers in https://github.com/baresip/re/pull/356 * thread: add thread-local storage functions by @sreimers in https://github.com/baresip/re/pull/355 * main/openssl: cleanup by @sreimers in https://github.com/baresip/re/pull/358 * cmake: sort warning flags by @alfredh in https://github.com/baresip/re/pull/359 * doxygen: update comments by @alfredh in https://github.com/baresip/re/pull/360 * main: use C11 thread mutex by @sreimers in https://github.com/baresip/re/pull/357 * make: disable warning flag -Wdeclaration-after-statement by @alfredh in https://github.com/baresip/re/pull/363 * cleanup pthread by @sreimers in https://github.com/baresip/re/pull/362 * update doxygen comments by @alfredh in https://github.com/baresip/re/pull/366 * ci/coverage: downgrade gcovr by @sreimers in https://github.com/baresip/re/pull/365 * tls: print openssl error queue if accept failed by @alfredh in https://github.com/baresip/re/pull/367 * main: fd_setsize -1 for RLIMIT_NOFILE value by @sreimers in https://github.com/baresip/re/pull/368 * jbuf: flush on RTP timeout by @cspiel1 in https://github.com/baresip/re/pull/370 * thread: add mtx_destroy by @sreimers in https://github.com/baresip/re/pull/371 * dns: add query cache by @sreimers in https://github.com/baresip/re/pull/369 * mem,btrace: fix struct alignment by @sreimers in https://github.com/baresip/re/pull/372 * av1: change start flag to continuation flag (inverse) by @alfredh in https://github.com/baresip/re/pull/375 * tmr: add tmr_start_dbg by @sreimers in https://github.com/baresip/re/pull/373 * ice: rename to local pref by @alfredh in https://github.com/baresip/re/pull/376 * tls: Switch from EVP_sha1() to EVP_sha256() when using it for X509_sign() by @robert-scheck in https://github.com/baresip/re/pull/377 --- ## [v2.3.0] - 2022-05-01 * cmake: use static build as default target (improves subdirectory usage) by @sreimers in https://github.com/baresip/re/pull/311 * jbuf: fix RELEASE build with DEBUG_LEVEL 6 by @cspiel1 in https://github.com/baresip/re/pull/313 * fmt/pl: use unsigned type before negation by @sreimers in https://github.com/baresip/re/pull/312 * fmt/pl: rewrite negative handling (avoid undefined behavior) by @sreimers in https://github.com/baresip/re/pull/314 * http/request: fix possbile null pointer dereference by @sreimers in https://github.com/baresip/re/pull/316 * sdp: check sdp_bandwidth lower bound by @sreimers in https://github.com/baresip/re/pull/317 * main: use re_sock_t by @sreimers in https://github.com/baresip/re/pull/315 * ccheck: check all CMakeLists.txt files by @sreimers in https://github.com/baresip/re/pull/320 * list: O(1) sorted insert if we expect append in most cases by @cspiel1 in https://github.com/baresip/re/pull/318 * add pcp protocol by @alfredh in https://github.com/baresip/re/pull/321 * cmake: define RELEASE for release builds by @alfredh in https://github.com/baresip/re/pull/323 * Mem lock win32 by @alfredh in https://github.com/baresip/re/pull/324 * pcp: fix win32 warning by @alfredh in https://github.com/baresip/re/pull/325 * ci/msvc: treat all compiler warnings as errors by @sreimers in https://github.com/baresip/re/pull/326 * cmake: add MSVC /W3 compile option by @sreimers in https://github.com/baresip/re/pull/327 * cmake: add FreeBSD and OpenBSD by @sreimers in https://github.com/baresip/re/pull/329 * md5: remove fallback implementation by @sreimers in https://github.com/baresip/re/pull/328 * cmake: add runtime and development install components by @sreimers in https://github.com/baresip/re/pull/330 * mem: remove low/high block size stats by @alfredh in https://github.com/baresip/re/pull/331 * mem: add error about missing locking by @alfredh in https://github.com/baresip/re/pull/332 * set TCP source port in Via and Contact header by @cspiel1 in https://github.com/baresip/re/pull/334 * remove sys_rel_get and epoll_check by @alfredh in https://github.com/baresip/re/pull/335 * support tls session reuse by @fAuernigg in https://github.com/baresip/re/pull/333 * rand: init only needed for libc rand by @alfredh in https://github.com/baresip/re/pull/336 * tls: fix crash in debug warn msg by @fAuernigg in https://github.com/baresip/re/pull/337 * mem: init g_memLock directly by @alfredh in https://github.com/baresip/re/pull/339 * prepare for version 2.3.0 by @alfredh in https://github.com/baresip/re/pull/338 --- ## [v2.2.2] - 2022-04-09 * sha256: add wrapper by @alfredh in https://github.com/baresip/re/pull/306 * workflow: upgrade to openssl 3.0.2 by @alfredh in https://github.com/baresip/re/pull/305 * aubuf adaptive jitter buffer by @cspiel1 in https://github.com/baresip/re/pull/303 * Improve WIN32 UDP socket handling by @sreimers in https://github.com/baresip/re/pull/296 * tcp: remove tcp_conn_fd by @alfredh in https://github.com/baresip/re/pull/308 * tcp: improve win32 socket and error handling by @sreimers in https://github.com/baresip/re/pull/309 --- ## [v2.2.1] - 2022-04-01 * cmake: add packaging by @sreimers in https://github.com/baresip/re/pull/299 * sha: add sha 256 and 512 digest length OpenSSL compats by @sreimers in https://github.com/baresip/re/pull/300 * main: use Winsock2.h by @sreimers in https://github.com/baresip/re/pull/302 * cmake: for Android platform dont enable ifaddrs/getifaddrs by @alfredh in https://github.com/baresip/re/pull/304 * sa/sa_is_loopback: check full IPv4 loopback range (127.0.0.0/8) by @sreimers in https://github.com/baresip/re/pull/301 --- ## [v2.2.0] - 2022-03-28 * tls: fix coverity defect by @alfredh in https://github.com/baresip/re/pull/270 * http/client: read_file check ftell return value by @sreimers in https://github.com/baresip/re/pull/272 * udp: fix coverity defect by @alfredh in https://github.com/baresip/re/pull/271 * cmake: add detection of HAVE_ARC4RANDOM by @alfredh in https://github.com/baresip/re/pull/269 * Fix coverity issues by @sreimers in https://github.com/baresip/re/pull/273 * Support adding CRLs by @fAuernigg in https://github.com/baresip/re/pull/274 * json/decode: fix possible out of bound access, if code changes by @sreimers in https://github.com/baresip/re/pull/275 * tls/tls_add_crlpem: use const by @sreimers in https://github.com/baresip/re/pull/276 * udp: fix coverity defect by @alfredh in https://github.com/baresip/re/pull/279 * dns: fix Coverity Defect by @alfredh in https://github.com/baresip/re/pull/278 * tls: use const pointer for tls_add_capem() by @cspiel1 in https://github.com/baresip/re/pull/277 * srtp/srtcp: add sanity check for rtcp->tag_len by @sreimers in https://github.com/baresip/re/pull/280 * shim: new module from rew by @alfredh in https://github.com/baresip/re/pull/282 * Trice module by @alfredh in https://github.com/baresip/re/pull/283 * retest trice by @alfredh in https://github.com/baresip/re/pull/284 * Add try_into conversion helper and drop gcc 4.8 support by @sreimers in https://github.com/baresip/re/pull/286 * rtp: fix signed/unsigned warning on WIN32 by @alfredh in https://github.com/baresip/re/pull/287 * fix build error on openbsd arm64 (raspberry pi) by @jimying in https://github.com/baresip/re/pull/290 * cmake: disable C extensions (like make) by @sreimers in https://github.com/baresip/re/pull/292 * fmt: add bool decode from struct pl by @cspiel1 in https://github.com/baresip/re/pull/293 * sdp: a utility function for decoding SDP direction by @cspiel1 in https://github.com/baresip/re/pull/294 * sa/sa_ntop: check inet_ntop() return value by @sreimers in https://github.com/baresip/re/pull/295 * sa_pton: use sa_addrinfo for interface suffix by @alfredh in https://github.com/baresip/re/pull/297 ### New Contributors * @jimying made their first contribution in https://github.com/baresip/re/pull/290 --- ## [v2.1.1] - 2022-03-12 ### Fixes * mk: fix ABI versioning [#268](https://github.com/baresip/re/issues/268) --- ## [v2.1.0] - 2022-03-11 ### What's Changed * Tls sipcert per acc by @cHuberCoffee in https://github.com/baresip/re/pull/96 * ToS for video and sip by @cspiel1 in https://github.com/baresip/re/pull/98 * sdp: in media_decode() reset rdir if port is zero by @cspiel1 in https://github.com/baresip/re/pull/99 * mk/re: add variable length array (-Wvla) compiler warning by @sreimers in https://github.com/baresip/re/pull/100 * Macos openssl by @sreimers in https://github.com/baresip/re/pull/105 * pkg-config version check by @sreimers in https://github.com/baresip/re/pull/107 * sa: add setter and getter for scope id by @cspiel1 in https://github.com/baresip/re/pull/108 * net: in net_dst_source_addr_get() make parameter dst const by @cspiel1 in https://github.com/baresip/re/pull/109 * Avoid 'ISO C90 forbids mixed declarations and code' warnings by @juha-h in https://github.com/baresip/re/pull/112 * SIP redirect callbackfunction by @cHuberCoffee in https://github.com/baresip/re/pull/111 * add secure websocket tls context by @sreimers in https://github.com/baresip/re/pull/113 * fmt: add string to bool function by @cspiel1 in https://github.com/baresip/re/pull/115 * fix clang analyze warnings by @sreimers in https://github.com/baresip/re/pull/114 * fmt: support different separators for parameter parsing by @cspiel1 in https://github.com/baresip/re/pull/117 * Refactor inet_ntop and inet_pton by @sreimers in https://github.com/baresip/re/pull/118 * add essential fields check by @I-mpossible in https://github.com/baresip/re/pull/119 * sa: add support for interface suffix for IPv6ll by @cspiel1 in https://github.com/baresip/re/pull/116 * net: fix net_if_getname IPv6 support by @sreimers in https://github.com/baresip/re/pull/120 * udp: add udp_recv_helper by @alfredh in https://github.com/baresip/re/pull/122 * sa: fix build for old systems by @cspiel1 in https://github.com/baresip/re/pull/121 * sa/addrinfo: fix openbsd (drop AI_V4MAPPED flag) by @sreimers in https://github.com/baresip/re/pull/125 * ci/codeql: add scan-build by @sreimers in https://github.com/baresip/re/pull/128 * Fixed debian changelog version by @juha-h in https://github.com/baresip/re/pull/129 * IPv6 link local support by @cspiel1 in https://github.com/baresip/re/pull/106 * sip: add fallback transport for transp_find() by @cspiel1 in https://github.com/baresip/re/pull/132 * SIP default protocol by @cspiel1 in https://github.com/baresip/re/pull/131 * remove orphaned files by @viordash in https://github.com/baresip/re/pull/136 * outgoing calls early callid by @cspiel1 in https://github.com/baresip/re/pull/135 * sip: fix possible "???" dns srv queries by skipping lines without srvid by @cHuberCoffee in https://github.com/baresip/re/pull/133 * odict: hide struct odict_entry by @sreimers in https://github.com/baresip/re/pull/130 * tls: add keylogger callback function by @cHuberCoffee in https://github.com/baresip/re/pull/140 * http/client: support other auth token types besides bearer by @fAuernigg in https://github.com/baresip/re/pull/142 * tls: fix client certificate replacement by @cHuberCoffee in https://github.com/baresip/re/pull/145 * http/client: support dns ipv6 by @fAuernigg in https://github.com/baresip/re/pull/141 * rtp: add payload-type helper by @alfredh in https://github.com/baresip/re/pull/148 * sip: check consistency between CSeq method and that of request line by @I-mpossible in https://github.com/baresip/re/pull/146 * Fix win32 by @viordash in https://github.com/baresip/re/pull/149 * fix warnings from PVS-Studio C++ static analyzer by @viordash in https://github.com/baresip/re/pull/150 * RTP inbound telephone events should not lead to packet loss by @cspiel1 in https://github.com/baresip/re/pull/151 * support inet6 by default in Win32 project by @viordash in https://github.com/baresip/re/pull/154 * sdp: differentiate between media line disabled or rejected by @cHuberCoffee in https://github.com/baresip/re/pull/134 * move network check to module by @cspiel1 in https://github.com/baresip/re/pull/152 * odict: move odict_compare from retest to re by @fAuernigg in https://github.com/baresip/re/pull/153 * sip: reuse transport protocol of first request in dialog (#143) by @cspiel1 in https://github.com/baresip/re/pull/144 * json: fix parsing json containing only single value by @fAuernigg in https://github.com/baresip/re/pull/155 * ice: fix checklist by @alfredh in https://github.com/baresip/re/pull/156 * mk: add compile_commands.json (clang only) by @sreimers in https://github.com/baresip/re/pull/157 * sdp: debug print session and media direction by @cspiel1 in https://github.com/baresip/re/pull/158 * add btrace module (linux/unix only) by @sreimers in https://github.com/baresip/re/pull/160 * mk: add CC_TEST header check by @sreimers in https://github.com/baresip/re/pull/162 * init dst address by @cspiel1 in https://github.com/baresip/re/pull/164 * ice: check if candpair exist before adding by @alfredh in https://github.com/baresip/re/pull/165 * mk: add CC_TEST cache by @sreimers in https://github.com/baresip/re/pull/163 * btrace: use HAVE_EXECINFO by @sreimers in https://github.com/baresip/re/pull/166 * Coverity by @sreimers in https://github.com/baresip/re/pull/170 * icem: remove dead code (found by coverity 240639) by @sreimers in https://github.com/baresip/re/pull/171 * hash: switch to simpler "fast algorithm" by @ydroneaud in https://github.com/baresip/re/pull/173 * dns: fix dnsc_alloc with IPv6 disabled by @sreimers in https://github.com/baresip/re/pull/174 * mk: deprecate HAVE_INET6 by @sreimers in https://github.com/baresip/re/pull/175 * Fix for btrace print for memory leaks by @cspiel1 in https://github.com/baresip/re/pull/177 * set sdp laddr to SIP src address by @cspiel1 in https://github.com/baresip/re/pull/172 * sdp: include all media formats in SDP offer by @cHuberCoffee in https://github.com/baresip/re/pull/176 * ci: add centos 7 build test by @sreimers in https://github.com/baresip/re/pull/179 * sip: move sip_auth_encode to public api for easier testing by @sreimers in https://github.com/baresip/re/pull/181 * sipsess: do not call desc handler on shutdown by @cspiel1 in https://github.com/baresip/re/pull/182 * stream flush rtp socket by @cspiel1 in https://github.com/baresip/re/pull/185 * ci: fix macos openssl build by @sreimers in https://github.com/baresip/re/pull/188 * http: HTTP Host header conform to RFC for IPv6 addresses by @cspiel1 in https://github.com/baresip/re/pull/189 * Increased debian compatibility level from 9 to 10 by @juha-h in https://github.com/baresip/re/pull/192 * mk: move darwin dns LFLAGS to re.mk (fixes static builds) by @sreimers in https://github.com/baresip/re/pull/193 * build infrastructure: silent and verbose modes by @abrodkin in https://github.com/baresip/re/pull/194 * mk: use posix regex for sed CC major version detection by @sreimers in https://github.com/baresip/re/pull/195 * dns: fix parse_resolv_conf for OpenBSD by @sreimers in https://github.com/baresip/re/pull/196 * sip: add optional TCP source port by @cspiel1 in https://github.com/baresip/re/pull/198 * ci: add mingw build and test by @sreimers in https://github.com/baresip/re/pull/199 * net: remove net_hostaddr by @sreimers in https://github.com/baresip/re/pull/200 * ci/centos7: add openssl by @sreimers in https://github.com/baresip/re/pull/203 * hmac: use HMAC() api (fixes OpenSSL 3.0 deprecations) by @sreimers in https://github.com/baresip/re/pull/202 * md5: use EVP_Digest for newer openssl versions by @sreimers in https://github.com/baresip/re/pull/204 * sha: add new sha1() api by @sreimers in https://github.com/baresip/re/pull/205 * OpenSSL 3.0 by @sreimers in https://github.com/baresip/re/pull/206 * udp: add win32 qos support by @sreimers in https://github.com/baresip/re/pull/186 * ci/mingw: fix dependency checkout by @sreimers in https://github.com/baresip/re/pull/207 * ice: remove ice_mode by @alfredh in https://github.com/baresip/re/pull/147 * Codeql security by @sreimers in https://github.com/baresip/re/pull/208 * aubuf insert auframes sorted by @cspiel1 in https://github.com/baresip/re/pull/209 * ci: add valgrind by @sreimers in https://github.com/baresip/re/pull/214 * tls: remove code for openssl 0.9.5 by @alfredh in https://github.com/baresip/re/pull/215 * ice: remove unused file by @alfredh in https://github.com/baresip/re/pull/217 * main: remove obsolete OPENWRT epoll check by @alfredh in https://github.com/baresip/re/pull/218 * dns,http,sa: fix HAVE_INET6 off warnings by @sreimers in https://github.com/baresip/re/pull/219 * preliminary support for cmake by @alfredh in https://github.com/baresip/re/pull/220 * make,cmake: set SOVERSION to major version by @sreimers in https://github.com/baresip/re/pull/221 * mk: remove MSVC project files, use cmake instead by @alfredh in https://github.com/baresip/re/pull/223 * natbd: remove module (deprecated) by @alfredh in https://github.com/baresip/re/pull/225 * sha: remove backup implementation by @alfredh in https://github.com/baresip/re/pull/224 * sha,hmac: use Apple CommonCrypto if defined by @alfredh in https://github.com/baresip/re/pull/226 * stun: add stun_generate_tid by @alfredh in https://github.com/baresip/re/pull/227 * add cmakelint by @sreimers in https://github.com/baresip/re/pull/228 * Cmake version by @alfredh in https://github.com/baresip/re/pull/229 * cmake: add option to enable/disable rtmp module by @alfredh in https://github.com/baresip/re/pull/230 * lock: use rwlock by default by @sreimers in https://github.com/baresip/re/pull/232 * cmake: fixes for MSVC 16 by @alfredh in https://github.com/baresip/re/pull/233 * json: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/234 * ci: add cmake build by @sreimers in https://github.com/baresip/re/pull/222 * mqueue: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/235 * tcp: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/236 * cmake: fix target_link_libraries for win32 by @alfredh in https://github.com/baresip/re/pull/238 * stun: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/237 * udp: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/239 * tls: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/241 * remove HAVE_INTTYPES_H by @alfredh in https://github.com/baresip/re/pull/231 * udp: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/242 * cmake: minor fixes by @alfredh in https://github.com/baresip/re/pull/244 * cmake: fix MSVC ninja by @sreimers in https://github.com/baresip/re/pull/243 * tcp: fix win32 warnings by @alfredh in https://github.com/baresip/re/pull/245 * udp: fix win32 msvc warnings by @sreimers in https://github.com/baresip/re/pull/246 * rtmp: fix win32 warning by @sreimers in https://github.com/baresip/re/pull/247 * bfcp: fix win32 warning by @sreimers in https://github.com/baresip/re/pull/248 * tls: fix libressl 3.5 by @sreimers in https://github.com/baresip/re/pull/250 * fix coverity scan warnings by @sreimers in https://github.com/baresip/re/pull/251 * Allow hanging up call that has not been ACKed yet by @juha-h in https://github.com/baresip/re/pull/252 * mk,cmake: add backtrace support and fix linking on OpenBSD by @sreimers in https://github.com/baresip/re/pull/254 * github: add CMake and Windows workflow by @alfredh in https://github.com/baresip/re/pull/255 * Windows (VS 2022/Ninja) by @sreimers in https://github.com/baresip/re/pull/257 * cmake: fixes for Android by @alfredh in https://github.com/baresip/re/pull/258 * tmr: reuse tmr_jiffies_usec by @alfredh in https://github.com/baresip/re/pull/259 * trace: use gettid as thread_id on linux by @sreimers in https://github.com/baresip/re/pull/213 * tmr: use CLOCK_MONOTONIC_RAW if defined by @alfredh in https://github.com/baresip/re/pull/260 * add atomic support by @sreimers in https://github.com/baresip/re/pull/261 * Sonarcloud by @sreimers in https://github.com/baresip/re/pull/262 * sip: fix gcc 6.3.0 warning for logical expression (#256) by @cspiel1 in https://github.com/baresip/re/pull/263 * add transport-cc rtcp feedback support by @fippo in https://github.com/baresip/re/pull/264 ### New Contributors * @I-mpossible made their first contribution in https://github.com/baresip/re/pull/119 * @viordash made their first contribution in https://github.com/baresip/re/pull/136 * @ydroneaud made their first contribution in https://github.com/baresip/re/pull/173 * @abrodkin made their first contribution in https://github.com/baresip/re/pull/194 --- ## [v2.0.1] - 2021-04-22 ### Fixed - tmr: fix FreeBSD and OpenBSD [#97] - mk: fix clang analyze CFLAGS ### Changed - tls: different return values for tls_get_ca_chain_field() [#94] --- ## [v2.0.0] - 2021-04-10 ### Added - .gitignore: add ctags and vim swp files to gitignore [#31] - tls: add tls_add_capem() for adding CA cert as PEM string [#33] - httpauth: Add digest support for http clients [#33] - httpauth: Add basic authentication for HTTP clients [#33] - dns: add set function for DNS config [#33] - http/client: support IPv6 [#33] - http/client: use const parameter for set laddr(6) functions [#33] - http/client: add set function for timeout [#33] - http/client: add http_client_add_capem() [#33] - http/client: add set functions for client certificate and private key [#33] - http: add HTTP request connection with authorization [#33] - http: setting of timeouts for http client [#35] - http: set default path for http requests [#35] - tls: set selfsigned Elliptic Curve (EC) function [#17] - tls: extend server verification by host name check (SNI) [#45] - jbuf: adapative jitter buffer [#41] - tmr: add tmr_jiffies_usec() - get accurate microseconds [#52] - fmt: add pl_i32() that converts pl to int32_t [#60] - fmt: add pl_i64() that converts pl to int64_t [#60] - mk/re: add C11 and Atomic detection [#61] - ci: add abi check [#39] - trace: add re_trace api [#48] - Add function that resets the timeout timer for a connection of the HTTP server. [#88] - add error trace helpers [#87] - sip/auth: add algorithm=MD5 [#86] - sys: filesystem isdir function - tls: use ENOENT in tls_add_cafile_path as error code - tls: more generic function to set cafile and capath - mk: add .so name versioning, resolves #32 - mk/re: add clang shorten-64-to-32 warning - mk/re: document new library/header prioritised order with custom SYSROOT - mk/re: info double colon rule (#64) [#64] - udp: Add function udp_open for socket without bind - rtp: Add rtp_open which creates an RTP object only for sending. [#77] - sip: add decode function for SIP transport - sip: SIP/TLS Server Name Indication (#67) [#67] - transp: add flag to disable SIP TLS server verification [#76] ### Removed - openssl: remove obsolete function tls_set_hostname() [#33] - mk/re: remove gcc 2.x/3.x support [#58] - ci: drop ubuntu 16.04 support - end of life ### Changed - http/client: cleanup doxygen [#33] - http/client: use host of http_req for the host name validation [#37] - main: disable MAIN_DEBUG, TMR_DEBUG and increase MAX_BLOCKING to 500ms [#43] - sipreg: dont't force digest challenge for register [#49] - mk/re: do not override LIBRE_INC, LIBRE_SO and LIBRE_PATH [#62] - readme: update supported systems and add tiers [#81] - tls: use ENOTDIR in tls_add_cafile_path if capath is not a dir [#84] - tls: check capath is directory - net: get default source addr from udp local test socket [#66] - Update chklist.c [#70] - Update icesdp.c [#69] - mk: cross build changes (#63) [#63] - sip: use sip_transp_decode() [#71] - tls: tls_get_issuer/subject return the info of the first loaded ca [#80] ### Fixed - dns/client: fix HAVE_INET6 and win32/vcxproj: updates [#28] - http: fix segfault in response.c [#35] - http/request: parameter NULL check for http_reqconn_send() [#37] - http/client: fix conn_idle [#46] - http/httpreq: mem leak fix [#47] - sip/request: fix msg->scode null pointer dereference - rtmp/conn: initialize err - mk/re: fix LIBRE_SO static detection - dns/res: Properly process IPV4 and IPV6 addresses (DARWIN) [#56] - sip/keepalive: fix codeql cpp/integer-multiplication-cast-to-long - fmt/time: fix codeql gmtime warning - mk/re: fix gcc 4.x and newer compiler warnings - sys: add _BSD_SOURCE 1 for compatibility reasons [#92] - fix weak self-signed certificates [#68] - net/tls: fixing shorten-64-to-32 warnings [#65] - http: add missing newline to warning [#78] - http: fix file read for client certificates - mk/re: do not override LIBRE_INC, LIBRE_SO and LIBRE_PATH [#62] - tls: safety NULL pointer check in tls_add_ca() [#79] ### Contributors (many thanks) - [sreimers](https://github.com/sreimers) - [cHuberCoffee](https://github.com/cHuberCoffee) - [RobertMi21](https://github.com/RobertMi21) - [cspiel1](https://github.com/cspiel1) - [alfredh](https://github.com/alfredh) - [fippo](https://github.com/fippo) - [jurjen-van-dijk](https://github.com/jurjen-van-dijk) - [rolizo](https://github.com/rolizo) ## [v1.1.0] - 2020-10-04 ### Added - tls: functions to get the certificate issuer and subject [#18] - uri: Added path field to struct uri and its decode to uri_decode [#22] - tcp: add tcp_connect_bind [#24] - http: support bind to laddr in http_request [#24] - sipreg: support Cisco REGISTER keep-alives [#19] - sip: websocket support [#26] ### Fixed - tls/openssl: fix X509_NAME win32/wincrypt.h conflict - dns: listen on IPv4 and IPv6 socket [#27] - main: fix/optimize windows file descriptors [#25] ### Contributors (many thanks) - Alfred E. Heggestad - Christian Spielberger - Christoph Huber - Franz Auernigg - Juha Heinanen - johnjuuljensen - Sebastian Reimers ## [v1.0.0] - 2020-09-08 ### Added - sip: add trace - sdp: sdp_media_disabled API function [#2] - tls: add tls_set_selfsigned_rsa [#6] - tls: add functions to verify server cert, purpose and hostname [#10] - http: client should set SNI [#10] - http: client should use tls functions to verify server certs, purpose and hostname [#10] - sipreg: add proxy expires field and get function [#13] - sipreg: make re-register interval configurable [#13] ### Changed - debian: Automatic cleanup after building debian package ### Fixed - Set SDK path (SYSROOT) using xcrun (fix building on macOS 10.14) - tcp: close socket on windows if connection is aborted or reset [#1] - rtmp: Fix URL path parsing (creytiv#245) - ice: various fixes [baresip/baresip#925] - openssl/tls: replace deprecated openssl 1.1.0 functions [#5] ### Contributors (many thanks) - Alfred E. Heggestad - Christian Spielberger - Christoph Huber - Franz Auernigg - juha-h - Juha Heinanen - Richard Aas - Sebastian Reimers [#97]: https://github.com/baresip/re/pull/97 [#94]: https://github.com/baresip/re/pull/94 [#81]: https://github.com/baresip/re/pull/81 [#48]: https://github.com/baresip/re/pull/48 [#92]: https://github.com/baresip/re/pull/92 [#88]: https://github.com/baresip/re/pull/88 [#87]: https://github.com/baresip/re/pull/87 [#86]: https://github.com/baresip/re/pull/86 [#84]: https://github.com/baresip/re/pull/84 [#83]: https://github.com/baresip/re/pull/83 [#82]: https://github.com/baresip/re/pull/82 [#80]: https://github.com/baresip/re/pull/80 [#79]: https://github.com/baresip/re/pull/79 [#78]: https://github.com/baresip/re/pull/78 [#77]: https://github.com/baresip/re/pull/77 [#76]: https://github.com/baresip/re/pull/76 [#39]: https://github.com/baresip/re/pull/39 [#66]: https://github.com/baresip/re/pull/66 [#74]: https://github.com/baresip/re/pull/74 [#67]: https://github.com/baresip/re/pull/67 [#71]: https://github.com/baresip/re/pull/71 [#70]: https://github.com/baresip/re/pull/70 [#69]: https://github.com/baresip/re/pull/69 [#68]: https://github.com/baresip/re/pull/68 [#65]: https://github.com/baresip/re/pull/65 [#63]: https://github.com/baresip/re/pull/63 [#64]: https://github.com/baresip/re/pull/64 [#62]: https://github.com/baresip/re/pull/62 [#61]: https://github.com/baresip/re/pull/61 [#60]: https://github.com/baresip/re/pull/60 [#58]: https://github.com/baresip/re/pull/58 [#56]: https://github.com/baresip/re/pull/56 [#52]: https://github.com/baresip/re/pull/52 [#49]: https://github.com/baresip/re/pull/49 [#47]: https://github.com/baresip/re/pull/47 [#46]: https://github.com/baresip/re/pull/46 [#45]: https://github.com/baresip/re/pull/45 [#43]: https://github.com/baresip/re/pull/43 [#41]: https://github.com/baresip/re/pull/41 [#37]: https://github.com/baresip/re/pull/37 [#35]: https://github.com/baresip/re/pull/35 [#33]: https://github.com/baresip/re/pull/33 [#31]: https://github.com/baresip/re/pull/31 [#28]: https://github.com/baresip/re/pull/28 [#27]: https://github.com/baresip/re/pull/27 [#26]: https://github.com/baresip/re/pull/26 [#25]: https://github.com/baresip/re/pull/25 [#19]: https://github.com/baresip/re/pull/19 [#24]: https://github.com/baresip/re/pull/24 [#22]: https://github.com/baresip/re/pull/22 [#18]: https://github.com/baresip/re/pull/18 [#17]: https://github.com/baresip/re/pull/17 [#13]: https://github.com/baresip/re/pull/13 [#10]: https://github.com/baresip/re/pull/10 [#6]: https://github.com/baresip/re/pull/6 [#5]: https://github.com/baresip/re/pull/5 [#2]: https://github.com/baresip/re/pull/2 [#1]: https://github.com/baresip/re/pull/1 [Unreleased]: https://github.com/baresip/re/compare/v2.7.0...HEAD [v2.7.0]: https://github.com/baresip/re/compare/v2.6.0...v2.7.0 [v2.6.0]: https://github.com/baresip/re/compare/v2.5.0...v2.6.0 [v2.5.0]: https://github.com/baresip/re/compare/v2.4.0...v2.5.0 [v2.4.0]: https://github.com/baresip/re/compare/v2.3.0...v2.4.0 [v2.3.0]: https://github.com/baresip/re/compare/v2.2.2...v2.3.0 [v2.2.2]: https://github.com/baresip/re/compare/v2.2.1...v2.2.2 [v2.2.1]: https://github.com/baresip/re/compare/v2.2.0...v2.2.1 [v2.2.0]: https://github.com/baresip/re/compare/v2.1.1...v2.2.0 [v2.1.1]: https://github.com/baresip/re/compare/v2.1.0...v2.1.1 [v2.1.0]: https://github.com/baresip/re/compare/v2.0.1...v2.1.0 [v2.0.1]: https://github.com/baresip/re/compare/v2.0.0...v2.0.1 [v2.0.0]: https://github.com/baresip/re/compare/v1.1.0...v2.0.0 [v1.1.0]: https://github.com/baresip/re/compare/v1.0.0...v1.1.0 [v1.0.0]: https://github.com/baresip/re/compare/v0.6.1...v1.0.0 ================================================ FILE: CMakeLists.txt ================================================ # # CMakeLists.txt # # Copyright (C) 2010 - 2025 Alfred E. Heggestad # Copyright (C) 2022 - 2025 Sebastian Reimers # Copyright (C) 2023 - 2025 Christian Spielberger # ############################################################################## # # Versioning # cmake_minimum_required(VERSION 3.18...4.0) project(re VERSION 4.8.0 LANGUAGES C HOMEPAGE_URL https://github.com/baresip/re DESCRIPTION "Generic library for real-time communications" ) set(PROJECT_SOVERSION 42) # bump if ABI breaks # Pre-release identifier, comment out on a release # Increment for breaking changes (dev2, dev3...) #set(PROJECT_VERSION_PRE dev) if(PROJECT_VERSION_PRE) set(PROJECT_VERSION_FULL ${PROJECT_VERSION}-${PROJECT_VERSION_PRE}) else() set(PROJECT_VERSION_FULL ${PROJECT_VERSION}) endif() if(WIN32 AND NOT MINGW) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std:c11") endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) ############################################################################## # # Module/Package Includes # include(GNUInstallDirs) include(CheckCCompilerFlag) ############################################################################## # # Options # option(USE_REM "Enable Librem" ON) option(USE_BFCP "Enable BFCP" ON) option(USE_PCP "Enable PCP" ON) option(USE_RTMP "Enable RTMP" ON) option(USE_SIP "Enable SIP" ON) option(LIBRE_BUILD_SHARED "Build shared library" ON) option(LIBRE_BUILD_STATIC "Build static library" ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(MSVC) add_compile_options("/W3") else() set(c_flags -pedantic -Wall -Wbad-function-cast -Wcast-align -Wextra -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wno-strict-aliasing -Wold-style-definition -Wshadow -Wstrict-prototypes -Wuninitialized -Wvla ) add_compile_options( "$<$:${c_flags}>" ) endif() if(CMAKE_C_COMPILER_ID MATCHES "Clang") add_compile_options( -Wno-gnu-zero-variadic-macro-arguments -Wno-c2x-extensions ) add_compile_options("$<$:-Wshorten-64-to-32>") endif() check_c_compiler_flag("-Watomic-implicit-seq-cst" COMPILER_SUPPORTS_WATOMIC) if(COMPILER_SUPPORTS_WATOMIC) add_compile_options("$<$:-Watomic-implicit-seq-cst>") endif() if(CMAKE_C_COMPILER_ID MATCHES "Clang") # Ensure struct mem is aligned (used as fat pointer) set_source_files_properties(src/mem/mem.c PROPERTIES COMPILE_FLAGS -Wpadded) endif() set(re_DIR ${CMAKE_CURRENT_LIST_DIR}/cmake) include("${CMAKE_CURRENT_LIST_DIR}/cmake/re-config.cmake") list(APPEND RE_DEFINITIONS -DRE_VERSION="${PROJECT_VERSION_FULL}" -DVER_MAJOR=${PROJECT_VERSION_MAJOR} -DVER_MINOR=${PROJECT_VERSION_MINOR} -DVER_PATCH=${PROJECT_VERSION_PATCH} -D_GNU_SOURCE ) if(DEFINED TRACE_SSL) list(APPEND RE_DEFINITIONS -DTRACE_SSL="${TRACE_SSL}") endif() ############################################################################## # # Source/Header section # set(HEADERS include/re.h include/re_aes.h include/re_async.h include/re_atomic.h include/re_av1.h include/re_base64.h include/re_bfcp.h include/re_btrace.h include/re_conf.h include/re_convert.h include/re_crc32.h include/re_dbg.h include/re_dd.h include/re_dns.h include/re_fmt.h include/re_h264.h include/re_h265.h include/re_hash.h include/re_hmac.h include/re_http.h include/re_httpauth.h include/re_ice.h include/re_json.h include/re_list.h include/re_main.h include/re_mbuf.h include/re_md5.h include/re_mem.h include/re_mod.h include/re_mqueue.h include/re_msg.h include/re_net.h include/re_odict.h include/re_pcp.h include/re_rtmp.h include/re_rtp.h include/re_rtpext.h include/re_sa.h include/re_sdp.h include/re_sha.h include/re_shim.h include/re_sip.h include/re_sipevent.h include/re_sipreg.h include/re_sipsess.h include/re_srtp.h include/re_stun.h include/re_sys.h include/re_tcp.h include/re_telev.h include/re_thread.h include/re_tls.h include/re_tmr.h include/re_trace.h include/re_trice.h include/re_turn.h include/re_types.h include/re_udp.h include/re_unixsock.h include/re_uri.h include/re_websock.h include/rem_aac.h include/rem_aubuf.h include/rem_auconv.h include/rem_audio.h include/rem_aufile.h include/rem_auframe.h include/rem_au.h include/rem_aulevel.h include/rem_aumix.h include/rem_auresamp.h include/rem_autone.h include/rem_avc.h include/rem_dsp.h include/rem_dtmf.h include/rem_fir.h include/rem_flv.h include/rem_g711.h include/rem_goertzel.h include/rem.h include/rem_vidconv.h include/rem_video.h include/rem_vid.h include/rem_vidmix.h ) set(SRCS src/av1/depack.c src/av1/obu.c src/av1/pkt.c src/async/async.c src/base64/b64.c src/btrace/btrace.c src/conf/conf.c src/dbg/dbg.c src/dd/dd.c src/dd/dd_enc.c src/dd/putbit.c src/dns/client.c src/dns/cstr.c src/dns/dname.c src/dns/hdr.c src/dns/ns.c src/dns/rr.c src/dns/rrlist.c src/fmt/ch.c src/fmt/hexdump.c src/fmt/pl.c src/fmt/print.c src/fmt/prm.c src/fmt/regex.c src/fmt/str.c src/fmt/str_error.c src/fmt/text2pcap.c src/fmt/time.c src/fmt/unicode.c src/h264/getbit.c src/h264/nal.c src/h264/sps.c src/h265/nal.c src/hash/func.c src/hash/hash.c src/hmac/hmac_sha1.c src/http/auth.c src/http/chunk.c src/http/client.c src/http/msg.c src/http/request.c src/http/server.c src/httpauth/basic.c src/httpauth/digest.c src/ice/cand.c src/ice/candpair.c src/ice/chklist.c src/ice/comp.c src/ice/connchk.c src/ice/icem.c src/ice/icesdp.c src/ice/icestr.c src/ice/stunsrv.c src/ice/util.c src/json/decode.c src/json/decode_odict.c src/json/encode.c src/list/list.c src/main/init.c src/main/main.c src/main/method.c src/mbuf/mbuf.c src/md5/wrap.c src/mem/mem.c src/mem/mem_pool.c src/mem/secure.c src/mod/mod.c src/mqueue/mqueue.c src/msg/ctype.c src/msg/param.c src/net/if.c src/net/net.c src/net/netstr.c src/net/rt.c src/net/sock.c src/net/sockopt.c src/odict/entry.c src/odict/get.c src/odict/odict.c src/odict/type.c src/rtp/fb.c src/rtp/member.c src/rtp/ntp.c src/rtp/pkt.c src/rtp/rr.c src/rtp/rtcp.c src/rtp/rtp.c src/rtp/sdes.c src/rtp/sess.c src/rtp/source.c src/rtpext/rtpext.c src/sa/printaddr.c src/sa/sa.c src/sdp/attr.c src/sdp/format.c src/sdp/media.c src/sdp/msg.c src/sdp/session.c src/sdp/str.c src/sdp/util.c src/sha/wrap.c src/shim/shim.c src/srtp/misc.c src/srtp/replay.c src/srtp/srtcp.c src/srtp/srtp.c src/srtp/stream.c src/stun/addr.c src/stun/attr.c src/stun/ctrans.c src/stun/dnsdisc.c src/stun/hdr.c src/stun/ind.c src/stun/keepalive.c src/stun/msg.c src/stun/rep.c src/stun/req.c src/stun/stun.c src/stun/stunstr.c src/sys/daemon.c src/sys/endian.c src/sys/fs.c src/sys/rand.c src/sys/sleep.c src/sys/sys.c src/tcp/tcp.c src/tcp/tcp_high.c src/telev/telev.c src/thread/thread.c src/tmr/tmr.c src/trace/trace.c src/trice/cand.c src/trice/candpair.c src/trice/chklist.c src/trice/connchk.c src/trice/lcand.c src/trice/rcand.c src/trice/stunsrv.c src/trice/tcpconn.c src/trice/trice.c src/turn/chan.c src/turn/perm.c src/turn/turnc.c src/udp/mcast.c src/udp/udp.c src/unixsock/unixsock.c src/uri/uri.c src/uri/uric.c src/websock/websock.c ) set(REM_SRCS rem/aac/aac.c rem/au/fmt.c rem/au/util.c rem/aubuf/aubuf.c rem/aubuf/ajb.c rem/auconv/auconv.c rem/aufile/aufile.c rem/aufile/wave.c rem/auframe/auframe.c rem/aulevel/aulevel.c rem/aumix/aumix.c rem/auresamp/resamp.c rem/autone/tone.c rem/avc/config.c rem/dtmf/dec.c rem/fir/fir.c rem/g711/g711.c rem/goertzel/goertzel.c rem/vid/draw.c rem/vid/fmt.c rem/vid/frame.c rem/vidconv/vconv.c rem/vidmix/vidmix.c ) if(USE_BFCP) list(APPEND SRCS src/bfcp/attr.c src/bfcp/conn.c src/bfcp/msg.c src/bfcp/reply.c src/bfcp/request.c ) endif() if(USE_PCP) list(APPEND SRCS src/pcp/msg.c src/pcp/option.c src/pcp/payload.c src/pcp/pcp.c src/pcp/reply.c src/pcp/request.c ) endif() if(USE_RTMP) list(APPEND SRCS src/rtmp/amf.c src/rtmp/amf_dec.c src/rtmp/amf_enc.c src/rtmp/chunk.c src/rtmp/conn.c src/rtmp/control.c src/rtmp/ctrans.c src/rtmp/dechunk.c src/rtmp/hdr.c src/rtmp/stream.c ) endif() if(USE_SIP) list(APPEND SRCS src/sip/addr.c src/sip/auth.c src/sip/contact.c src/sip/cseq.c src/sip/ctrans.c src/sip/dialog.c src/sip/keepalive.c src/sip/keepalive_udp.c src/sip/msg.c src/sip/rack.c src/sip/reply.c src/sip/request.c src/sip/sip.c src/sip/strans.c src/sip/transp.c src/sip/via.c src/sipevent/listen.c src/sipevent/msg.c src/sipevent/notify.c src/sipevent/subscribe.c src/sipreg/reg.c src/sipsess/accept.c src/sipsess/ack.c src/sipsess/close.c src/sipsess/connect.c src/sipsess/info.c src/sipsess/listen.c src/sipsess/modify.c src/sipsess/prack.c src/sipsess/reply.c src/sipsess/request.c src/sipsess/sess.c src/sipsess/update.c ) endif() if(USE_OPENSSL) list(APPEND SRCS src/main/openssl.c src/aes/openssl/aes.c src/tls/openssl/tls_tcp.c src/tls/openssl/tls_udp.c src/tls/openssl/tls.c src/tls/openssl/sni.c src/hmac/openssl/hmac.c ) elseif(APPLE) list(APPEND SRCS src/aes/apple/aes.c src/hmac/apple/hmac.c ) else() list(APPEND SRCS src/aes/stub.c src/hmac/hmac.c src/tls/stub.c ) endif() if(WIN32) list(APPEND SRCS src/dns/win32/srv.c src/mod/win32/dll.c src/mqueue/win32/pipe.c src/net/win32/wif.c ) elseif(UNIX) list(APPEND SRCS src/mod/dl.c src/net/posix/pif.c ) if(HAVE_GETIFADDRS) list(APPEND SRCS src/net/ifaddrs.c ) endif() endif() list(APPEND SRCS src/crc32/crc32.c ) if(HAVE_THREADS) #Do nothing elseif(CMAKE_USE_WIN32_THREADS_INIT) list(APPEND SRCS src/thread/win32.c ) else() list(APPEND SRCS src/thread/posix.c ) endif() if(HAVE_RESOLV) list(APPEND SRCS src/dns/res.c ) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") list(APPEND SRCS src/dns/darwin/srv.c src/net/bsd/brt.c ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS") list(APPEND SRCS src/dns/darwin/srv.c ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") list(APPEND SRCS src/net/bsd/brt.c ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") list(APPEND SRCS src/net/bsd/brt.c ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") list(APPEND SRCS src/net/linux/rt.c src/net/linux/addrs.c ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Android") list(APPEND SRCS src/net/linux/rt.c ) endif() if(USE_REM) list(APPEND SRCS ${REM_SRCS}) endif() ############################################################################## # # Main target object # add_library(re-objs OBJECT ${SRCS} ${HEADERS}) set_target_properties(re-objs PROPERTIES POSITION_INDEPENDENT_CODE ON) target_compile_definitions(re-objs PRIVATE ${RE_DEFINITIONS}) target_include_directories(re-objs PRIVATE include) target_include_directories(re-objs PRIVATE ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS}) ############################################################################## # # Shared target libre.[so|dll|dylib] # if(LIBRE_BUILD_SHARED) list(APPEND RE_INSTALL_TARGETS re-shared) add_library(re-shared SHARED $) target_link_libraries(re-shared PRIVATE ${RE_LIBS}) set_target_properties(re-shared PROPERTIES VERSION ${PROJECT_SOVERSION}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) set_target_properties(re-shared PROPERTIES SOVERSION ${PROJECT_SOVERSION}) set_target_properties(re-shared PROPERTIES OUTPUT_NAME "re") add_library(libre::re-shared ALIAS re-shared) endif() ############################################################################## # # Static target libre.a # if(LIBRE_BUILD_STATIC) list(APPEND RE_INSTALL_TARGETS re) add_library(re STATIC $) target_link_libraries(re PRIVATE ${RE_LIBS}) target_include_directories(re PUBLIC $ ) add_library(libre::re ALIAS re) if(MSVC) set_target_properties(re PROPERTIES OUTPUT_NAME "re-static") if(NOT LIBRE_BUILD_SHARED) set(PC_LIBNAME "re-static") endif() endif() endif() ############################################################################## # # PKGCONF section # if(NOT PC_LIBNAME) set(PC_LIBNAME "re") endif() set(PC_REQUIRES "") set(PC_LINKLIBS "") foreach(item IN LISTS RE_LIBS) if(item STREQUAL "Threads::Threads") list(APPEND PC_LINKLIBS ${CMAKE_THREADS_LIBS_INIT}) elseif(item STREQUAL "OpenSSL::Crypto") list(APPEND PC_REQUIRES "libcrypto") elseif(item STREQUAL "OpenSSL::SSL") list(APPEND PC_REQUIRES "libssl") elseif(item STREQUAL "ZLIB::ZLIB") list(APPEND PC_REQUIRES "zlib") elseif(item MATCHES "^-|/") list(APPEND PC_LINKLIBS "${item}") else() list(APPEND PC_LINKLIBS "-l${item}") endif() endforeach() list(JOIN PC_LINKLIBS " " PC_LINKLIBS) list(JOIN PC_REQUIRES " " PC_REQUIRES) configure_file(packaging/libre.pc.in libre.pc @ONLY) ############################################################################## # # Install section # install(TARGETS ${RE_INSTALL_TARGETS} EXPORT libre RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Libraries LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries NAMELINK_SKIP ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/re ) install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/re COMPONENT Development ) install(EXPORT libre DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libre FILE libre-targets.cmake NAMESPACE libre:: COMPONENT Development ) if(LIBRE_BUILD_SHARED) install(TARGETS re-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} NAMELINK_ONLY COMPONENT Development ) endif() install(FILES cmake/re-config.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/re COMPONENT Development ) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/libre-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/cmake/libre-config.cmake" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/libre-config.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libre COMPONENT Development ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libre.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT Development ) ############################################################################## # # Packaging section # if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) add_subdirectory(packaging) endif() ############################################################################## # Test # add_subdirectory(test EXCLUDE_FROM_ALL) ================================================ FILE: LICENSE ================================================ Copyright (C) 2020 - 2026, Baresip Foundation (https://github.com/baresip) Copyright (c) 2010 - 2024, Alfred E. Heggestad Copyright (c) 2010 - 2020, Richard Aas Copyright (c) 2010 - 2020, Creytiv.com All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ .PHONY: build build: [ -d build ] || cmake -B build cmake --build build --parallel .PHONY: ninja ninja: [ -d build ] || cmake -B build -G Ninja make build .PHONY: release release: [ -d build ] || cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo cmake --build build --parallel .PHONY: dist dist: build cmake --install build --prefix dist .PHONY: deb deb: release cd build && cpack -G DEB .PHONY: test test: build cmake --build build --parallel -t retest build/test/retest -rv .PHONY: clean clean: @rm -Rf build dist CMakeCache.txt CMakeFiles ############################################################################### # # Documentation section # DOX_DIR=../re-dox $(DOX_DIR): @mkdir $@ $(DOX_DIR)/Doxyfile: mk/Doxyfile Makefile @cp $< $@ @perl -pi -e 's/PROJECT_NUMBER\s*=.*/PROJECT_NUMBER = $(VERSION)/' \ $(DOX_DIR)/Doxyfile .PHONY: dox: $(DOX_DIR) $(DOX_DIR)/Doxyfile @doxygen $(DOX_DIR)/Doxyfile 2>&1 | grep -v DEBUG_ ; true echo "Doxygen docs in $(DOX_DIR)" ================================================ FILE: README.md ================================================ libre README ============ libre is a Generic library for real-time communications with async IO support. - Copyright (C) 2010 - 2020 Creytiv.com - Copyright (C) 2020 - 2026 Baresip Foundation (https://github.com/baresip) ![Build](https://github.com/baresip/re/workflows/Build/badge.svg) ![ccheck](https://github.com/baresip/re/workflows/ccheck/badge.svg) ![OpenSSL no-deprecated and LibreSSL](https://github.com/baresip/re/workflows/OpenSSL%20no-deprecated%20and%20LibreSSL/badge.svg) ## Features * SIP Stack ([RFC 3261](https://tools.ietf.org/html/rfc3261)) * SDP * RTP and RTCP * SRTP and SRTCP (Secure RTP) * DNS-Client * STUN/TURN/ICE stack * BFCP * HTTP-stack with client/server * Websockets * Async I/O (select, epoll, kqueue) * UDP/TCP/TLS/DTLS transport * JSON parser * Real Time Messaging Protocol (RTMP) ## Building libre is using CMake. CMake and OpenSSL development headers must be installed before building. ### Build with debug enabled ``` $ cmake -B build $ cmake --build build -j $ sudo cmake --install build $ sudo ldconfig ``` ### Build/run tests ``` cmake -B build && cmake --build build -t retest -j build/test/retest -rv ``` On some distributions, /usr/local/lib may not be included in ld.so.conf. You can check with `grep "/usr/local/lib" /etc/ld.so.conf.d/*.conf` and add if necessary: ``` $ echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/libc.conf $ sudo ldconfig ``` ### Build with release ``` $ cmake -B build -DCMAKE_BUILD_TYPE=Release $ cmake --build build -j $ sudo cmake --install build $ sudo ldconfig ``` ### Build with clang compiler ``` $ cmake -B build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ $ cmake --build build -j $ sudo cmake --install build $ sudo ldconfig ``` ### Examples Coding examples are available from the [redemo](https://github.com/creytiv/redemo "creytiv/redemo: Demo example applications using libre") project. ## License The libre project is using the BSD license. ## Contributing Patches can sent via Github [Pull-Requests](https://github.com/baresip/re/pulls) ## Design goals * Portable POSIX source code (ISO C99 and C11 standard) * Robust, fast, low memory footprint * RFC compliance * IPv4 and IPv6 support ## Modules | Name | Status | Description | |----------|----------|------------------------------------------------| | aes | stable | AES (Advanced Encryption Standard) | | async | testing | Async module | | av1 | testing | AV1 Packetizer | | base64 | stable | Base-64 encoding/decoding functions | | bfcp | stable | The Binary Floor Control Protocol (BFCP) | | btrace | testing | Backtrace module | | conf | stable | Configuration file parser | | crc32 | stable | 32-bit CRC defined in ITU V.42 | | dbg | stable | Debug printing | | dd | testing | Dependency Descriptor | | dns | stable | DNS resolving (NAPTR, SRV, A) | | fmt | stable | Formatted printing and regular expression | | h264 | testing | H.264 packetizer | | h265 | testing | H.265 packetizer | | hash | stable | Hashmap table | | hmac | stable | HMAC: Keyed-Hashing for Message Authentication | | http | stable | HTTP parser (RFC 2616) | | httpauth | stable | HTTP-based Authentication (RFC 2617) | | ice | stable | Interactive Connectivity Establishment (ICE) | | json | stable | JavaScript Object Notation (JSON) | | list | stable | Sortable doubly-linked list handling | | main | stable | Main poll loop | | mbuf | stable | Linear memory buffers | | md5 | stable | The MD5 Message-Digest Algorithm (RFC 1321) | | mem | stable | Memory referencing | | mod | stable | Run-time module loading | | mqueue | stable | Thread-safe message queue | | msg | stable | Generic message component library | | net | stable | Networking routines | | odict | stable | Ordered Dictionary | | pcp | testing | Port Control Protocol | | rtmp | stable | Real Time Messaging Protocol | | rtp | stable | Real-time Transport Protocol | | rtpext | testing | RTP extensions | | sa | stable | Socket Address functions | | sdp | stable | Session Description Protocol | | sha | stable | Secure Hash Standard, NIST, FIPS PUB 180-1 | | sip | stable | Core SIP library | | sipevent | stable | SIP Event framework | | sipreg | stable | SIP register client | | sipsess | stable | SIP Sessions | | srtp | stable | Secure Real-time Transport Protocol (SRTP) | | stun | stable | Session Traversal Utilities for NAT (STUN) | | sys | stable | System information | | tcp | stable | TCP transport | | telev | stable | Telephony Events (RFC 4733) | | thread | testing | C11 threads (with pthread and win32 emulation) | | tls | stable | Transport Layer Security | | tmr | stable | Timer handling | | turn | stable | Obtaining Relay Addresses from STUN (TURN) | | trace | testing | Trace Helpers JSON traces (chrome://tracing) | | trice | testing | Trickle ICE | | udp | stable | UDP transport | | unixsock | testing | Unix domain sockets | | uri | stable | Generic URI library | | websock | stable | WebSocket Client and Server | legend: * *stable* - code complete; stable code and stable API * *testing* - code complete, but API might change * *unstable* - code complete but not completely tested * *development* - code is under development ## Features * [RFC 1321](https://tools.ietf.org/html/rfc1321) - The MD5 Message-Digest Algorithm * [RFC 1886](https://tools.ietf.org/html/rfc1886) - DNS Extensions to support IP version 6 * [RFC 2616](https://tools.ietf.org/html/rfc2616) - Hypertext Transfer Protocol -- HTTP/1.1 * [RFC 2617](https://tools.ietf.org/html/rfc2617) - HTTP Authentication: Basic and Digest Access Authentication * [RFC 2782](https://tools.ietf.org/html/rfc2782) - A DNS RR for Specifying the Location of Services (DNS SRV) * [RFC 2915](https://tools.ietf.org/html/rfc2915) - The Naming Authority Pointer (NAPTR) DNS Resource Record * [RFC 3261](https://tools.ietf.org/html/rfc3261) - SIP: Session Initiation Protocol * [RFC 3262](https://tools.ietf.org/html/rfc3262) - SIP Reliability of Provisional Responses * [RFC 3263](https://tools.ietf.org/html/rfc3263) - Locating SIP Servers * [RFC 3264](https://tools.ietf.org/html/rfc3264) - An Offer/Answer Model with SDP * [RFC 3265](https://tools.ietf.org/html/rfc3265) - SIP-Specific Event Notification * [RFC 3311](https://tools.ietf.org/html/rfc3311) - The SIP UPDATE Method * [RFC 3327](https://tools.ietf.org/html/rfc3327) - SIP Extension Header Field for Registering Non-Adjacent Contacts * [RFC 3428](https://tools.ietf.org/html/rfc3428) - SIP Extension for Instant Messaging * [RFC 3489](https://tools.ietf.org/html/rfc3489) - STUN - Simple Traversal of UDP Through NATs * [RFC 3515](https://tools.ietf.org/html/rfc3515) - The SIP Refer Method * [RFC 3550](https://tools.ietf.org/html/rfc3550) - RTP: A Transport Protocol for Real-Time Applications * [RFC 3551](https://tools.ietf.org/html/rfc3551) - RTP Profile for Audio and Video Conferences with Minimal Control * [RFC 3555](https://tools.ietf.org/html/rfc3555) - MIME Type Registration of RTP Payload Formats * [RFC 3556](https://tools.ietf.org/html/rfc3556) - SDP Bandwidth Modifiers for RTCP Bandwidth * [RFC 3581](https://tools.ietf.org/html/rfc3581) - An Extension to SIP for Symmetric Response Routing * [RFC 3605](https://tools.ietf.org/html/rfc3605) - RTCP attribute in SDP * [RFC 3611](https://tools.ietf.org/html/rfc3611) - RTCP Extended Reports * [RFC 3711](https://tools.ietf.org/html/rfc3711) - The Secure Real-time Transport Protocol (SRTP) * [RFC 3969](https://tools.ietf.org/html/rfc3969) - The IANA URI Parameter Registry for SIP * [RFC 3994](https://tools.ietf.org/html/rfc3994) - Indication of Message Composition for Instant Messaging * [RFC 4566](https://tools.ietf.org/html/rfc4566) - SDP: Session Description Protocol * [RFC 4582](https://tools.ietf.org/html/rfc4582) - The Binary Floor Control Protocol (BFCP) * [RFC 4582bis](https://tools.ietf.org/html/draft-ietf-bfcpbis-rfc4582bis-08) - The Binary Floor Control Protocol (BFCP) * [RFC 4585](https://tools.ietf.org/html/rfc4585) - Extended RTP Profile for RTCP-Based Feedback * [RFC 4733](https://tools.ietf.org/html/rfc4733) - RTP Payload for DTMF Digits, Telephony Tones, and Teleph. Signals * [RFC 4961](https://tools.ietf.org/html/rfc4961) - Symmetric RTP / RTP Control Protocol (RTCP) * [RFC 5104](https://tools.ietf.org/html/rfc5104) - Codec Control Messages in AVPF * [RFC 5118](https://tools.ietf.org/html/rfc5118) - SIP Torture Test Messages for IPv6 * [RFC 5245](https://tools.ietf.org/html/rfc5245) - Interactive Connectivity Establishment (ICE) * [RFC 5246](https://tools.ietf.org/html/rfc5246) - The TLS Protocol Version 1.2 * [RFC 5389](https://tools.ietf.org/html/rfc5389) - Session Traversal Utilities for NAT (STUN) * [RFC 5626](https://tools.ietf.org/html/rfc5626) - Managing Client-Initiated Connections in SIP * [RFC 5761](https://tools.ietf.org/html/rfc5761) - Multiplexing RTP Data and Control Packets on a Single Port * [RFC 5766](https://tools.ietf.org/html/rfc5766) - Traversal Using Relays around NAT (TURN) * [RFC 5768](https://tools.ietf.org/html/rfc5768) - Indicating Support for ICE in SIP * [RFC 5769](https://tools.ietf.org/html/rfc5769) - Test vectors for STUN * [RFC 6026](https://tools.ietf.org/html/rfc6026) - Correct Transaction Handling for 2xx Resp. to SIP INVITE Requests * [RFC 6156](https://tools.ietf.org/html/rfc6156) - TURN Extension for IPv6 * [RFC 6188](https://tools.ietf.org/html/rfc6188) - The Use of AES-192 and AES-256 in Secure RTP * [RFC 6455](https://tools.ietf.org/html/rfc6455) - The WebSocket Protocol * [RFC 7159](https://tools.ietf.org/html/rfc7159) - JavaScript Object Notation (JSON) * [RFC 7350](https://tools.ietf.org/html/rfc7350) - DTLS as Transport for STUN * [RFC 7616](https://tools.ietf.org/html/rfc7616) - HTTP Digest Access Authentication * [RFC 7714](https://tools.ietf.org/html/rfc7714) - AES-GCM Authenticated Encryption in SRTP * [AV1-RTP](https://aomediacodec.github.io/av1-rtp-spec/) - RTP Payload Format For AV1 ## Supported platforms | System | Support type | Supported versions | Notes | |---|---|---|---| | Linux | Tier 1 | glibc >= 2.31 | | | Linux | Tier 1 | musl >= 1.2 | | | macOS | Tier 1 | macOS >= 10.10 | | | Windows | Tier 1 | >= Windows 10 | MinGW-w64, >= VS 2022 | | Android | Tier 2 | Android 8 (API Level 26)| | | iOS | Tier 2 | | | | FreeBSD | Tier 2 | >= 12 | | | OpenBSD | Tier 2 | >= 7.4 | | | Linux | Tier 2 | uClibc | | ### Known bugs macOS clang-1600.0.26.3 (Xcode 16.0) and clang-1600.0.26.4 (Xcode 16.1) have a optimization bug: - https://github.com/baresip/re/pull/1399 - https://github.com/baresip/baresip/issues/3240 ### Support types * **Tier 1**: Officially supported and tested with CI. Any contributed patch MUST NOT break such systems. * **Tier 2**: Officially supported, but not necessarily tested with CI. These systems are maintained to the best of collaborators ability, without being a top priority. * **Tier 3**: Community maintained. These systems may inadvertently break and the community and interested parties are expected to help with the maintenance. ### Supported versions of C Standard library * Android bionic * BSD libc * GNU C Library (glibc) * Windows C Run-Time Libraries (CRT) * uClibc * musl ### Supported compilers: * gcc 9 or later * MSVC 2022 * clang 9.x or later ### Supported versions of OpenSSL * OpenSSL version 3.x.x and 4.x.x * LibreSSL version 4.x ## Coding guidelines * Use enum for constants where appropriate * Use const as much as possible (where appropriate) * Use C99 data types (intN_t, uintN_t, bool) * Hide data-types in .c files where possible (use struct foo) * Avoid malloc/free, use mem_alloc/mem_deref instead * CVS/svn/git tags are NOT allowed in the code! * Avoid bit-fields in structs which are not portable * Use dummy handlers for timing-critical callbacks * return err, return alloced objects as pointer-pointers * in allocating functions, first arg is always double pointer * Use POSIX error-codes; EINVAL for invalid args, EBADMSG for parse errors and EPROTO for protocol errors ## Transport protocols | | TCP | UDP | TLS | DTLS| |:--------|:---:|:---:|:---:|:---:| | BFCP | - | yes | - | - | | DNS | yes | yes | - | - | | HTTP | yes | n/a | yes | n/a | | ICE | - | yes | - | - | | RTP | - | yes | - | - | | RTCP | - | yes | - | - | | RTMP | yes | - | yes | - | | SIP | yes | yes | yes | - | | STUN | yes | yes | yes | yes | | TURN | yes | yes | yes | yes | | WEBSOCK | yes | n/a | yes | n/a | ## Related projects * [librem](https://github.com/baresip/rem) * [retest](https://github.com/baresip/retest) * [baresip](https://github.com/baresip/baresip) ## References https://github.com/creytiv/re ================================================ FILE: cmake/FindMBEDTLS.cmake ================================================ find_path(MBEDTLS_INCLUDE_DIR NAMES mbedtls/ssl.h mbedtls/md.h mbedtls/md5.h mbedtls/error.h mbedtls/sha1.h mbedtls/sha256.h HINTS "${MBEDTLS_INCLUDE_DIRS}" "${MBEDTLS_HINTS}/include" PATHS /usr/local/include /usr/include ) find_library(MBEDTLS_LIBRARY NAMES mbedtls mbedx509 mbedcrypto HINTS "${MBEDTLS_LIBRARY_DIRS}" "${MBEDTLS_HINTS}/lib" PATHS /usr/local/lib /usr/lib ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MBEDTLS DEFAULT_MSG MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARY) if(MBEDTLS_FOUND) set( MBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR} ) set( MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ) else() set( MBEDTLS_INCLUDE_DIRS ) set( MBEDTLS_LIBRARIES ) endif() mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARIES) ================================================ FILE: cmake/libre-config.cmake ================================================ if("@LIBRE_BUILD_STATIC@") include(CMakeFindDependencyMacro) find_dependency(Threads) if("@USE_OPENSSL@") find_dependency(OpenSSL) endif() if("@ZLIB_FOUND@") find_dependency(ZLIB) endif() endif() include("${CMAKE_CURRENT_LIST_DIR}/libre-targets.cmake") # convenience target libre::libre for uniform usage if(NOT TARGET libre::libre) if(TARGET libre::re_shared AND (BUILD_SHARED_LIBS OR NOT TARGET libre::re)) add_library(libre::libre INTERFACE IMPORTED) set_target_properties(libre::libre PROPERTIES INTERFACE_LINK_LIBRARIES libre::re_shared) elseif(TARGET libre::re AND (NOT BUILD_SHARED_LIBS OR NOT TARGET libre::re_shared)) add_library(libre::libre INTERFACE IMPORTED) set_target_properties(libre::libre PROPERTIES INTERFACE_LINK_LIBRARIES libre::re) endif() endif() ================================================ FILE: cmake/re-config.cmake ================================================ # # re-config.cmake # include(CheckIncludeFile) include(CheckFunctionExists) include(CheckSymbolExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) option(USE_MBEDTLS "Enable MbedTLS" OFF) option(USE_TLS1_3_PHA "Enable TLS 1.3 Post-Handshake Auth" ON) find_package(Backtrace) find_package(Threads REQUIRED) find_package(ZLIB) if(USE_MBEDTLS) find_package(MBEDTLS) else() find_package(OpenSSL "1.1.1") endif() option(USE_OPENSSL "Enable OpenSSL" ${OPENSSL_FOUND}) option(USE_UNIXSOCK "Enable Unix Domain Sockets" ON) option(USE_TRACE "Enable Tracing helpers" OFF) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Debug' as none was specified.") set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build." FORCE) endif() check_symbol_exists(LIBRESSL_VERSION_NUMBER "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" HAVE_LIBRESSL) if(USE_TLS1_3_PHA AND NOT HAVE_LIBRESSL AND NOT USE_MBEDTLS AND OPENSSL_FOUND) list(APPEND RE_DEFINITIONS HAVE_TLS1_3_POST_HANDSHAKE_AUTH) endif() check_symbol_exists("arc4random" "stdlib.h" HAVE_ARC4RANDOM) if(HAVE_ARC4RANDOM) list(APPEND RE_DEFINITIONS HAVE_ARC4RANDOM) endif() if(ZLIB_FOUND) list(APPEND RE_DEFINITIONS USE_ZLIB) endif() check_include_file(syslog.h HAVE_SYSLOG_H) if(HAVE_SYSLOG_H) list(APPEND RE_DEFINITIONS HAVE_SYSLOG) endif() check_include_file(getopt.h HAVE_GETOPT_H) if(HAVE_GETOPT_H) list(APPEND RE_DEFINITIONS HAVE_GETOPT) endif() check_include_file(unistd.h HAVE_UNISTD_H) if(HAVE_UNISTD_H) list(APPEND RE_DEFINITIONS HAVE_UNISTD_H) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") check_symbol_exists(res_init resolv.h HAVE_RESOLV) else() check_symbol_exists(res_ninit resolv.h HAVE_RESOLV) endif() if(HAVE_RESOLV AND ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") list(APPEND RE_DEFINITIONS HAVE_RESOLV) set(RESOLV_LIBRARY) # Provided by libc elseif(HAVE_RESOLV) set(RESOLV_LIBRARY resolv) list(APPEND RE_DEFINITIONS HAVE_RESOLV) else() set(RESOLV_LIBRARY) endif() if(Backtrace_FOUND) list(APPEND RE_DEFINITIONS HAVE_EXECINFO) else() set(Backtrace_LIBRARIES) endif() check_function_exists(thrd_create HAVE_THREADS_FUN) check_include_file(threads.h HAVE_THREADS_H) if(HAVE_THREADS_FUN AND HAVE_THREADS_H) set(HAVE_THREADS ON CACHE BOOL "HAVE C11 Threads") endif() if(HAVE_THREADS) list(APPEND RE_DEFINITIONS HAVE_THREADS) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") check_function_exists(accept4 HAVE_ACCEPT4) if(HAVE_ACCEPT4) list(APPEND RE_DEFINITIONS HAVE_ACCEPT4) endif() endif() if(CMAKE_USE_PTHREADS_INIT) list(APPEND RE_DEFINITIONS HAVE_PTHREAD) set(HAVE_PTHREAD ON) endif() if(UNIX) check_symbol_exists(epoll_create "sys/epoll.h" HAVE_EPOLL) if(HAVE_EPOLL) list(APPEND RE_DEFINITIONS HAVE_EPOLL) endif() check_symbol_exists(kqueue "sys/types.h;sys/event.h" HAVE_KQUEUE) if(HAVE_KQUEUE) list(APPEND RE_DEFINITIONS HAVE_KQUEUE) endif() endif() check_include_file(sys/prctl.h HAVE_PRCTL) if(HAVE_PRCTL) list(APPEND RE_DEFINITIONS HAVE_PRCTL) endif() list(APPEND RE_DEFINITIONS HAVE_ATOMIC HAVE_SELECT ) if(UNIX) if(ANDROID) string(REPLACE "android-" "" ANDROID_API_LEVEL ${ANDROID_PLATFORM}) if(ANDROID_API_LEVEL GREATER_EQUAL 24) set(HAVE_GETIFADDRS ON CACHE BOOL "" FORCE) endif() else() check_include_file(ifaddrs.h HAVE_GETIFADDRS) endif() if(HAVE_GETIFADDRS) list(APPEND RE_DEFINITIONS HAVE_GETIFADDRS) endif() endif() if(UNIX) list(APPEND RE_DEFINITIONS HAVE_PWD_H HAVE_SETRLIMIT HAVE_STRERROR_R HAVE_STRINGS_H HAVE_SYS_TIME_H HAVE_UNAME HAVE_SELECT_H HAVE_SIGNAL HAVE_FORK ) if(NOT IOS) list(APPEND RE_DEFINITIONS HAVE_ROUTE_LIST) endif() endif() if(MSVC) list(APPEND RE_DEFINITIONS HAVE_IO_H _CRT_SECURE_NO_WARNINGS ) endif() if(WIN32) list(APPEND RE_DEFINITIONS WIN32 _WIN32_WINNT=0x0A00 ) unset(CMAKE_EXTRA_INCLUDE_FILES) set(CMAKE_EXTRA_INCLUDE_FILES "winsock2.h;qos2.h") check_type_size("QOS_FLOWID" HAVE_QOS_FLOWID BUILTIN_TYPES_ONLY) check_type_size("PQOS_FLOWID" HAVE_PQOS_FLOWID BUILTIN_TYPES_ONLY) unset(CMAKE_EXTRA_INCLUDE_FILES) if(HAVE_QOS_FLOWID) list(APPEND RE_DEFINITIONS HAVE_QOS_FLOWID) endif() if(HAVE_PQOS_FLOWID) list(APPEND RE_DEFINITIONS HAVE_PQOS_FLOWID) endif() endif() if(USE_OPENSSL) list(APPEND RE_DEFINITIONS USE_DTLS USE_OPENSSL USE_OPENSSL_AES USE_OPENSSL_HMAC USE_TLS ) endif() if(USE_MBEDTLS) list(APPEND RE_DEFINITIONS USE_MBEDTLS ) endif() if(USE_UNIXSOCK) list(APPEND RE_DEFINITIONS HAVE_UNIXSOCK=1 ) else() list(APPEND RE_DEFINITIONS HAVE_UNIXSOCK=0 ) endif() if(USE_TRACE) list(APPEND RE_DEFINITIONS RE_TRACE_ENABLED ) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") list(APPEND RE_DEFINITIONS DARWIN) elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS") list(APPEND RE_DEFINITIONS DARWIN) elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") list(APPEND RE_DEFINITIONS FREEBSD) elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") list(APPEND RE_DEFINITIONS OPENBSD) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") list(APPEND RE_DEFINITIONS LINUX) endif() list(APPEND RE_DEFINITIONS ARCH="${CMAKE_SYSTEM_PROCESSOR}" OS="${CMAKE_SYSTEM_NAME}" $<$>:RELEASE> ) if(NOT ${CMAKE_BUILD_TYPE} MATCHES "[Rr]el") if(Backtrace_FOUND) set(CMAKE_ENABLE_EXPORTS ON) endif() endif() ############################################################################## # # Linking LIBS # set(RE_LIBS Threads::Threads ${RESOLV_LIBRARY}) if(BACKTRACE_FOUND) list(APPEND RE_LIBS ${Backtrace_LIBRARIES}) endif() if(ZLIB_FOUND) list(APPEND RE_LIBS ZLIB::ZLIB) endif() if(USE_OPENSSL) list(APPEND RE_LIBS OpenSSL::SSL OpenSSL::Crypto) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") list(APPEND RE_LIBS "-framework SystemConfiguration" "-framework CoreFoundation" ) endif() if(WIN32) list(APPEND RE_LIBS qwave iphlpapi wsock32 ws2_32 dbghelp ) else() list(APPEND RE_LIBS m) endif() if(UNIX) list(APPEND RE_LIBS ${CMAKE_DL_LIBS} ) endif() ############################################################################## # # Testing Atomic # enable_language(CXX) set(ATOMIC_TEST_CODE " #include #include std::atomic n8 (0); // riscv64 std::atomic n64 (0); // armel, mipsel, powerpc int main() { ++n8; ++n64; return 0; }") check_cxx_source_compiles("${ATOMIC_TEST_CODE}" atomic_test) if(NOT atomic_test) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} atomic) check_cxx_source_compiles("${ATOMIC_TEST_CODE}" atomic_test_lib) if(NOT atomic_test_lib) message(FATAL_ERROR "No builtin or libatomic support") else() list(APPEND RE_LIBS atomic) endif() endif() ================================================ FILE: cmake/sanitizer.cmake ================================================ if(USE_SANITIZER STREQUAL "address") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") elseif(USE_SANITIZER STREQUAL "thread") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") elseif(USE_SANITIZER STREQUAL "undefined") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") elseif(USE_SANITIZER STREQUAL "memory") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memory") endif() ================================================ FILE: docs/ChangeLog ================================================ 2019-09-07 Alfred E. Heggestad * Version 0.6.1 Aleksei (1): Update MSVS project (Remove uri_cmp) (#209) Alfred E. Heggestad (10): rtmp: update timestamp after complete packet (#172) rtmp: add rtmp_meta() to send metadata on stream (#173) remove gai_strerror stub (#174) rtmp: add function to set handlers (#180) rtmp: proper handling of extended timestamps (#184) docs: update copyright year (#186) debian: fix some warnings and add version to SONAME (#192) uri: remove uri_cmp() (#199) rtmp: add rtmps (tls) support (#195) Update README.md (#205) Andrey Semashev (1): Fix libdir in pkgconfig (#185) Lennart Grahl (1): Raise FD_EXCEPT on EPOLLHUP (fixes closed pipes) (#159) Richard Aas (9): debian release 0.6.0-1 debian release 0.6.0-2 debian release 0.6.0-3 dns: fix dname decode buffer checks (#197) regex: fix character buffer check (#198) Debian release 0.6.0-4 tls: don't close connection on SSL_ERROR_ZERO_RETURN error (#217) DNS TXT resource record support (#219) debian release 0.6.0-6 Steffen Vogel (1): rpm: add missing file to %install section (#178) 2018-11-24 Alfred E. Heggestad * Version 0.6.0 * Project URL: https://github.com/creytiv/re * build: add major,minor,patch versions to CFLAGS * odict: add high-level odict helper functions * rtmp: new module for Real Time Messaging Protocol (RTMP) * uri: add uri_decode_hostport 2018-09-01 Alfred E. Heggestad * Version 0.5.9 * Project URL: https://github.com/creytiv/re * build: Added support for 64-bit MINGW (#131) (thanks Alexander Ushakov) fixed inline issue when compiling VS as C++ (#143) (thanks TheSil) * jbuf: zero out jbuf_stat on jbuf flush (#147) (thanks Christian Spielberger) * net: remove net_conn api (old and unused) (#145) fix bug in net_if_getname (#144) * sip: get local TCP address in establish handler (#146) * tls: add AES-GCM to DTLS-SRTP (#141) 2018-04-20 Alfred E. Heggestad * Version 0.5.8 * Project URL: https://github.com/creytiv/re * build: update win32 files (thanks Encamy) * aes: add support for AES-GCM (Galois Counter Mode) * fmt: json/utf8: fix unescaping of unicode code points (#127) add utf8_byteseq * mqueue: set non-blocking mode for read/write file descriptors (#122) * srtp: add support for AES-GCM cipher suite (RFC 7714) 2018-01-12 Alfred E. Heggestad * Version 0.5.7 * Project URL: https://github.com/creytiv/re * build: remove support for Cygwin (#95) remove support for splint (#96) * mem: add secure memory functions (#102) * net: larger buffer for net_if_list (#100) * sipreg: add from_name (Display Name) (#104) * tls: use per connection bio_method (fixes issue #92) (#93) 2017-11-06 Alfred E. Heggestad * Version 0.5.6 * Project URL: https://github.com/creytiv/re * build: Update ar flags; use deterministic mode (#79) * http: added support for chunked transfer encoding (#90) * ice: Added functions to get selected candidates. (#72) (thanks Joachim Bauch) * json: improved performance for mypower10 (#88) (thanks Chris Owen) * mqueue: Pack struct of mqueue messages. (#62) (thanks Joachim Bauch) * odict: use int instead of enum to avoid vararg promotion (#81) * tls: add dtls_recv_packet() (#89) 2017-09-05 Alfred E. Heggestad * Version 0.5.5 * Project URL: https://github.com/creytiv/re * ice: move gathering to application * mod: add accessor function to module list * sipreg: Added function sipreg_laddr() * sys: optimize rand_str() and rand_char() 2017-06-24 Alfred E. Heggestad * Version 0.5.4 * Project URL: https://github.com/creytiv/re * rtp: add extension bit to the api 2017-05-13 Alfred E. Heggestad * Version 0.5.3 * Project URL: https://github.com/creytiv/re * build: upgrade windows project to VS2015 (thanks Mikhail Barg) makefile improvements (thanks Lennart Grahl) * ice: remove session object "struct ice" * telev: add telev_set_srate (thanks Jan Hoffmann) 2017-04-08 Alfred E. Heggestad * Version 0.5.2 * Project URL: https://github.com/creytiv/re * build: add Debian/kFreeBSD+Hurd (thanks Vasudev Kamath) * ice: make enum ice_role type public * main: fix build for Solaris 11 * srtcp: use unsigned for encrypted bit * tls: add tls_openssl_context() accessor function 2017-02-04 Alfred E. Heggestad * Version 0.5.1 * Project URL: https://github.com/creytiv/re * fmt: print directly to stream using handler (#38) * http: HTTP client improvements (#36) - http client connection reuse - retry failed requests using fresh connections - Handle Connection: close response header 2016-11-25 Alfred E. Heggestad * Version 0.5.0 * Project URL: https://github.com/creytiv/re * build: add Dragonfly BSD (thanks Dmitrij D. Czarkoff) remove support for Symbian OS * aes: add support for OpenSSL version 1.1.0 * dns: dns/resolv cleanup (#11) (thanks Dmitrij D. Czarkoff) * hmac: add support for OpenSSL version 1.1.0 * main: remove support for ActiveScheduler (SymbianOS) * tls: add support for OpenSSL version 1.1.0 add tls_set_certificate_pem() add tls_set_certificate_der() add dtls_peer() add dtls_set_peer() add tls_flush_error to dump openssl errors (thanks to Lennart Grahl) * udp: add udp_helper_find() 2016-06-24 Alfred E. Heggestad * Version 0.4.17 * build: add USE_OPENSSL_AES and USE_OPENSSL_HMAC * dns: add key to dns_rrlist_sort() add dns_rrlist_sort_addr * tls: add tls_set_ciphers() add tls_set_servername() * sip: fix for stateless SIP requests sort DNS RR entries by a fixed key * stun: fix bug with 8-bit and 16-bit attributes on certain platforms, such as MIPS 2016-04-27 Alfred E. Heggestad * Version 0.4.16 * build: fix warnings about DEFAULT_SOURCE with new glibc * lock: fix debian build without HAVE_PTHREAD_RWLOCK * rand: add arc4random (based on patch from Dmitrij D. Czarkoff) * rtcp: adjust mbuf positions for RTCP_PSFB_AFB decoding * tls: add tls_cipher_name() add dtls_set_mtu() 2016-02-06 Alfred E. Heggestad * Version 0.4.15 * build: fix warnings about DEFAULT_SOURCE with new glibc fix compile error for mingw32 (thanks Dmitrij D. Czarkoff) * aes: handle buffers with zero length * dns: add support for multiple DNS name-servers on Android * hmac: add support for HMAC-SHA256 * rtp: fix packet-loss calc when first packet has seq=0 * srtp: split error code in ENOSR and ENOMEM * stun: keepalive: handle method BINDING only * telev: add a maximum queue size * uri: fix a potential read buffer overflow 2015-10-24 Alfred E. Heggestad * Version 0.4.14 * New modules: json and odict * build: add pkg-config file (thanks to William King) * re_types: added a portable __REFUNC__ * fmt: add utf8_encode/decode, used by JSON module * hash: add hash_fast() function * json: new JavaScript Object Notation (JSON) module * main: fix order of kqueue setting events (WRITE,READ) * odict: new Ordered Dictionary module * sip: reverse order of transport enumeration for SRV-records * udp,tcp,net: add __USE_XOPEN2K (thanks Dmitrij D. Czarkoff) 2015-07-01 Alfred E. Heggestad * Version 0.4.13 * aes: added support for CommonCrypto API * fmt: pl_float() handles negative numbers now * hmac: added support for CommonCrypto API * main: added support for async I/O method `kqueue' this is now the default on platforms like OSX/iOS, FreeBSD, NetBSD and OpenBSD. * mem: added mem_reallocarray(), inspired by OpenBSD * net: added net_default_gateway_get() * tls: use RSA_generate_key_ex() instead of deprecated functions 2015-03-16 Alfred E. Heggestad * Version 0.4.12 * ice: added ice_ prefix to some functions and types fix bug in priority calculations (thanks to Daniel Ma) * mqueue: fix bug with leaking sockets on Windows-32 * rtp: fix bug with RTCP timestamp calculation * sip: export sip_transp_laddr() * tls: added more TLS methods 2014-12-09 Alfred E. Heggestad * Version 0.4.11 * build: export USE_TLS and USE_DTLS flags in re.mk makefile detect sysctl.h and epoll.h for multi-arch platforms dont use libresolv for openbsd * main: check that maxfds is less than FD_SETSIZE (for select method) * dtls: added udp-socket accessor and function to set handlers * stun: added support for DTLS-transport added doxygen comments * tls: added function to set certificate from a string * turn: added support for DTLS-transport 2014-10-19 Alfred E. Heggestad * Version 0.4.10 * dns: added support for using multi-threaded libresolv (thanks to Thomas Klausner) (thanks to Dmitrij D. Czarkoff for testing on OpenBSD) * dtls: added support for sending DTLS over e.g. TURN (this is done by adding 4 bytes of headroom in the packet) * ice: added ice_set_conf() continue checklist if send fails (thanks to SnakE) * mbuf: added mbuf_shift() * sdp: added sdp_media_session_rattr() added extmap decoding RFC 5285 (thanks to Jose Carlos Pujol) * sip: added struct sip_contact and related functions * sipevent: added support for URI in contact-user, used for GRUU (thanks to Juha Heinanen) * sipreg: added "gruu" to list of Supported extensions * sipsess: added support for URI in contact-user, used for GRUU (thanks to Juha Heinanen) 2014-06-18 Alfred E. Heggestad * Version 0.4.9 * aes: clear openssl error queue in error cases * bfcp: disabled support for DTLS-transport * hmac: clear openssl error queue in error cases * http: make response parsing a bit more robust * main: make use of openssl's multi-threading API * rtcp: added Round-Trip Time (RTT) field to struct rtcp_stats fix some rounding errors * sa: added padding buffer to struct sa union * sdp: improved handling of unsupported transport protocols and sub-sequence offer/answer exchanges * srtp: added support for Secure Real-time Transport Protocol (SRTP) (RFC 3711 and RFC 6188) * sys: rand -- clear openssl error queue in error cases * tls: added support for generating self-signed certificates added support for the SRTP-extension using openssl clear openssl error queue in error cases improved DTLS api 2014-04-11 Alfred E. Heggestad * Version 0.4.8 * build: added support for Apple in cmake * debian: update package * aes: added AES (Advanced Encryption Standard) wrapper * hmac: added a stateful HMAC wrapper * http: added a HTTP client * ice: minor API improvements * msg: added a Generic message component library (shared by SIP module and HTTP module) * sdp: added sdp_media_laddr() to get local transport address * sip: change struct sip_msg to use new struct msg_ctype fixed a bug in parsing of Via headers * sipsess: added sipsess_set_close_headers() to set any additional SIP headers for BYE or BYE-response * net: fixed a bug in net_rt_list() for darwin * websock: added WebSocket Protocol (RFC 6455) 2014-01-05 Alfred E. Heggestad * Version 0.4.7 * build: added support for LLVM clang compiler * dns: added support for getting Android nameserver address * ice: minor debug tuning * sipsess: only send INFO when dialog is established 2013-11-12 Alfred E. Heggestad * Version 0.4.6 * bfcp: fix bitwise operator for bool (thanks Tomasz Ostrowski) * dns: do not connect the UDP socket * ice: fix deref of NULL-pointer (thanks Tomasz Ostrowski) * rtp: add support for RTCP AFB (Application-layer Feedback) make RTCP decoding more robust * udp: udp_connect() -- add peer address add udp_error_handler_set() 2013-10-03 Alfred E. Heggestad * Version 0.4.5 * udp: add functions for joining/leaving multicast groups * fmt: re_regex() fix va_end robustness * rtp: set sequence number in range 0-32767 * sa: sa_print_addr() fix build when HAVE_INET6 is not set 2013-08-27 Alfred E. Heggestad * Version 0.4.4 * base64: added base64_print() * http: added HTTP (Hypertext Transfer Protocol) parser * ice: cleanup and minor bug fixes * main: added external mutex for re_main() loop * sdp: added sdp_media_set_alt_protos() added sdp_media_proto() * stun: make API compatible with C++ fix endianess-bug in STUN attributes * sys: sys_rel_get() detect new kernels * tls: fingerprint: add SHA-256 proper error handling, call ERR_clear_error() 2013-05-05 Alfred E. Heggestad * Version 0.4.3 * bfcp: added udp * dns: added doxygen comments * fmt: added str_cmp() * mbuf: make mbuf_get_left() and mbuf_get_space() more robust added mbuf_fill() * mod: fixed a bug in mod_find() * mqueue: move handler to mqueue_alloc() * sa: fix building on some Windows platforms * sdp: added sdp_session_lbandwidth() added sdp_media_set_fmt_ignore() * stun: make stun_msg_vencode() public unlink element from attribute-list in destructor * tcp: make fd handling more robust * tls: added tls_get_remote_fingerprint() 2012-08-10 Alfred E. Heggestad * Version 0.4.2 * added debian build * build: fix building for Ubuntu 12.04 * re_types: increase ERRNO values * fmt: re_printf() add support for %m to print errno description added str_error() * hash: added hash_clear() * list: added list_clear() * net: added net_if_getlinklocal() * rtp: added rtcp_set_srate_tx/rx() rtcp_msg_print(): add all types * sa: added sa_print_addr() * sdp: added media encode handler sdp_format_add(): added fmtp encode handler (breaks API) * sip: handle merged SIP requests (482 Loop Detected) added doxygen comments * sipevent: fix bug in handler argument * sys: added sys_username() added fs_mkdir() and fs_gethome() * tcp: added tcp_conn_txqsz() fix enqueue buffer size handle scopeid for IPv6 linklocal * tmr: added tmr_status %H handler * udp: handle scopeid for IPv6 linklocal 2012-04-21 Alfred E. Heggestad * Version 0.4.1 * updated doxygen comments for sdp and tls * dns: dnsc_srv_set: copy DNS servers to fixed-size array * fmt: added str_isset() added fmt_param_exists() * rtp: fix lock protection of RTCP txstat during read * sdp: sdp_media_align_formats: move unsupported codecs to end of list * sip: limit startline to max 8192 bytes limit tcp buffersize to max 65536 bytes * tcp: limit the size of the tcp send queue 2011-12-25 Alfred E. Heggestad * Version 0.4.0 * updated doxygen comments * build: add support for CMake (thanks to Stefan Radomski) clean up OS and ARCH detection * dns: fix potential infinite loop in dname decode * sip: change struct sip_via transp to enum sip_transp (breaks API) added sip_transp_isladdr() and sip_transp_port() added sip_dialog_fork(), sip_dialog_lseq(), sip_dialog_established(), sip_dialog_cmp_half() * sipevent: new module for SIP Event framework (RFC 3265, RFC 3515) * sys: add portable sys_usleep() and sys_msleep() * tcp: add tcp_send_helper() * tls: add support for DTLSv1 (Datagram TLS) tls_alloc: add tls_method and layer (breaks API) tls_tcp: use custom BIO to send data * tmr: optimize tmr_start() where delay == 0 * turn: add stun_msg to turnc handler (breaks API) * udp: add udp_send_helper() 2011-09-07 Alfred E. Heggestad * Version 0.3.0 * build support for native mingw32 (thanks to Michael Erskine) * bfcp: new module for The Binary Floor Control Protocol (RFC 4582) * g711: module moved to librem * sipreg: fix a bug in failwait() calculation * stun: add support for STUNS (secure STUN) * tcp: added tcp_set_handlers() * turn: added send/recv functions 2011-05-20 Alfred E. Heggestad * Version 0.2.0 * updated doxygen comments * conf: added conf_get_bool() * dns: fixed a bug in get_resolv_dns() * fmt: added pl_x64() pl_float() fmt_gmtime() * hash: added hash_valid_size() * httpauth: clean up API * ice: many improvements and bugfixes * main: fix a bug if re_main() fails * mbuf: added mbuf_debug() * natbd: fixed some race conditions and memory leaks * rtp: added rtcp_enable_mux() (RFC 5761; RTP and RTCP multiplexing) * sdp: fixed setting RTCP port if RTP port is zero * sip: added support for SIP Outbound (RFC 5626) added sip_msg_hdr_count() sip_msg_xhdr_count() added sip_msg_hdr_has_value() sip_msg_xhdr_has_value() added sip_auth_reset() handle multiple authenticate headers with equal realm value fixed a bug with loose-routing in Route header fixed decoding of Via header * sipreg: added support for SIP Outbound (breaks API compatibility) * sipsess: fix a bug in sipsess_reject() if fmt is NULL * tcp: update tcp_register_helper() (breaks API) * tmr: removed tmrl from 'struct tmr' (breaks ABI) added tmr_isrunning() * udp: update udp_register_helper() (breaks API) * uri: fix optional username in uri_decode() 2010-11-05 Alfred E. Heggestad * Version 0.1.0 * Initial Release ================================================ FILE: docs/TODO ================================================ TODO ------------------------------------------------------------------------------- Version v0.x.y tmr: scaling using binary heap or hash ------------------------------------------------------------------------------- ================================================ FILE: docs/main.dox ================================================ /** * \mainpage libre Development Documentation * * Development documentation for libre * * * \include{doc} README.md * * * \section modules modules */ ================================================ FILE: include/re.h ================================================ /** * @file re.h Wrapper for all header files * * Copyright (C) 2010 Creytiv.com */ #ifndef RE_H__ #define RE_H__ #ifdef __cplusplus extern "C" { #endif /* Basic types */ #include "re_types.h" #include "re_fmt.h" #include "re_mbuf.h" #include "re_msg.h" #include "re_list.h" #include "re_sa.h" /* Library modules */ #include "re_aes.h" #include "re_async.h" #include "re_base64.h" #include "re_bfcp.h" #include "re_btrace.h" #include "re_conf.h" #include "re_convert.h" #include "re_crc32.h" #include "re_dns.h" #include "re_h264.h" #include "re_hash.h" #include "re_hmac.h" #include "re_http.h" #include "re_httpauth.h" #include "re_ice.h" #include "re_net.h" #include "re_main.h" #include "re_md5.h" #include "re_mem.h" #include "re_mod.h" #include "re_mqueue.h" #include "re_odict.h" #include "re_json.h" #include "re_rtmp.h" #include "re_rtp.h" #include "re_rtpext.h" #include "re_sdp.h" #include "re_uri.h" #include "re_sip.h" #include "re_sipevent.h" #include "re_sipreg.h" #include "re_sipsess.h" #include "re_stun.h" #include "re_srtp.h" #include "re_sys.h" #include "re_tcp.h" #include "re_telev.h" #include "re_thread.h" #include "re_tmr.h" #include "re_trace.h" #include "re_tls.h" #include "re_turn.h" #include "re_udp.h" #include "re_unixsock.h" #include "re_websock.h" #include "re_shim.h" #include "re_trice.h" #include "re_pcp.h" #ifdef __cplusplus } #endif #endif ================================================ FILE: include/re_aes.h ================================================ /** * @file re_aes.h Interface to AES (Advanced Encryption Standard) * * Copyright (C) 2010 Creytiv.com */ #ifndef AES_BLOCK_SIZE #define AES_BLOCK_SIZE 16 #endif /** AES mode */ enum aes_mode { AES_MODE_CTR, /**< AES Counter mode (CTR) */ AES_MODE_GCM, /**< AES Galois Counter Mode (GCM) */ }; struct aes; int aes_alloc(struct aes **stp, enum aes_mode mode, const uint8_t *key, size_t key_bits, const uint8_t *iv); void aes_set_iv(struct aes *aes, const uint8_t *iv); int aes_encr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len); int aes_decr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len); int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen); int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen); ================================================ FILE: include/re_async.h ================================================ /** * @file re_async.h async * * Copyright (C) 2022 Sebastian Reimers */ #ifndef RE_H_ASYNC__ #define RE_H_ASYNC__ struct re_async; typedef int(re_async_work_h)(void *arg); typedef void(re_async_h)(int err, void *arg); int re_async_alloc(struct re_async **asyncp, uint16_t workers); int re_async(struct re_async *a, intptr_t id, re_async_work_h *workh, re_async_h *cb, void *arg); void re_async_cancel(struct re_async *async, intptr_t id); #endif ================================================ FILE: include/re_atomic.h ================================================ /** * @file re_atomic.h Atomic support * * Copyright (C) 2022 Sebastian Reimers */ #ifndef RE_H_ATOMIC__ #define RE_H_ATOMIC__ /* C11 */ #if defined(HAVE_ATOMIC) && __STDC_VERSION__ >= 201112L && \ !defined(__STDC_NO_ATOMICS__) #include #define RE_ATOMIC _Atomic #define RE_ATOMIC_BOOL_LOCK_FREE ATOMIC_BOOL_LOCK_FREE #define RE_ATOMIC_CHAR_LOCK_FREE ATOMIC_CHAR_LOCK_FREE #define RE_ATOMIC_WCHAR_T_LOCK_FREE ATOMIC_WCHAR_T_LOCK_FREE #define RE_ATOMIC_SHORT_LOCK_FREE ATOMIC_SHORT_LOCK_FREE #define RE_ATOMIC_INT_LOCK_FREE ATOMIC_INT_LOCK_FREE #define RE_ATOMIC_LONG_LOCK_FREE ATOMIC_LONG_LOCK_FREE #define RE_ATOMIC_LLONG_LOCK_FREE ATOMIC_LLONG_LOCK_FREE #define RE_ATOMIC_POINTER_LOCK_FREE ATOMIC_POINTER_LOCK_FREE #define re_memory_order_relaxed memory_order_relaxed #define re_memory_order_acquire memory_order_acquire #define re_memory_order_release memory_order_release #define re_memory_order_acq_rel memory_order_acq_rel #define re_memory_order_seq_cst memory_order_seq_cst #define re_atomic_store(_a, _v, _mo) \ atomic_store_explicit(_a, _v, _mo) #define re_atomic_load(_a, _mo) \ atomic_load_explicit(_a, _mo) #define re_atomic_exchange(_a, _v, _mo) \ atomic_exchange_explicit(_a, _v, _mo) #define re_atomic_compare_exchange_strong(\ _a, _expected, _desired, _success_mo, _fail_mo) \ atomic_compare_exchange_strong_explicit(\ _a, _expected, _desired, _success_mo, _fail_mo) #define re_atomic_compare_exchange_weak(\ _a, _expected, _desired, _success_mo, _fail_mo) \ atomic_compare_exchange_weak_explicit(\ _a, _expected, _desired, _success_mo, _fail_mo) #define re_atomic_fetch_add(_a, _v, _mo) \ atomic_fetch_add_explicit(_a, _v, _mo) #define re_atomic_fetch_sub(_a, _v, _mo) \ atomic_fetch_sub_explicit(_a, _v, _mo) #define re_atomic_fetch_or(_a, _v, _mo) \ atomic_fetch_or_explicit(_a, _v, _mo) #define re_atomic_fetch_xor(_a, _v, _mo) \ atomic_fetch_xor_explicit(_a, _v, _mo) #define re_atomic_fetch_and(_a, _v, _mo) \ atomic_fetch_and_explicit(_a, _v, _mo) /* gcc-style __atomic* intrinsics. * Note: clang-cl also supports these, even though it impersonates MSVC. */ #elif (defined(__GNUC__) || defined(__clang__)) && \ defined(__GCC_ATOMIC_BOOL_LOCK_FREE) && \ defined(__GCC_ATOMIC_CHAR_LOCK_FREE) && \ defined(__GCC_ATOMIC_WCHAR_T_LOCK_FREE) && \ defined(__GCC_ATOMIC_SHORT_LOCK_FREE) && \ defined(__GCC_ATOMIC_INT_LOCK_FREE) && \ defined(__GCC_ATOMIC_LONG_LOCK_FREE) && \ defined(__GCC_ATOMIC_LLONG_LOCK_FREE) && \ defined(__GCC_ATOMIC_POINTER_LOCK_FREE) && \ defined(__ATOMIC_RELAXED) && defined(__ATOMIC_ACQUIRE) && \ defined(__ATOMIC_RELEASE) && defined(__ATOMIC_ACQ_REL) && \ defined(__ATOMIC_SEQ_CST) #define RE_ATOMIC_BOOL_LOCK_FREE __GCC_ATOMIC_BOOL_LOCK_FREE #define RE_ATOMIC_CHAR_LOCK_FREE __GCC_ATOMIC_CHAR_LOCK_FREE #define RE_ATOMIC_WCHAR_T_LOCK_FREE __GCC_ATOMIC_WCHAR_T_LOCK_FREE #define RE_ATOMIC_SHORT_LOCK_FREE __GCC_ATOMIC_SHORT_LOCK_FREE #define RE_ATOMIC_INT_LOCK_FREE __GCC_ATOMIC_INT_LOCK_FREE #define RE_ATOMIC_LONG_LOCK_FREE __GCC_ATOMIC_LONG_LOCK_FREE #define RE_ATOMIC_LLONG_LOCK_FREE __GCC_ATOMIC_LLONG_LOCK_FREE #define RE_ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE #define re_memory_order_relaxed __ATOMIC_RELAXED #define re_memory_order_acquire __ATOMIC_ACQUIRE #define re_memory_order_release __ATOMIC_RELEASE #define re_memory_order_acq_rel __ATOMIC_ACQ_REL #define re_memory_order_seq_cst __ATOMIC_SEQ_CST #define re_atomic_store(_a, _v, _mo) \ __atomic_store_n(_a, _v, _mo) #define re_atomic_load(_a, _mo) \ __atomic_load_n(_a, _mo) #define re_atomic_exchange(_a, _v, _mo) \ __atomic_exchange_n(_a, _v, _mo) #define re_atomic_compare_exchange_strong(\ _a, _expected, _desired, _success_mo, _fail_mo) \ __atomic_compare_exchange_n(\ _a, _expected, _desired, 0, _success_mo, _fail_mo) #define re_atomic_compare_exchange_weak(\ _a, _expected, _desired, _success_mo, _fail_mo) \ __atomic_compare_exchange_n(\ _a, _expected, _desired, 1, _success_mo, _fail_mo) #define re_atomic_fetch_add(_a, _v, _mo) \ __atomic_fetch_add(_a, _v, _mo) #define re_atomic_fetch_sub(_a, _v, _mo) \ __atomic_fetch_sub(_a, _v, _mo) #define re_atomic_fetch_or(_a, _v, _mo) \ __atomic_fetch_or(_a, _v, _mo) #define re_atomic_fetch_xor(_a, _v, _mo) \ __atomic_fetch_xor(_a, _v, _mo) #define re_atomic_fetch_and(_a, _v, _mo) \ __atomic_fetch_and(_a, _v, _mo) /* gcc-style __sync* intrinsics. */ #elif defined(__GNUC__) && \ (defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) || \ defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) || \ defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) || \ defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8)) #if !defined(__SIZEOF_SHORT__) || !defined(__SIZEOF_INT__) || \ !defined(__SIZEOF_LONG__) || !defined(__SIZEOF_LONG_LONG__) #include #endif #if !defined(__SIZEOF_POINTER__) #include #endif #if !defined(__SIZEOF_WCHAR_T__) #include #endif #if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) #define RE_ATOMIC_CHAR_LOCK_FREE 2 #endif #if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) #if (defined(__SIZEOF_SHORT__) && __SIZEOF_SHORT__ == 2) || \ (defined(USHRT_MAX) && USHRT_MAX == 0xffffu) #define RE_ATOMIC_SHORT_LOCK_FREE 2 #endif #if (defined(__SIZEOF_INT__) && __SIZEOF_INT__ == 2) || \ (defined(UINT_MAX) && UINT_MAX == 0xffffu) #define RE_ATOMIC_INT_LOCK_FREE 2 #endif #if (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 2) || \ (defined(ULONG_MAX) && ULONG_MAX == 0xffffu) #define RE_ATOMIC_LONG_LOCK_FREE 2 #endif #if (defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ == 2) || \ (defined(ULLONG_MAX) && ULLONG_MAX == 0xffffu) #define RE_ATOMIC_LLONG_LOCK_FREE 2 #endif #if (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 2) || \ (defined(UINTPTR_MAX) && UINTPTR_MAX == 0xffffu) #define RE_ATOMIC_POINTER_LOCK_FREE 2 #endif #if (defined(__SIZEOF_WCHAR_T__) && __SIZEOF_WCHAR_T__ == 2) || \ (defined(WCHAR_MAX) && (WCHAR_MAX == 0xffff || WCHAR_MAX == 0x7fff)) #define RE_ATOMIC_WCHAR_T_LOCK_FREE 2 #endif #endif #if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) #if (defined(__SIZEOF_SHORT__) && __SIZEOF_SHORT__ == 4) || \ (defined(USHRT_MAX) && USHRT_MAX == 0xffffffffu) #define RE_ATOMIC_SHORT_LOCK_FREE 2 #endif #if (defined(__SIZEOF_INT__) && __SIZEOF_INT__ == 4) || \ (defined(UINT_MAX) && UINT_MAX == 0xffffffffu) #define RE_ATOMIC_INT_LOCK_FREE 2 #endif #if (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 4) || \ (defined(ULONG_MAX) && ULONG_MAX == 0xffffffffu) #define RE_ATOMIC_LONG_LOCK_FREE 2 #endif #if (defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ == 4) || \ (defined(ULLONG_MAX) && ULLONG_MAX == 0xffffffffu) #define RE_ATOMIC_LLONG_LOCK_FREE 2 #endif #if (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 4) || \ (defined(UINTPTR_MAX) && UINTPTR_MAX == 0xffffffffu) #define RE_ATOMIC_POINTER_LOCK_FREE 2 #endif #if (defined(__SIZEOF_WCHAR_T__) && __SIZEOF_WCHAR_T__ == 4) || \ (defined(WCHAR_MAX) && (WCHAR_MAX == 0xffffffff || \ WCHAR_MAX == 0x7fffffff)) #define RE_ATOMIC_WCHAR_T_LOCK_FREE 2 #endif #endif #if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) #if (defined(__SIZEOF_SHORT__) && __SIZEOF_SHORT__ == 8) || \ (defined(USHRT_MAX) && USHRT_MAX == 0xffffffffffffffffu) #define RE_ATOMIC_SHORT_LOCK_FREE 2 #endif #if (defined(__SIZEOF_INT__) && __SIZEOF_INT__ == 8) || \ (defined(UINT_MAX) && UINT_MAX == 0xffffffffffffffffu) #define RE_ATOMIC_INT_LOCK_FREE 2 #endif #if (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 8) || \ (defined(ULONG_MAX) && ULONG_MAX == 0xffffffffffffffffu) #define RE_ATOMIC_LONG_LOCK_FREE 2 #endif #if (defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ == 8) || \ (defined(ULLONG_MAX) && ULLONG_MAX == 0xffffffffffffffffu) #define RE_ATOMIC_LLONG_LOCK_FREE 2 #endif #if (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8) || \ (defined(UINTPTR_MAX) && UINTPTR_MAX == 0xffffffffffffffffu) #define RE_ATOMIC_POINTER_LOCK_FREE 2 #endif #if (defined(__SIZEOF_WCHAR_T__) && __SIZEOF_WCHAR_T__ == 8) || \ (defined(WCHAR_MAX) && (WCHAR_MAX == 0xffffffffffffffff || \ WCHAR_MAX == 0x7fffffffffffffff)) #define RE_ATOMIC_WCHAR_T_LOCK_FREE 2 #endif #endif #if !defined(RE_ATOMIC_CHAR_LOCK_FREE) #define RE_ATOMIC_CHAR_LOCK_FREE 0 #endif #if !defined(RE_ATOMIC_SHORT_LOCK_FREE) #define RE_ATOMIC_SHORT_LOCK_FREE 0 #endif #if !defined(RE_ATOMIC_INT_LOCK_FREE) #define RE_ATOMIC_INT_LOCK_FREE 0 #endif #if !defined(RE_ATOMIC_LONG_LOCK_FREE) #define RE_ATOMIC_LONG_LOCK_FREE 0 #endif #if !defined(RE_ATOMIC_LLONG_LOCK_FREE) #define RE_ATOMIC_LLONG_LOCK_FREE 0 #endif #if !defined(RE_ATOMIC_POINTER_LOCK_FREE) #define RE_ATOMIC_POINTER_LOCK_FREE 0 #endif #if !defined(RE_ATOMIC_WCHAR_T_LOCK_FREE) #define RE_ATOMIC_WCHAR_T_LOCK_FREE 0 #endif /* Assume bool is always 1 byte. Add platform-specific exceptions, * if needed. */ #define RE_ATOMIC_BOOL_LOCK_FREE RE_ATOMIC_CHAR_LOCK_FREE /* These constants match __ATOMIC_* predefined macros on * gcc versions that support __atomic intrinsics. */ #define re_memory_order_relaxed 0 #define re_memory_order_acquire 2 #define re_memory_order_release 3 #define re_memory_order_acq_rel 4 #define re_memory_order_seq_cst 5 #if defined(__x86_64__) #define re_atomic_store(_a, _v, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val = (_v);\ if ((_mo) != re_memory_order_seq_cst) {\ __asm__ __volatile__ ("mov %1, %0"\ : "=m" (*(_a))\ : "q" (_val)\ : "memory");\ }\ else {\ __asm__ __volatile__ ("xchg %1, %0"\ : "=m" (*(_a)), "+q" (_val)\ : \ : "memory");\ }\ }) #define re_atomic_load(_a, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val;\ __asm__ __volatile__ ("mov %1, %0"\ : "=q" (_val)\ : "m" (*(_a))\ : "memory");\ _val;\ }) #define re_atomic_exchange(_a, _v, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val = (_v);\ __asm__ __volatile__ ("xchg %1, %0"\ : "+m" (*(_a)), "+q" (_val)\ : \ : "memory");\ _val;\ }) #elif defined(__i386__) #define re_atomic_store(_a, _v, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val = (_v);\ if (sizeof(_val) < 8) {\ if ((_mo) != re_memory_order_seq_cst) {\ __asm__ __volatile__ ("mov %1, %0"\ : "=m" (*(_a))\ : "q" (_val)\ : "memory");\ }\ else {\ __asm__ __volatile__ ("xchg %1, %0"\ : "=m" (*(_a)), "+q" (_val)\ : \ : "memory");\ }\ }\ else {\ __typeof__(*(_a)) _expected = *(_a);\ while (1) {\ __typeof__(*(_a)) _prev_val =\ __sync_val_compare_and_swap(\ _a, _expected, _val);\ if (_prev_val == _expected)\ break;\ _expected = _prev_val;\ }\ }\ }) #define re_atomic_load(_a, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val;\ if (sizeof(_val) < 8) {\ __asm__ __volatile__ ("mov %1, %0"\ : "=q" (_val)\ : "m" (*(_a))\ : "memory");\ }\ else {\ _val = __sync_val_compare_and_swap(\ _a,\ (__typeof__(*(_a)))0,\ (__typeof__(*(_a)))0);\ }\ _val;\ }) #define re_atomic_exchange(_a, _v, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val = (_v);\ if (sizeof(_val) < 8) {\ __asm__ __volatile__ ("xchg %1, %0"\ : "+m" (*(_a)), "+q" (_val)\ : \ : "memory");\ }\ else {\ __typeof__(*(_a)) _expected = *(_a);\ while (1) {\ __typeof__(*(_a)) _prev_val =\ __sync_val_compare_and_swap(\ _a, _expected, _val);\ if (_prev_val == _expected)\ break;\ _expected = _prev_val;\ }\ _val = _expected;\ }\ _val;\ }) #else #define re_atomic_store(_a, _v, _mo) \ (void)re_atomic_exchange(_a, _v, _mo) #define re_atomic_load(_a, _mo) \ __sync_val_compare_and_swap(\ _a, (__typeof__(*(_a)))0, (__typeof__(*(_a)))0) #define re_atomic_exchange(_a, _v, _mo) \ __extension__\ ({\ __typeof__(*(_a)) _val = (_v);\ __typeof__(*(_a)) _expected = *(_a);\ while (1) {\ __typeof__(*(_a)) _prev_val =\ __sync_val_compare_and_swap(\ _a, _expected, _val);\ if (_prev_val == _expected)\ break;\ _expected = _prev_val;\ }\ _expected;\ }) #endif #define re_atomic_compare_exchange_strong(\ _a, _expected, _desired, _success_mo, _fail_mo) \ __extension__\ ({\ __typeof__(*(_a)) _exp_val = *(_expected);\ __typeof__(*(_a)) _prev_val =\ __sync_val_compare_and_swap(_a, _exp_val,\ (__typeof__(*(_a)))(_desired));\ *(_expected) = _prev_val;\ _prev_val == _exp_val;\ }) #define re_atomic_compare_exchange_weak(\ _a, _expected, _desired, _success_mo, _fail_mo) \ re_atomic_compare_exchange_strong(\ _a, _expected, _desired, _success_mo, _fail_mo) #define re_atomic_fetch_add(_a, _v, _mo) \ __sync_fetch_and_add(_a, (__typeof__(*(_a)))(_v)) #define re_atomic_fetch_sub(_a, _v, _mo) \ __sync_fetch_and_sub(_a, (__typeof__(*(_a)))(_v)) #define re_atomic_fetch_or(_a, _v, _mo) \ __sync_fetch_and_or(_a, (__typeof__(*(_a)))(_v)) #define re_atomic_fetch_xor(_a, _v, _mo) \ __sync_fetch_and_xor(_a, (__typeof__(*(_a)))(_v)) #define re_atomic_fetch_and(_a, _v, _mo) \ __sync_fetch_and_and(_a, (__typeof__(*(_a)))(_v)) /* MSVC Interlocked* intrinsics. This needs to go after clang to let clang-cl * get handled above. */ #elif defined(_MSC_VER) #include #include #include "re_types.h" #ifdef __cplusplus extern "C" { #endif #define RE_ATOMIC_BOOL_LOCK_FREE 2 #define RE_ATOMIC_CHAR_LOCK_FREE 2 #define RE_ATOMIC_WCHAR_T_LOCK_FREE 2 #define RE_ATOMIC_SHORT_LOCK_FREE 2 #define RE_ATOMIC_INT_LOCK_FREE 2 #define RE_ATOMIC_LONG_LOCK_FREE 2 #define RE_ATOMIC_LLONG_LOCK_FREE 2 #define RE_ATOMIC_POINTER_LOCK_FREE 2 /* These constants don't matter but for consistency they match * values in std::memory_order from in C++. * There are specialized intrinsics for ARM and ARM64 * for different memory ordering types, but they are not used (yet) below. */ #define re_memory_order_relaxed 0 #define re_memory_order_acquire 2 #define re_memory_order_release 3 #define re_memory_order_acq_rel 4 #define re_memory_order_seq_cst 5 static unsigned __int64 _re_atomic_exchange( size_t size, void *a, unsigned __int64 v); #if defined(_M_IX86) || defined(_M_AMD64) static __forceinline void _re_atomic_store( size_t size, void *a, unsigned __int64 v, unsigned int mo) { assert(size == 1u || size == 2u || size == 4u || size == 8u); if (mo != re_memory_order_seq_cst) { _ReadWriteBarrier(); switch (size) { case 1u: *(volatile unsigned __int8*)a = (unsigned __int8)v; break; case 2u: *(volatile unsigned __int16*)a = (unsigned __int16)v; break; case 4u: *(volatile unsigned __int32*)a = (unsigned __int32)v; break; default: #if defined(_M_IX86) { __int64 prev_val = *(const volatile __int64*)(a); while (1) { __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)v, prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } } #else *(volatile unsigned __int64*)a = v; #endif break; } _ReadWriteBarrier(); } else { _re_atomic_exchange(size, a, v); } } static __forceinline unsigned __int64 _re_atomic_load( size_t size, const void *a, unsigned int mo) { unsigned __int64 v; assert(size == 1u || size == 2u || size == 4u || size == 8u); _ReadWriteBarrier(); switch (size) { case 1u: v = *(const volatile unsigned __int8*)a; break; case 2u: v = *(const volatile unsigned __int16*)a; break; case 4u: v = *(const volatile unsigned __int32*)a; break; default: #if defined(_M_IX86) v = _InterlockedCompareExchange64((__int64*)a, 0, 0); #else v = *(const volatile unsigned __int64*)a; #endif break; } _ReadWriteBarrier(); return v; } #elif defined(_M_ARM) || defined(_M_ARM64) static __forceinline void _re_atomic_store( size_t size, void *a, unsigned __int64 v, unsigned int mo) { assert(size == 1u || size == 2u || size == 4u || size == 8u); _ReadWriteBarrier(); if (mo >= re_memory_order_release) __dmb(0x0b); /* dmb ish */ _ReadWriteBarrier(); switch (size) { case 1u: __iso_volatile_store8((__int8*)a, (__int8)v); break; case 2u: __iso_volatile_store16((__int16*)a, (__int16)v); break; case 4u: __iso_volatile_store32((__int32*)a, (__int32)v); break; default: __iso_volatile_store64((__int64*)a, (__int64)v); break; } _ReadWriteBarrier(); if (mo == re_memory_order_seq_cst) __dmb(0x0b); /* dmb ish */ _ReadWriteBarrier(); } static __forceinline unsigned __int64 _re_atomic_load( size_t size, const void *a, unsigned int mo) { unsigned __int64 v; assert(size == 1u || size == 2u || size == 4u || size == 8u); _ReadWriteBarrier(); switch (size) { case 1u: v = __iso_volatile_load8((const volatile __int8*)a); break; case 2u: v = __iso_volatile_load16((const volatile __int16*)a); break; case 4u: v = __iso_volatile_load32((const volatile __int32*)a); break; default: v = __iso_volatile_load64((const volatile __int64*)a); break; } _ReadWriteBarrier(); if (mo != re_memory_order_relaxed && mo <= re_memory_order_acquire) __dmb(0x0b); /* dmb ish */ _ReadWriteBarrier(); return v; } #else static __forceinline void _re_atomic_store( size_t size, void *a, unsigned __int64 v, unsigned int mo) { assert(size == 1u || size == 2u || size == 4u || size == 8u); _ReadWriteBarrier(); switch (size) { case 1u: { char prev_val = *(const volatile char*)(a); while (1) { char prev_val2 = _InterlockedCompareExchange8( (char*)a, (char)v, prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } } break; case 2u: { short prev_val = *(const volatile short*)(a); while (1) { short prev_val2 = _InterlockedCompareExchange16( (short*)a, (short)v, prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } } break; case 4u: { long prev_val = *(const volatile long*)(a); while (1) { long prev_val2 = _InterlockedCompareExchange( (long*)a, (long)v, prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } } break; default: { __int64 prev_val = *(const volatile __int64*)(a); while (1) { __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)v, prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } } break; } _ReadWriteBarrier(); } static __forceinline unsigned __int64 _re_atomic_load( size_t size, const void *a, unsigned int mo) { unsigned __int64 v; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: v = _InterlockedCompareExchange8((char*)a, 0, 0); break; case 2u: v = _InterlockedCompareExchange16((short*)a, 0, 0); break; case 4u: v = _InterlockedCompareExchange((long*)a, 0, 0); break; default: v = _InterlockedCompareExchange64((__int64*)a, 0, 0); break; } return v; } #endif #define re_atomic_store(_a, _v, _mo) \ _re_atomic_store(sizeof(*(_a)), _a, _v, _mo); #define re_atomic_load(_a, _mo) \ _re_atomic_load(sizeof(*(_a)), _a, _mo) static __forceinline unsigned __int64 _re_atomic_exchange( size_t size, void *a, unsigned __int64 v) { unsigned __int64 prev_val; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: prev_val = _InterlockedExchange8((char*)a, (char)v); break; case 2u: prev_val = _InterlockedExchange16((short*)a, (short)v); break; case 4u: prev_val = _InterlockedExchange((long*)a, (long)v); break; default: #if defined(_M_IX86) { _ReadWriteBarrier(); prev_val = *(const volatile __int64*)(a); while (1) { __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)v, (__int64)prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } _ReadWriteBarrier(); } #else prev_val = _InterlockedExchange64((__int64*)a, (__int64)v); #endif break; } return prev_val; } #define re_atomic_exchange(_a, _v, _mo) \ _re_atomic_exchange(sizeof(*(_a)), _a, _v) static __forceinline bool _re_atomic_compare_exchange_strong( size_t size, void *a, void *expected, unsigned __int64 desired) { bool res; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: { char expected_val = *(char*)expected; char prev_val = _InterlockedCompareExchange8( (char*)a, (char)desired, expected_val); *(char*)expected = prev_val; res = prev_val == expected_val; } break; case 2u: { short expected_val = *(short*)expected; short prev_val = _InterlockedCompareExchange16( (short*)a, (short)desired, expected_val); *(short*)expected = prev_val; res = prev_val == expected_val; } break; case 4u: { long expected_val = *(long*)expected; long prev_val = _InterlockedCompareExchange( (long*)a, (long)desired, expected_val); *(long*)expected = prev_val; res = prev_val == expected_val; } break; default: { __int64 expected_val = *(__int64*)expected; __int64 prev_val = _InterlockedCompareExchange64( (__int64*)a, (__int64)desired, expected_val); *(__int64*)expected = prev_val; res = prev_val == expected_val; } break; } return res; } #define re_atomic_compare_exchange_strong(\ _a, _expected, _desired, _success_mo, _fail_mo) \ _re_atomic_compare_exchange_strong(\ sizeof(*(_a)), _a, _expected, _desired) #define re_atomic_compare_exchange_weak(\ _a, _expected, _desired, _success_mo, _fail_mo) \ re_atomic_compare_exchange_strong(\ _a, _expected, _desired, _success_mo, _fail_mo) static __forceinline unsigned __int64 _re_atomic_fetch_add( size_t size, void *a, unsigned __int64 v) { unsigned __int64 prev_val; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: prev_val = _InterlockedExchangeAdd8((char*)a, (char)v); break; case 2u: prev_val = _InterlockedExchangeAdd16((short*)a, (short)v); break; case 4u: prev_val = _InterlockedExchangeAdd((long*)a, (long)v); break; default: #if defined(_M_IX86) { _ReadWriteBarrier(); prev_val = *(const volatile __int64*)(a); while (1) { __int64 new_val = prev_val + v; __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)new_val, (__int64)prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } _ReadWriteBarrier(); } #else prev_val = _InterlockedExchangeAdd64((__int64*)a, (__int64)v); #endif break; } return prev_val; } #define re_atomic_fetch_add(_a, _v, _mo) \ _re_atomic_fetch_add(sizeof(*(_a)), _a, _v) #define re_atomic_fetch_sub(_a, _v, _mo) \ re_atomic_fetch_add(_a, -(__int64)(_v), _mo) static __forceinline unsigned __int64 _re_atomic_fetch_or( size_t size, void *a, unsigned __int64 v) { unsigned __int64 prev_val; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: prev_val = _InterlockedOr8((char*)a, (char)v); break; case 2u: prev_val = _InterlockedOr16((short*)a, (short)v); break; case 4u: prev_val = _InterlockedOr((long*)a, (long)v); break; default: #if defined(_M_IX86) { _ReadWriteBarrier(); prev_val = *(const volatile __int64*)(a); while (1) { __int64 new_val = prev_val | v; __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)new_val, (__int64)prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } _ReadWriteBarrier(); } #else prev_val = _InterlockedOr64((__int64*)a, (__int64)v); #endif break; } return prev_val; } #define re_atomic_fetch_or(_a, _v, _mo) \ _re_atomic_fetch_or(sizeof(*(_a)), _a, _v) static __forceinline unsigned __int64 _re_atomic_fetch_xor( size_t size, void *a, unsigned __int64 v) { unsigned __int64 prev_val; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: prev_val = _InterlockedXor8((char*)a, (char)v); break; case 2u: prev_val = _InterlockedXor16((short*)a, (short)v); break; case 4u: prev_val = _InterlockedXor((long*)a, (long)v); break; default: #if defined(_M_IX86) { _ReadWriteBarrier(); prev_val = *(const volatile __int64*)(a); while (1) { __int64 new_val = prev_val ^ v; __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)new_val, (__int64)prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } _ReadWriteBarrier(); } #else prev_val = _InterlockedXor64((__int64*)a, (__int64)v); #endif break; } return prev_val; } #define re_atomic_fetch_xor(_a, _v, _mo) \ _re_atomic_fetch_xor(sizeof(*(_a)), _a, _v) static __forceinline unsigned __int64 _re_atomic_fetch_and( size_t size, void *a, unsigned __int64 v) { unsigned __int64 prev_val; assert(size == 1u || size == 2u || size == 4u || size == 8u); switch (size) { case 1u: prev_val = _InterlockedAnd8((char*)a, (char)v); break; case 2u: prev_val = _InterlockedAnd16((short*)a, (short)v); break; case 4u: prev_val = _InterlockedAnd((long*)a, (long)v); break; default: #if defined(_M_IX86) { _ReadWriteBarrier(); prev_val = *(const volatile __int64*)(a); while (1) { __int64 new_val = prev_val & v; __int64 prev_val2 = _InterlockedCompareExchange64( (__int64*)a, (__int64)new_val, (__int64)prev_val); if (prev_val2 == prev_val) break; prev_val = prev_val2; } _ReadWriteBarrier(); } #else prev_val = _InterlockedAnd64((__int64*)a, (__int64)v); #endif break; } return prev_val; } #define re_atomic_fetch_and(_a, _v, _mo) \ _re_atomic_fetch_and(sizeof(*(_a)), _a, _v) #ifdef __cplusplus } /* extern "C" */ #endif #else #error "Compiler does not support atomics" #endif /* HAVE_ATOMIC */ #ifndef RE_ATOMIC #define RE_ATOMIC #endif /* --- Some short alias helpers --- */ /** * @def re_atomic_rlx(_a) * * Load value from an atomic object with relaxed order * * @param _a pointer to the atomic object * * @return value of the atomic variable */ #define re_atomic_rlx(_a) re_atomic_load(_a, re_memory_order_relaxed) /** * @def re_atomic_rlx_set(_a, _v) * * Store value in an atomic object with relaxed order * * @param _a pointer to the atomic object * @param _v new value */ #define re_atomic_rlx_set(_a, _v) \ re_atomic_store(_a, _v, re_memory_order_relaxed) /** * @def re_atomic_rlx_add(_a, _v) * * Replace value from an atomic object with addition and relaxed order * * @param _a pointer to the atomic object * @param _v value to add * * @return value held previously by the atomic variable */ #define re_atomic_rlx_add(_a, _v) \ re_atomic_fetch_add(_a, _v, re_memory_order_relaxed) /** * @def re_atomic_rlx_sub(_a, _v) * * Replace value from an atomic object with subtraction and relaxed order * * @param _a pointer to the atomic object * @param _v value to subtract * * @return value held previously by the atomic variable */ #define re_atomic_rlx_sub(_a, _v) \ re_atomic_fetch_sub(_a, _v, re_memory_order_relaxed) /** * @def re_atomic_acq(_a) * * Load value from an atomic object with acquire order * * @param _a pointer to the atomic object * * @return value of the atomic variable */ #define re_atomic_acq(_a) re_atomic_load(_a, re_memory_order_acquire) /** * @def re_atomic_rls_set(_a, _v) * * Store value in an atomic object with release order * * @param _a pointer to the atomic object * @param _v new value */ #define re_atomic_rls_set(_a, _v) \ re_atomic_store(_a, _v, re_memory_order_release) /** * @def re_atomic_acq_add(_a, _v) * * Replace value from an atomic object with addition and acquire-release order * * @param _a pointer to the atomic object * @param _v value to add * * @return value held previously by the atomic variable */ #define re_atomic_acq_add(_a, _v) \ re_atomic_fetch_add(_a, _v, re_memory_order_acq_rel) /** * @def re_atomic_acq_sub(_a, _v) * * Replace value from an atomic object with subtraction and acquire-release * order * * @param _a pointer to the atomic object * @param _v value to subtract * * @return value held previously by the atomic variable */ #define re_atomic_acq_sub(_a, _v) \ re_atomic_fetch_sub(_a, _v, re_memory_order_acq_rel) /** * @def re_atomic_seq(_a) * * Load value from an atomic object with sequentially-consistent order * * @param _a pointer to the atomic object * * @return value of the atomic variable */ #define re_atomic_seq(_a) re_atomic_load(_a, re_memory_order_seq_cst) /** * @def re_atomic_seq_set(_a, _v) * * Store value in an atomic object with sequentially-consistent order * * @param _a pointer to the atomic object * @param _v new value */ #define re_atomic_seq_set(_a, _v) \ re_atomic_store(_a, _v, re_memory_order_seq_cst) /** * @def re_atomic_seq_add(_a, _v) * * Replace value from an atomic object with addition and * sequentially-consistent order * * @param _a pointer to the atomic object * @param _v value to add * * @return value held previously by the atomic variable */ #define re_atomic_seq_add(_a, _v) \ re_atomic_fetch_add(_a, _v, re_memory_order_seq_cst) /** * @def re_atomic_seq_sub(_a, _v) * * Replace value from an atomic object with subtraction and * sequentially-consistent order * * @param _a pointer to the atomic object * @param _v value to subtract * * @return value held previously by the atomic variable */ #define re_atomic_seq_sub(_a, _v) \ re_atomic_fetch_sub(_a, _v, re_memory_order_seq_cst) #endif /* RE_H_ATOMIC__ */ ================================================ FILE: include/re_av1.h ================================================ /** * @file re_av1.h AV1 Open Bitstream Unit (OBU) * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ /* OBU (Open Bitstream Units) */ /** Defines the OBU type */ enum obu_type { AV1_OBU_SEQUENCE_HEADER = 1, AV1_OBU_TEMPORAL_DELIMITER = 2, AV1_OBU_FRAME_HEADER = 3, AV1_OBU_TILE_GROUP = 4, AV1_OBU_METADATA = 5, AV1_OBU_FRAME = 6, AV1_OBU_REDUNDANT_FRAME_HEADER = 7, AV1_OBU_TILE_LIST = 8, AV1_OBU_PADDING = 15, }; /** * AV1 OBU Header * * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |F| type |X|S|-| (REQUIRED) * +-+-+-+-+-+-+-+-+ */ struct av1_obu_hdr { enum obu_type type; /**< OBU type */ bool x; /**< Extension flag */ bool s; /**< Has size field */ size_t size; /**< Payload size */ }; int av1_leb128_encode(struct mbuf *mb, uint64_t value); int av1_leb128_decode(struct mbuf *mb, uint64_t *value); int av1_obu_encode(struct mbuf *mb, uint8_t type, bool has_size, size_t len, const uint8_t *payload); int av1_obu_decode(struct av1_obu_hdr *hdr, struct mbuf *mb); int av1_obu_print(struct re_printf *pf, const struct av1_obu_hdr *hdr); unsigned av1_obu_count(const uint8_t *buf, size_t size); unsigned av1_obu_count_rtp(const uint8_t *buf, size_t size); const char *av1_obu_name(enum obu_type type); bool obu_allowed_rtp(enum obu_type type); /* * Packetizer */ typedef int (av1_packet_h)(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg); int av1_packetize(bool *newp, bool marker, uint64_t rtp_ts, const uint8_t *buf, size_t len, size_t maxlen, av1_packet_h *pkth, void *arg); enum { AV1_AGGR_HDR_SIZE = 1, }; /** AV1 Aggregation Header */ struct av1_aggr_hdr { unsigned z:1; /**< Continuation of OBU fragment from prev packet */ unsigned y:1; /**< Last OBU element will continue in next packet */ unsigned w:2; /**< Number of OBU elements in the packet */ unsigned n:1; /**< First packet of a coded video sequence */ }; int av1_aggr_hdr_decode(struct av1_aggr_hdr *hdr, struct mbuf *mb); ================================================ FILE: include/re_base64.h ================================================ /** * @file re_base64.h Interface to Base64 encoding/decoding functions * * Copyright (C) 2010 Creytiv.com */ int base64_encode(const uint8_t *in, size_t ilen, char *out, size_t *olen); int base64url_encode(const uint8_t *in, size_t ilen, char *out, size_t *olen); int base64_print(struct re_printf *pf, const uint8_t *ptr, size_t len); int base64_decode(const char *in, size_t ilen, uint8_t *out, size_t *olen); ================================================ FILE: include/re_bfcp.h ================================================ /** * @file re_bfcp.h Interface to Binary Floor Control Protocol (BFCP) * * Copyright (C) 2010 Creytiv.com */ /** BFCP Versions */ enum { BFCP_VER1 = 1, BFCP_VER2 = 2, }; /** BFCP Primitives */ enum bfcp_prim { BFCP_FLOOR_REQUEST = 1, BFCP_FLOOR_RELEASE = 2, BFCP_FLOOR_REQUEST_QUERY = 3, BFCP_FLOOR_REQUEST_STATUS = 4, BFCP_USER_QUERY = 5, BFCP_USER_STATUS = 6, BFCP_FLOOR_QUERY = 7, BFCP_FLOOR_STATUS = 8, BFCP_CHAIR_ACTION = 9, BFCP_CHAIR_ACTION_ACK = 10, BFCP_HELLO = 11, BFCP_HELLO_ACK = 12, BFCP_ERROR = 13, BFCP_FLOOR_REQ_STATUS_ACK = 14, BFCP_FLOOR_STATUS_ACK = 15, BFCP_GOODBYE = 16, BFCP_GOODBYE_ACK = 17, }; /** BFCP Attributes */ enum bfcp_attrib { BFCP_BENEFICIARY_ID = 1, BFCP_FLOOR_ID = 2, BFCP_FLOOR_REQUEST_ID = 3, BFCP_PRIORITY = 4, BFCP_REQUEST_STATUS = 5, BFCP_ERROR_CODE = 6, BFCP_ERROR_INFO = 7, BFCP_PART_PROV_INFO = 8, BFCP_STATUS_INFO = 9, BFCP_SUPPORTED_ATTRS = 10, BFCP_SUPPORTED_PRIMS = 11, BFCP_USER_DISP_NAME = 12, BFCP_USER_URI = 13, /* grouped: */ BFCP_BENEFICIARY_INFO = 14, BFCP_FLOOR_REQ_INFO = 15, BFCP_REQUESTED_BY_INFO = 16, BFCP_FLOOR_REQ_STATUS = 17, BFCP_OVERALL_REQ_STATUS = 18, /** Mandatory Attribute */ BFCP_MANDATORY = 1<<7, /** Encode Handler */ BFCP_ENCODE_HANDLER = 1<<8, }; /** BFCP Request Status */ enum bfcp_reqstat { BFCP_PENDING = 1, BFCP_ACCEPTED = 2, BFCP_GRANTED = 3, BFCP_DENIED = 4, BFCP_CANCELLED = 5, BFCP_RELEASED = 6, BFCP_REVOKED = 7 }; /** BFCP Error Codes */ enum bfcp_err { BFCP_CONF_NOT_EXIST = 1, BFCP_USER_NOT_EXIST = 2, BFCP_UNKNOWN_PRIM = 3, BFCP_UNKNOWN_MAND_ATTR = 4, BFCP_UNAUTH_OPERATION = 5, BFCP_INVALID_FLOOR_ID = 6, BFCP_FLOOR_REQ_ID_NOT_EXIST = 7, BFCP_MAX_FLOOR_REQ_REACHED = 8, BFCP_USE_TLS = 9, BFCP_PARSE_ERROR = 10, BFCP_USE_DTLS = 11, BFCP_UNSUPPORTED_VERSION = 12, BFCP_BAD_LENGTH = 13, BFCP_GENERIC_ERROR = 14, }; /** BFCP Priority */ enum bfcp_priority { BFCP_PRIO_LOWEST = 0, BFCP_PRIO_LOW = 1, BFCP_PRIO_NORMAL = 2, BFCP_PRIO_HIGH = 3, BFCP_PRIO_HIGHEST = 4 }; /** BFCP Transport */ enum bfcp_transp { BFCP_UDP, BFCP_DTLS, BFCP_TCP }; /** BFCP Request Status */ struct bfcp_reqstatus { enum bfcp_reqstat status; uint8_t qpos; }; /** BFCP Error Code */ struct bfcp_errcode { enum bfcp_err code; uint8_t *details; /* optional */ size_t len; }; /** BFCP Supported Attributes */ struct bfcp_supattr { enum bfcp_attrib *attrv; size_t attrc; }; /** BFCP Supported Primitives */ struct bfcp_supprim { enum bfcp_prim *primv; size_t primc; }; /** BFCP Attribute */ struct bfcp_attr { struct le le; struct list attrl; enum bfcp_attrib type; bool mand; union bfcp_union { /* generic types */ char *str; uint16_t u16; /* actual attributes */ uint16_t beneficiaryid; uint16_t floorid; uint16_t floorreqid; enum bfcp_priority priority; struct bfcp_reqstatus reqstatus; struct bfcp_errcode errcode; char *errinfo; char *partprovinfo; char *statusinfo; struct bfcp_supattr supattr; struct bfcp_supprim supprim; char *userdname; char *useruri; uint16_t reqbyid; } v; }; /** BFCP unknown attributes */ struct bfcp_unknown_attr { uint8_t typev[16]; size_t typec; }; /** BFCP Message */ struct bfcp_msg { struct bfcp_unknown_attr uma; struct sa src; uint8_t ver; unsigned r:1; unsigned f:1; enum bfcp_prim prim; uint16_t len; uint32_t confid; uint16_t tid; uint16_t userid; struct list attrl; }; struct tls; struct bfcp_conn; /** * Defines the BFCP encode handler * * @param mb Mbuf to encode into * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ typedef int (bfcp_encode_h)(struct mbuf *mb, void *arg); /** BFCP Encode */ struct bfcp_encode { bfcp_encode_h *ench; void *arg; }; /** * Defines the BFCP attribute handler * * @param attr BFCP attribute * @param arg Handler argument * * @return True to stop processing, false to continue */ typedef bool (bfcp_attr_h)(const struct bfcp_attr *attr, void *arg); /** * Defines the BFCP receive handler * * @param msg BFCP message * @param arg Handler argument */ typedef void (bfcp_recv_h)(const struct bfcp_msg *msg, void *arg); /** * Defines the BFCP response handler * * @param err Error code * @param msg BFCP message * @param arg Handler argument */ typedef void (bfcp_resp_h)(int err, const struct bfcp_msg *msg, void *arg); /* attr */ int bfcp_attrs_vencode(struct mbuf *mb, unsigned attrc, va_list *ap); int bfcp_attrs_encode(struct mbuf *mb, unsigned attrc, ...); struct bfcp_attr *bfcp_attr_subattr(const struct bfcp_attr *attr, enum bfcp_attrib type); struct bfcp_attr *bfcp_attr_subattr_apply(const struct bfcp_attr *attr, bfcp_attr_h *h, void *arg); int bfcp_attr_print(struct re_printf *pf, const struct bfcp_attr *attr); const char *bfcp_attr_name(enum bfcp_attrib type); const char *bfcp_reqstatus_name(enum bfcp_reqstat status); const char *bfcp_errcode_name(enum bfcp_err code); /* msg */ int bfcp_msg_vencode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim, uint32_t confid, uint16_t tid, uint16_t userid, unsigned attrc, va_list *ap); int bfcp_msg_encode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim, uint32_t confid, uint16_t tid, uint16_t userid, unsigned attrc, ...); int bfcp_msg_decode(struct bfcp_msg **msgp, struct mbuf *mb); struct bfcp_attr *bfcp_msg_attr(const struct bfcp_msg *msg, enum bfcp_attrib type); struct bfcp_attr *bfcp_msg_attr_apply(const struct bfcp_msg *msg, bfcp_attr_h *h, void *arg); int bfcp_msg_print(struct re_printf *pf, const struct bfcp_msg *msg); const char *bfcp_prim_name(enum bfcp_prim prim); /* conn */ /** * Defines the BFCP incoming connection handler * * @param peer Remote peer address * @param arg Handler argument */ typedef void (bfcp_conn_h)(const struct sa *peer, void *arg); /** * Defines the BFCP connection established handler * * @param arg Handler argument */ typedef void (bfcp_estab_h)(void *arg); /** * Defines the BFCP close handler for example for TCP connection * * @param err Error code * @param arg Handler argument */ typedef void (bfcp_close_h)(int err, void *arg); int bfcp_listen(struct bfcp_conn **bcp, enum bfcp_transp tp, struct sa *laddr, struct tls *tls, bfcp_conn_h *connh, bfcp_estab_h *estabh, bfcp_recv_h *recvh, bfcp_close_h *closeh, void *arg); int bfcp_connect(struct bfcp_conn **bcp, enum bfcp_transp tp, struct sa *laddr, const struct sa *peer, bfcp_estab_h *estabh, bfcp_recv_h *recvh, bfcp_close_h *closeh, void *arg); int bfcp_accept(struct bfcp_conn *bc); void bfcp_reject(struct bfcp_conn *bc); void *bfcp_sock(const struct bfcp_conn *bc); /* request */ int bfcp_request(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver, enum bfcp_prim prim, uint32_t confid, uint16_t userid, bfcp_resp_h *resph, void *arg, unsigned attrc, ...); /* notify */ int bfcp_notify(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver, enum bfcp_prim prim, uint32_t confid, uint16_t userid, unsigned attrc, ...); /* reply */ int bfcp_reply(struct bfcp_conn *bc, const struct bfcp_msg *req, enum bfcp_prim prim, unsigned attrc, ...); int bfcp_edreply(struct bfcp_conn *bc, const struct bfcp_msg *req, enum bfcp_err code, const uint8_t *details, size_t len); int bfcp_ereply(struct bfcp_conn *bc, const struct bfcp_msg *req, enum bfcp_err code); ================================================ FILE: include/re_btrace.h ================================================ /** * @file re_btrace.h Backtrace API * */ #define BTRACE_SZ 16 struct btrace { void *stack[BTRACE_SZ]; size_t len; }; int btrace_print(struct re_printf *pf, struct btrace *bt); int btrace_println(struct re_printf *pf, struct btrace *bt); int btrace_print_json(struct re_printf *pf, struct btrace *bt); #if defined(HAVE_EXECINFO) && !defined(RELEASE) #include static inline int btrace(struct btrace *bt) { if (!bt) return EINVAL; bt->len = backtrace(bt->stack, BTRACE_SZ); return 0; } #elif defined(WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include static inline int btrace(struct btrace *bt) { if (!bt) return EINVAL; bt->len = CaptureStackBackTrace(0, BTRACE_SZ, bt->stack, NULL); return 0; } #else static inline int btrace(struct btrace *bt) { (void)bt; return 0; } #endif ================================================ FILE: include/re_conf.h ================================================ /** * @file re_conf.h Interface to configuration * * Copyright (C) 2010 Creytiv.com */ struct conf; typedef int (conf_h)(const struct pl *val, void *arg); int conf_alloc(struct conf **confp, const char *filename); int conf_alloc_buf(struct conf **confp, const uint8_t *buf, size_t sz); int conf_get(const struct conf *conf, const char *name, struct pl *pl); int conf_get_str(const struct conf *conf, const char *name, char *str, size_t size); int conf_get_u32(const struct conf *conf, const char *name, uint32_t *num); int conf_get_i32(const struct conf *conf, const char *name, int32_t *num); int conf_get_float(const struct conf *conf, const char *name, double *num); int conf_get_bool(const struct conf *conf, const char *name, bool *val); int conf_apply(const struct conf *conf, const char *name, conf_h *ch, void *arg); ================================================ FILE: include/re_convert.h ================================================ /** * @file re_convert.h Conversion helpers * * Copyright (C) 2022 Sebastian Reimers */ #include static inline int try_into_u16_from_size(uint16_t *dest, const size_t src) { *dest = 0; if (src > UINT16_MAX) return ERANGE; *dest = (uint16_t)src; return 0; } static inline int try_into_u16_from_int(uint16_t *dest, const int src) { *dest = 0; if (src > UINT16_MAX) return ERANGE; if (src < 0) return ERANGE; *dest = (uint16_t)src; return 0; } static inline int try_into_int_from_size(int *dest, const size_t src) { *dest = 0; if (src > INT_MAX) return ERANGE; *dest = (int)src; return 0; } static inline int try_into_err(void *dest, ...) { (void)dest; return ENOTSUP; } #if __STDC_VERSION__ >= 201112L /* Needs C11 support */ /** * Try to convert safely from one type (src) into another (dest). * Types are auto detected. * * @param dest Destination * @param src Source value * * @return 0 if success, ERANGE if value overflow and ENOTSUP if not supported */ #define try_into(dest, src) \ _Generic((dest), \ uint16_t: _Generic((src), \ size_t: try_into_u16_from_size, \ int: try_into_u16_from_int, \ default: try_into_err \ ), \ int: _Generic((src), \ size_t: try_into_int_from_size, \ default: try_into_err \ ) \ ) \ (&(dest), (src)) #endif ================================================ FILE: include/re_crc32.h ================================================ /** * @file re_crc32.h Interface to CRC-32 functions * * Copyright (C) 2010 Creytiv.com */ uint32_t re_crc32(uint32_t crc, const void *buf, uint32_t size); ================================================ FILE: include/re_dbg.h ================================================ /** * @file re_dbg.h Interface to debugging module * * Copyright (C) 2010 Creytiv.com */ #ifdef __cplusplus extern "C" { #endif /** Debug levels */ enum { DBG_EMERG = 0, /**< System is unusable */ DBG_ALERT = 1, /**< Action must be taken immediately */ DBG_CRIT = 2, /**< Critical conditions */ DBG_ERR = 3, /**< Error conditions */ DBG_WARNING = 4, /**< Warning conditions */ DBG_NOTICE = 5, /**< Normal but significant condition */ DBG_INFO = 6, /**< Informational */ DBG_DEBUG = 7 /**< Debug-level messages */ }; /** * @def DEBUG_MODULE * * Module name defined by application */ /** * @def DEBUG_LEVEL * * Debug level defined by application */ #ifndef DEBUG_MODULE # warning "DEBUG_MODULE is not defined" #define DEBUG_MODULE "?" #endif #ifndef DEBUG_LEVEL # warning "DEBUG_LEVEL is not defined" #define DEBUG_LEVEL 7 #endif /** * @def DEBUG_WARNING(...) * * Print warning message */ #if (DEBUG_LEVEL >= 4) #define DEBUG_WARNING(...) \ dbg_printf(DBG_WARNING, DEBUG_MODULE ": " __VA_ARGS__) #else #define DEBUG_WARNING(...) #endif /** * @def DEBUG_NOTICE(...) * * Print notice message */ #if (DEBUG_LEVEL >= 5) #define DEBUG_NOTICE(...) \ dbg_printf(DBG_NOTICE, DEBUG_MODULE ": " __VA_ARGS__) #else #define DEBUG_NOTICE(...) #endif /** * @def DEBUG_INFO(...) * * Print info message */ #if (DEBUG_LEVEL >= 6) #define DEBUG_INFO(...) \ dbg_printf(DBG_INFO, DEBUG_MODULE ": " __VA_ARGS__) #else #define DEBUG_INFO(...) #endif /** * @def DEBUG_PRINTF(...) * * Print debug message */ #if (DEBUG_LEVEL >= 7) #define DEBUG_PRINTF(...) \ dbg_printf(DBG_DEBUG, DEBUG_MODULE ": " __VA_ARGS__) #else #define DEBUG_PRINTF(...) #endif /** Debug flags */ enum dbg_flags { DBG_NONE = 0, /**< No debug flags */ DBG_TIME = 1<<0, /**< Print timestamp flag */ DBG_ANSI = 1<<1, /**< Print ANSI color codes */ DBG_ALL = DBG_TIME|DBG_ANSI /**< All flags enabled */ }; /** * Defines the debug print handler * * @param level Debug level * @param p Debug string * @param len String length * @param arg Handler argument */ typedef void (dbg_print_h)(int level, const char *p, size_t len, void *arg); void dbg_init(int level, enum dbg_flags flags); void dbg_handler_set(dbg_print_h *ph, void *arg); void dbg_printf(int level, const char *fmt, ...); const char *dbg_level_str(int level); #ifdef __cplusplus } #endif ================================================ FILE: include/re_dd.h ================================================ /** * @file re_dd.h Dependency Descriptor (DD) * * Copyright (C) 2010 - 2023 Alfred E. Heggestad */ /* * Put bits wrapper (XXX: move to common place) */ struct putbit { struct mbuf *mb; size_t bit_pos; }; void putbit_init(struct putbit *pb, struct mbuf *mb); int putbit_one(struct putbit *pb, unsigned bit); int putbit_write(struct putbit *pb, unsigned count, unsigned val); int putbit_write_ns(struct putbit *pb, unsigned n, unsigned v); /* * Dependency Descriptor (DD) * * DT: Decode Target * DTI: Decode Target Indication * SID: Spatial ID * TID: Temporal ID */ /* Constants. */ enum { DD_MAX_SPATIAL_IDS = 4u, DD_MAX_TEMPORAL_IDS = 4u, DD_MAX_TEMPLATES = 8u, DD_MAX_FDIFFS = 16u, DD_MAX_DECODE_TARGETS= 16u, DD_MAX_CHAINS = 32u, }; /* Decode Target Indication (DTI) */ enum dd_dti { DD_DTI_NOT_PRESENT = 0, DD_DTI_DISCARDABLE = 1, DD_DTI_SWITCH = 2, DD_DTI_REQUIRED = 3, }; enum dd_next_layer_idc { DD_SAME_LAYER = 0, DD_NEXT_TEMPORAL_LAYER = 1, DD_NEXT_SPATIAL_LAYER = 2, DD_NO_MORE_TEMPLATES = 3, }; /* * https://aomediacodec.github.io/av1-rtp-spec/ * #dependency-descriptor-rtp-header-extension */ struct dd { /* Mandatory Descriptor Fields */ unsigned start_of_frame:1; unsigned end_of_frame:1; unsigned frame_dependency_template_id:6; uint16_t frame_number; bool ext; unsigned template_dependency_structure_present_flag:1; unsigned active_decode_targets_present_flag:1; unsigned custom_dtis_flag:1; unsigned custom_fdiffs_flag:1; unsigned custom_chains_flag:1; unsigned active_decode_targets_bitmask; unsigned template_id_offset:6; uint8_t dt_cnt; uint8_t template_cnt; uint8_t max_spatial_id; uint8_t template_spatial_id[DD_MAX_TEMPLATES]; uint8_t template_temporal_id[DD_MAX_TEMPLATES]; /* render_resolutions */ bool resolutions_present_flag; uint16_t max_render_width_minus_1[DD_MAX_SPATIAL_IDS]; uint16_t max_render_height_minus_1[DD_MAX_SPATIAL_IDS]; uint8_t render_count; /* type: enum dd_dti */ uint8_t template_dti[DD_MAX_TEMPLATES][DD_MAX_DECODE_TARGETS]; /* template fdiffs */ uint8_t template_fdiff[DD_MAX_TEMPLATES][DD_MAX_FDIFFS]; uint8_t template_fdiff_cnt[DD_MAX_TEMPLATES]; /* template chains */ uint8_t decode_target_protected_by[DD_MAX_DECODE_TARGETS]; uint8_t template_chain_fdiff[DD_MAX_TEMPLATES][DD_MAX_CHAINS]; uint8_t chain_cnt; }; int dd_encode(struct mbuf *mb, const struct dd *dd); int dd_decode(struct dd *dd, const uint8_t *buf, size_t sz); int dd_print(struct re_printf *pf, const struct dd *dd); ================================================ FILE: include/re_dns.h ================================================ /** * @file re_dns.h Interface to DNS module * * Copyright (C) 2010 Creytiv.com */ enum { DNS_PORT = 53, DNS_HEADER_SIZE = 12 }; /** DNS Opcodes */ enum { DNS_OPCODE_QUERY = 0, DNS_OPCODE_IQUERY = 1, DNS_OPCODE_STATUS = 2, DNS_OPCODE_NOTIFY = 4 }; /** DNS Response codes */ enum { DNS_RCODE_OK = 0, DNS_RCODE_FMT_ERR = 1, DNS_RCODE_SRV_FAIL = 2, DNS_RCODE_NAME_ERR = 3, DNS_RCODE_NOT_IMPL = 4, DNS_RCODE_REFUSED = 5, DNS_RCODE_NOT_AUTH = 9 }; /** DNS Resource Record types */ enum { DNS_TYPE_A = 0x0001, DNS_TYPE_NS = 0x0002, DNS_TYPE_CNAME = 0x0005, DNS_TYPE_SOA = 0x0006, DNS_TYPE_PTR = 0x000c, DNS_TYPE_MX = 0x000f, DNS_TYPE_TXT = 0x0010, DNS_TYPE_AAAA = 0x001c, DNS_TYPE_SRV = 0x0021, DNS_TYPE_NAPTR = 0x0023, DNS_QTYPE_IXFR = 0x00fb, DNS_QTYPE_AXFR = 0x00fc, DNS_QTYPE_ANY = 0x00ff }; /** DNS Classes */ enum { DNS_CLASS_IN = 0x0001, DNS_QCLASS_ANY = 0x00ff }; /** Defines a DNS Header */ struct dnshdr { uint16_t id; bool qr; uint8_t opcode; bool aa; bool tc; bool rd; bool ra; uint8_t z; uint8_t rcode; uint16_t nq; uint16_t nans; uint16_t nauth; uint16_t nadd; }; /** Defines a DNS Resource Record (RR) */ struct dnsrr { struct le le; struct le le_priv; char *name; uint16_t type; uint16_t dnsclass; int64_t ttl; uint16_t rdlen; union { struct { uint32_t addr; } a; struct { char *nsdname; } ns; struct { char *cname; } cname; struct { char *mname; char *rname; uint32_t serial; uint32_t refresh; uint32_t retry; uint32_t expire; uint32_t ttlmin; } soa; struct { char *ptrdname; } ptr; struct { uint16_t pref; char *exchange; } mx; struct { char *data; } txt; struct { uint8_t addr[16]; } aaaa; struct { uint16_t pri; uint16_t weight; uint16_t port; char *target; } srv; struct { uint16_t order; uint16_t pref; char *flags; char *services; char *regexp; char *replace; } naptr; } rdata; }; struct hash; /** * Defines the DNS Query handler * * @param err 0 if success, otherwise errorcode * @param hdr DNS Header * @param ansl List of Answer records * @param authl List of Authoritative records * @param addl List of Additional records * @param arg Handler argument */ typedef void(dns_query_h)(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg); /** * Defines the DNS Resource Record list handler * * @param rr DNS Resource Record * @param arg Handler argument * * @return True to stop traversing, False to continue */ typedef bool(dns_rrlist_h)(struct dnsrr *rr, void *arg); int dns_hdr_encode(struct mbuf *mb, const struct dnshdr *hdr); int dns_hdr_decode(struct mbuf *mb, struct dnshdr *hdr); const char *dns_hdr_opcodename(uint8_t opcode); const char *dns_hdr_rcodename(uint8_t rcode); struct dnsrr *dns_rr_alloc(void); int dns_rr_dup(struct dnsrr **rrp, const struct dnsrr *rr); int dns_rr_encode(struct mbuf *mb, const struct dnsrr *rr, int64_t ttl_offs, struct hash *ht_dname, size_t start); int dns_rr_decode(struct mbuf *mb, struct dnsrr **rr, size_t start); bool dns_rr_cmp(const struct dnsrr *rr1, const struct dnsrr *rr2, bool rdata); const char *dns_rr_typename(uint16_t type); const char *dns_rr_classname(uint16_t dnsclass); int dns_rr_print(struct re_printf *pf, const struct dnsrr *rr); int dns_dname_encode(struct mbuf *mb, const char *name, struct hash *ht_dname, size_t start, bool comp); int dns_dname_decode(struct mbuf *mb, char **name, size_t start); int dns_cstr_encode(struct mbuf *mb, const char *str); int dns_cstr_decode(struct mbuf *mb, char **str); void dns_rrlist_sort(struct list *rrl, uint16_t type, size_t key); void dns_rrlist_sort_addr(struct list *rrl, size_t key); struct dnsrr *dns_rrlist_apply(struct list *rrl, const char *name, uint16_t type, uint16_t dnsclass, bool recurse, dns_rrlist_h *rrlh, void *arg); struct dnsrr *dns_rrlist_apply2(struct list *rrl, const char *name, uint16_t type1, uint16_t type2, uint16_t dnsclass, bool recurse, dns_rrlist_h *rrlh, void *arg); struct dnsrr *dns_rrlist_find(struct list *rrl, const char *name, uint16_t type, uint16_t dnsclass, bool recurse); /* DNS Client */ struct sa; struct dnsc; struct dns_query; /** DNS Client configuration */ struct dnsc_conf { uint32_t query_hash_size; uint32_t tcp_hash_size; uint32_t conn_timeout; /* in [ms] */ uint32_t idle_timeout; /* in [ms] */ uint32_t cache_ttl_max; /* in [s] 0 for disabled */ bool getaddrinfo; /* use getaddrinfo (by default disabled) */ }; int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf, const struct sa *srvv, uint32_t srvc); int dnsc_conf_set(struct dnsc *dnsc, const struct dnsc_conf *conf); void dnsc_conf_set_timeout(struct dnsc *dnsc, uint32_t connect, uint32_t idle); int dnsc_srv_set(struct dnsc *dnsc, const struct sa *srvv, uint32_t srvc); int dnsc_query(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, bool rd, dns_query_h *qh, void *arg); int dnsc_query_srv(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, int proto, const struct sa *srvv, const uint32_t *srvc, bool rd, dns_query_h *qh, void *arg); int dnsc_notify(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, const struct dnsrr *ans_rr, int proto, const struct sa *srvv, const uint32_t *srvc, dns_query_h *qh, void *arg); void dnsc_cache_flush(struct dnsc *dnsc); void dnsc_cache_max(struct dnsc *dnsc, uint32_t max); void dnsc_getaddrinfo(struct dnsc *dnsc, bool active); bool dnsc_getaddrinfo_enabled(struct dnsc *dnsc); bool dnsc_getaddrinfo_only(const struct dnsc *dnsc); /* DNS System functions */ int dns_srv_get(char *domain, size_t dsize, struct sa *srvv, uint32_t *n); ================================================ FILE: include/re_fmt.h ================================================ /** * @file re_fmt.h Interface to formatted text functions * * Copyright (C) 2010 Creytiv.com */ #include #include enum { ITOA_BUFSZ = 34, }; struct mbuf; /** Defines a pointer-length string type */ struct pl { const char *p; /**< Pointer to string */ size_t l; /**< Length of string */ }; /** Initialise a pointer-length object from a constant string */ #define PL(s) {(s), sizeof((s))-1} /** Pointer-length Initializer */ #define PL_INIT {NULL, 0} extern const struct pl pl_null; struct pl *pl_alloc_str(const char *str); struct pl *pl_alloc_dup(const struct pl *src); void pl_set_str(struct pl *pl, const char *str); void pl_set_mbuf(struct pl *pl, const struct mbuf *mb); int32_t pl_i32(const struct pl *pl); int64_t pl_i64(const struct pl *pl); uint32_t pl_u32(const struct pl *pl); uint32_t pl_x32(const struct pl *pl); uint64_t pl_u64(const struct pl *pl); uint64_t pl_x64(const struct pl *pl); double pl_float(const struct pl *pl); int pl_bool(bool *val, const struct pl *pl); int pl_hex(const struct pl *pl, uint8_t *hex, size_t len); bool pl_isset(const struct pl *pl); int pl_strcpy(const struct pl *pl, char *str, size_t size); int pl_strdup(char **dst, const struct pl *src); int pl_dup(struct pl *dst, const struct pl *src); int pl_strcmp(const struct pl *pl, const char *str); int pl_strncmp(const struct pl *pl, const char *str, size_t n); int pl_strncasecmp(const struct pl *pl, const char *str, size_t n); int pl_strcasecmp(const struct pl *pl, const char *str); int pl_cmp(const struct pl *pl1, const struct pl *pl2); int pl_casecmp(const struct pl *pl1, const struct pl *pl2); const char *pl_strchr(const struct pl *pl, char c); const char *pl_strrchr(const struct pl *pl, char c); const char *pl_strstr(const struct pl *pl, const char *str); int pl_trim(struct pl *pl); int pl_ltrim(struct pl *pl); int pl_rtrim(struct pl *pl); void pl_strip_html(struct pl *pl); /** Advance pl position/length by +/- N bytes */ static inline void pl_advance(struct pl *pl, ssize_t n) { pl->p += n; pl->l -= n; } /* Formatted printing */ /** * Defines the re_vhprintf() print handler * * @param p String to print * @param size Size of string to print * @param arg Handler argument * * @return 0 for success, otherwise errorcode */ typedef int(re_vprintf_h)(const char *p, size_t size, void *arg); /** Defines a print backend */ struct re_printf { re_vprintf_h *vph; /**< Print handler */ void *arg; /**< Handler argument */ }; /** * Defines the %H print handler * * @param pf Print backend * @param arg Handler argument * * @return 0 for success, otherwise errorcode */ typedef int(re_printf_h)(struct re_printf *pf, void *arg); int re_vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg); int re_vfprintf(FILE *stream, const char *fmt, va_list ap); int re_vprintf(const char *fmt, va_list ap); int re_vsnprintf(char *re_restrict str, size_t size, const char *re_restrict fmt, va_list ap); int re_vsdprintf(char **strp, const char *fmt, va_list ap); /* Secure va_list print */ int re_vhprintf_s(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg); int re_vfprintf_s(FILE *stream, const char *fmt, va_list ap); int re_vprintf_s(const char *fmt, va_list ap); int re_vsnprintf_s(char *re_restrict str, size_t size, const char *re_restrict fmt, va_list ap); int re_vsdprintf_s(char **strp, const char *fmt, va_list ap); #ifdef HAVE_RE_ARG #define re_printf(fmt, ...) _re_printf_s((fmt), RE_VA_ARGS(__VA_ARGS__)) #define re_hprintf(pf, fmt, ...) \ _re_hprintf_s((pf), (fmt), RE_VA_ARGS(__VA_ARGS__)) #define re_fprintf(stream, fmt, ...) \ _re_fprintf_s((stream), (fmt), RE_VA_ARGS(__VA_ARGS__)) #define re_snprintf(str, size, fmt, ...) \ _re_snprintf_s((str), (size), (fmt), RE_VA_ARGS(__VA_ARGS__)) #define re_sdprintf(strp, fmt, ...) \ _re_sdprintf_s((strp), (fmt), RE_VA_ARGS(__VA_ARGS__)) #else #define re_printf(...) _re_printf(__VA_ARGS__) #define re_hprintf _re_hprintf #define re_fprintf _re_fprintf #define re_snprintf _re_snprintf #define re_sdprintf _re_sdprintf #endif int _re_printf(const char *fmt, ...); int _re_hprintf(struct re_printf *pf, const char *fmt, ...); int _re_fprintf(FILE *stream, const char *fmt, ...); int _re_snprintf(char *re_restrict str, size_t size, const char *re_restrict fmt, ...); int _re_sdprintf(char **strp, const char *fmt, ...); int _re_printf_s(const char *fmt, ...); int _re_hprintf_s(struct re_printf *pf, const char *fmt, ...); int _re_fprintf_s(FILE *stream, const char *fmt, ...); int _re_snprintf_s(char *re_restrict str, size_t size, const char *re_restrict fmt, ...); int _re_sdprintf_s(char **strp, const char *fmt, ...); /* Regular expressions */ int re_regex(const char *ptr, size_t len, const char *expr, ...); /* Character functions */ uint8_t ch_hex(char ch); /* String functions */ int str_bool(bool *val, const char *str); int str_hex(uint8_t *hex, size_t len, const char *str); void str_ncpy(char *dst, const char *src, size_t n); int str_dup(char **dst, const char *src); int str_x64dup(char **dst, uint64_t val); int str_cmp(const char *s1, const char *s2); int str_ncmp(const char *s1, const char *s2, size_t n); const char *str_str(const char *s1, const char *s2); int str_casecmp(const char *s1, const char *s2); size_t str_len(const char *s); const char *str_error(int errnum, char *buf, size_t sz); char *str_itoa(uint32_t val, char *buf, int base); wchar_t *str_wchar(const char *c); /** * Check if string is set * * @param s Zero-terminated string * * @return true if set, false if not set */ static inline bool str_isset(const char *s) { return s && s[0] != '\0'; } /* time */ int fmt_gmtime(struct re_printf *pf, void *ts); int fmt_timestamp(struct re_printf *pf, void *ts); int fmt_timestamp_us(struct re_printf *pf, void *arg); int fmt_human_time(struct re_printf *pf, const uint32_t *seconds); void hexdump(FILE *f, const void *p, size_t len); /* param */ typedef void (fmt_param_h)(const struct pl *name, const struct pl *val, void *arg); bool fmt_param_exists(const struct pl *pl, const char *pname); bool fmt_param_sep_get(const struct pl *pl, const char *pname, char sep, struct pl *val); bool fmt_param_get(const struct pl *pl, const char *pname, struct pl *val); void fmt_param_apply(const struct pl *pl, fmt_param_h *ph, void *arg); /* unicode */ int utf8_encode(struct re_printf *pf, const char *str); int utf8_decode(struct re_printf *pf, const struct pl *pl); size_t utf8_byteseq(char u[4], unsigned cp); /* text2pcap */ struct re_text2pcap { bool in; const struct mbuf *mb; const char *id; }; int re_text2pcap(struct re_printf *pf, struct re_text2pcap *pcap); void re_text2pcap_trace(const char *name, const char *id, bool in, const struct mbuf *mb); ================================================ FILE: include/re_h264.h ================================================ /** * @file re_h264.h Interface to H.264 header parser * * Copyright (C) 2010 Creytiv.com */ /** NAL unit types */ enum h264_nalu { H264_NALU_SLICE = 1, H264_NALU_DPA = 2, H264_NALU_DPB = 3, H264_NALU_DPC = 4, H264_NALU_IDR_SLICE = 5, H264_NALU_SEI = 6, H264_NALU_SPS = 7, H264_NALU_PPS = 8, H264_NALU_AUD = 9, H264_NALU_END_SEQUENCE = 10, H264_NALU_END_STREAM = 11, H264_NALU_FILLER_DATA = 12, H264_NALU_SPS_EXT = 13, H264_NALU_AUX_SLICE = 19, H264_NALU_STAP_A = 24, H264_NALU_STAP_B = 25, H264_NALU_MTAP16 = 26, H264_NALU_MTAP24 = 27, H264_NALU_FU_A = 28, H264_NALU_FU_B = 29, }; /** * H.264 NAL Header */ struct h264_nal_header { unsigned f:1; /**< Forbidden zero bit (must be 0) */ unsigned nri:2; /**< nal_ref_idc */ unsigned type:5; /**< NAL unit type */ }; int h264_nal_header_encode(struct mbuf *mb, const struct h264_nal_header *hdr); int h264_nal_header_decode(struct h264_nal_header *hdr, struct mbuf *mb); void h264_nal_header_decode_buf(struct h264_nal_header *hdr, const uint8_t *buf); const char *h264_nal_unit_name(enum h264_nalu nal_type); /** * H.264 Sequence Parameter Set (SPS) */ struct h264_sps { uint8_t profile_idc; uint8_t level_idc; uint8_t seq_parameter_set_id; /* 0-31 */ uint8_t chroma_format_idc; /* 0-3 */ unsigned log2_max_frame_num; unsigned pic_order_cnt_type; unsigned max_num_ref_frames; unsigned pic_width_in_mbs; unsigned pic_height_in_map_units; unsigned frame_crop_left_offset; /* pixels */ unsigned frame_crop_right_offset; /* pixels */ unsigned frame_crop_top_offset; /* pixels */ unsigned frame_crop_bottom_offset; /* pixels */ }; int h264_sps_decode(struct h264_sps *sps, const uint8_t *p, size_t len); void h264_sps_resolution(const struct h264_sps *sps, unsigned *width, unsigned *height); const char *h264_sps_chroma_format_name(uint8_t chroma_format_idc); typedef int (h264_packet_h)(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg); /** Fragmentation Unit header */ struct h264_fu { unsigned s:1; /**< Start bit */ unsigned e:1; /**< End bit */ unsigned r:1; /**< The Reserved bit MUST be equal to 0 */ unsigned type:5; /**< The NAL unit payload type */ }; int h264_fu_hdr_encode(const struct h264_fu *fu, struct mbuf *mb); int h264_fu_hdr_decode(struct h264_fu *fu, struct mbuf *mb); const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end); int h264_packetize(uint64_t rtp_ts, const uint8_t *buf, size_t len, size_t pktsize, h264_packet_h *pkth, void *arg); int h264_nal_send(bool first, bool last, bool marker, uint32_t ihdr, uint64_t rtp_ts, const uint8_t *buf, size_t size, size_t maxsz, h264_packet_h *pkth, void *arg); bool h264_is_keyframe(int type); int h264_stap_encode(struct mbuf *mb, const uint8_t *frame, size_t frame_sz); int h264_stap_decode_annexb(struct mbuf *mb_frame, struct mbuf *mb_pkt); int h264_stap_decode_annexb_long(struct mbuf *mb_frame, struct mbuf *mb_pkt); /* * Get bits wrapper */ struct getbit { const uint8_t *buf; size_t pos; size_t end; }; void getbit_init(struct getbit *gb, const uint8_t *buf, size_t size); size_t getbit_get_left(const struct getbit *gb); unsigned get_bit(struct getbit *gb); int get_ue_golomb(struct getbit *gb, unsigned *valp); unsigned get_bits(struct getbit *gb, unsigned n); unsigned getbit_read_ns(struct getbit *gb, unsigned n); ================================================ FILE: include/re_h265.h ================================================ /** * @file re_h265.h Interface to H.265 header parser * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ enum { H265_HDR_SIZE = 2 }; enum h265_naltype { /* VCL class */ H265_NAL_TRAIL_N = 0, H265_NAL_TRAIL_R = 1, H265_NAL_TSA_N = 2, H265_NAL_TSA_R = 3, H265_NAL_STSA_N = 4, H265_NAL_STSA_R = 5, H265_NAL_RADL_N = 6, H265_NAL_RADL_R = 7, H265_NAL_RASL_N = 8, H265_NAL_RASL_R = 9, H265_NAL_BLA_W_LP = 16, H265_NAL_BLA_W_RADL = 17, H265_NAL_BLA_N_LP = 18, H265_NAL_IDR_W_RADL = 19, H265_NAL_IDR_N_LP = 20, H265_NAL_CRA_NUT = 21, H265_NAL_RSV_IRAP_VCL22 = 22, H265_NAL_RSV_IRAP_VCL23 = 23, /* non-VCL class */ H265_NAL_VPS_NUT = 32, H265_NAL_SPS_NUT = 33, H265_NAL_PPS_NUT = 34, H265_NAL_AUD = 35, H265_NAL_EOS_NUT = 36, /* End of sequence */ H265_NAL_EOB_NUT = 37, /* End of bitstream */ H265_NAL_FD_NUT = 38, /* Filler data */ H265_NAL_PREFIX_SEI_NUT = 39, H265_NAL_SUFFIX_SEI_NUT = 40, /* RFC 7798 */ H265_NAL_AP = 48, /* Aggregation Packets */ H265_NAL_FU = 49, }; struct h265_nal { unsigned nal_unit_type:6; /* NAL unit type (0-40) */ unsigned nuh_layer_id:6; /* layer identifier */ unsigned nuh_temporal_id_plus1:3; /* temporal identifier plus 1 */ }; void h265_nal_encode(uint8_t buf[2], unsigned nal_unit_type, unsigned nuh_temporal_id_plus1); int h265_nal_encode_mbuf(struct mbuf *mb, const struct h265_nal *nal); int h265_nal_decode(struct h265_nal *nal, const uint8_t *p); int h265_nal_print(struct re_printf *pf, const struct h265_nal *nal); const char *h265_nalunit_name(enum h265_naltype type); bool h265_is_keyframe(enum h265_naltype type); typedef int (h265_packet_h)(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg); int h265_packetize(uint64_t rtp_ts, const uint8_t *buf, size_t len, size_t pktsize, h265_packet_h *pkth, void *arg); ================================================ FILE: include/re_hash.h ================================================ /** * @file re_hash.h Interface to hashmap table * * Copyright (C) 2010 Creytiv.com */ struct hash; struct pl; int hash_alloc(struct hash **hp, uint32_t bsize); void hash_append(struct hash *h, uint32_t key, struct le *le, void *data); void hash_unlink(struct le *le); struct le *hash_lookup(const struct hash *h, uint32_t key, list_apply_h *ah, void *arg); struct le *hash_apply(const struct hash *h, list_apply_h *ah, void *arg); struct list *hash_list_idx(const struct hash *h, uint32_t i); struct list *hash_list(const struct hash *h, uint32_t key); uint32_t hash_bsize(const struct hash *h); void hash_flush(struct hash *h); void hash_clear(struct hash *h); uint32_t hash_valid_size(uint32_t size); int hash_debug(struct re_printf *pf, struct hash *h); /* Hash functions */ uint32_t hash_joaat(const uint8_t *key, size_t len); uint32_t hash_joaat_ci(const char *str, size_t len); uint32_t hash_joaat_str(const char *str); uint32_t hash_joaat_str_ci(const char *str); uint32_t hash_joaat_pl(const struct pl *pl); uint32_t hash_joaat_pl_ci(const struct pl *pl); uint32_t hash_fast(const char *k, size_t len); uint32_t hash_fast_str(const char *str); ================================================ FILE: include/re_hmac.h ================================================ /** * @file re_hmac.h Interface to HMAC functions * * Copyright (C) 2010 Creytiv.com */ void hmac_sha1(const uint8_t *k, /* secret key */ size_t lk, /* length of the key in bytes */ const uint8_t *d, /* data */ size_t ld, /* length of data in bytes */ uint8_t* out, /* output buffer, at least "t" bytes */ size_t t); void hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len, uint8_t *out, size_t out_len); enum hmac_hash { HMAC_HASH_SHA1, HMAC_HASH_SHA256 }; struct hmac; int hmac_create(struct hmac **hmacp, enum hmac_hash hash, const uint8_t *key, size_t key_len); int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len, const uint8_t *data, size_t data_len); ================================================ FILE: include/re_http.h ================================================ /** * @file re_http.h Hypertext Transfer Protocol * * Copyright (C) 2010 Creytiv.com */ /* forward declarations */ struct tls; /** HTTP Header ID (perfect hash value) */ enum http_hdrid { HTTP_HDR_ACCEPT = 3186, HTTP_HDR_ACCEPT_CHARSET = 24, HTTP_HDR_ACCEPT_ENCODING = 708, HTTP_HDR_ACCEPT_LANGUAGE = 2867, HTTP_HDR_ACCEPT_RANGES = 3027, HTTP_HDR_AGE = 742, HTTP_HDR_ALLOW = 2429, HTTP_HDR_AUTHORIZATION = 2503, HTTP_HDR_CACHE_CONTROL = 2530, HTTP_HDR_CONNECTION = 865, HTTP_HDR_CONTENT_ENCODING = 580, HTTP_HDR_CONTENT_LANGUAGE = 3371, HTTP_HDR_CONTENT_LENGTH = 3861, HTTP_HDR_CONTENT_LOCATION = 3927, HTTP_HDR_CONTENT_MD5 = 406, HTTP_HDR_CONTENT_RANGE = 2846, HTTP_HDR_CONTENT_TYPE = 809, HTTP_HDR_DATE = 1027, HTTP_HDR_ETAG = 2392, HTTP_HDR_EXPECT = 1550, HTTP_HDR_EXPIRES = 1983, HTTP_HDR_FROM = 1963, HTTP_HDR_HOST = 3191, HTTP_HDR_IF_MATCH = 2684, HTTP_HDR_IF_MODIFIED_SINCE = 2187, HTTP_HDR_IF_NONE_MATCH = 4030, HTTP_HDR_IF_RANGE = 2220, HTTP_HDR_IF_UNMODIFIED_SINCE = 962, HTTP_HDR_LAST_MODIFIED = 2946, HTTP_HDR_LOCATION = 2514, HTTP_HDR_MAX_FORWARDS = 3549, HTTP_HDR_PRAGMA = 1673, HTTP_HDR_PROXY_AUTHENTICATE = 116, HTTP_HDR_PROXY_AUTHORIZATION = 2363, HTTP_HDR_RANGE = 4004, HTTP_HDR_REFERER = 2991, HTTP_HDR_RETRY_AFTER = 409, HTTP_HDR_SEC_WEBSOCKET_ACCEPT = 2959, HTTP_HDR_SEC_WEBSOCKET_EXTENSIONS = 2937, HTTP_HDR_SEC_WEBSOCKET_KEY = 746, HTTP_HDR_SEC_WEBSOCKET_PROTOCOL = 2076, HTTP_HDR_SEC_WEBSOCKET_VERSION = 3158, HTTP_HDR_SERVER = 973, HTTP_HDR_TE = 2035, HTTP_HDR_TRAILER = 2577, HTTP_HDR_TRANSFER_ENCODING = 2115, HTTP_HDR_UPGRADE = 717, HTTP_HDR_USER_AGENT = 4064, HTTP_HDR_VARY = 3076, HTTP_HDR_VIA = 3961, HTTP_HDR_WARNING = 2108, HTTP_HDR_WWW_AUTHENTICATE = 2763, HTTP_HDR_NONE = -1 }; /** HTTP Header */ struct http_hdr { struct le le; /**< Linked-list element */ struct pl name; /**< HTTP Header name */ struct pl val; /**< HTTP Header value */ enum http_hdrid id; /**< HTTP Header id (unique) */ }; /** HTTP Message */ struct http_msg { struct pl ver; /**< HTTP Version number */ struct pl met; /**< Request Method */ struct pl path; /**< Request path/resource */ struct pl prm; /**< Request parameters */ uint16_t scode; /**< Response Status code */ struct pl reason; /**< Response Reason phrase */ struct list hdrl; /**< List of HTTP headers (struct http_hdr) */ struct msg_ctype ctyp; /**< Content-type */ struct mbuf *_mb; /**< Buffer containing the HTTP message */ struct mbuf *mb; /**< Buffer containing the HTTP body */ uint32_t clen; /**< Content length */ }; struct http_uri { struct pl scheme; struct pl host; struct pl port; struct pl path; }; int http_uri_decode(struct http_uri *hu, const struct pl *uri); /** Http Client configuration */ struct http_conf { uint32_t conn_timeout; /* in [ms] */ uint32_t recv_timeout; /* in [ms] */ uint32_t idle_timeout; /* in [ms] */ }; typedef bool(http_hdr_h)(const struct http_hdr *hdr, void *arg); int http_msg_decode(struct http_msg **msgp, struct mbuf *mb, bool req); const struct http_hdr *http_msg_hdr(const struct http_msg *msg, enum http_hdrid id); const struct http_hdr *http_msg_hdr_apply(const struct http_msg *msg, bool fwd, enum http_hdrid id, http_hdr_h *h, void *arg); const struct http_hdr *http_msg_xhdr(const struct http_msg *msg, const char *name); const struct http_hdr *http_msg_xhdr_apply(const struct http_msg *msg, bool fwd, const char *name, http_hdr_h *h, void *arg); uint32_t http_msg_hdr_count(const struct http_msg *msg, enum http_hdrid id); uint32_t http_msg_xhdr_count(const struct http_msg *msg, const char *name); bool http_msg_hdr_has_value(const struct http_msg *msg, enum http_hdrid id, const char *value); bool http_msg_xhdr_has_value(const struct http_msg *msg, const char *name, const char *value); int http_msg_print(struct re_printf *pf, const struct http_msg *msg); /* Client */ struct http_cli; struct http_req; struct dnsc; struct tcp_conn; struct tls_conn; typedef void (http_resp_h)(int err, const struct http_msg *msg, void *arg); typedef int (http_data_h)(const uint8_t *buf, size_t size, const struct http_msg *msg, void *arg); typedef void (http_conn_h)(struct tcp_conn *tc, struct tls_conn *sc, void *arg); typedef size_t (http_bodyh)(struct mbuf *mb, void *arg); int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc); int http_client_set_config(struct http_cli *cli, struct http_conf *conf); int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, http_resp_h *resph, http_data_h *datah, http_bodyh *bodyh, void *arg, const char *fmt, ...); int http_request_addr(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, const struct sa *addr, http_resp_h *resph, http_data_h *datah, http_bodyh *bodyh, void *arg, const char *fmt, ...); void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh); void http_client_set_laddr(struct http_cli *cli, const struct sa *addr); void http_client_set_laddr6(struct http_cli *cli, const struct sa *addr); void http_client_set_bufsize_max(struct http_cli *cli, size_t max_size); size_t http_client_get_bufsize_max(struct http_cli *cli); #ifdef USE_TLS int http_client_set_tls(struct http_cli *cli, struct tls *tls); int http_client_get_tls(struct http_cli *cli, struct tls **tls); int http_client_add_ca(struct http_cli *cli, const char *tls_ca); int http_client_add_capem(struct http_cli *cli, const char *capem); int http_client_add_crlpem(struct http_cli *cli, const char *pem); int http_client_set_tls_hostname(struct http_cli *cli, const struct pl *hostname); int http_client_set_cert(struct http_cli *cli, const char *path); int http_client_set_certpem(struct http_cli *cli, const char *pem); int http_client_set_key(struct http_cli *cli, const char *path); int http_client_set_keypem(struct http_cli *cli, const char *pem); int http_client_use_chain(struct http_cli *cli, const char *path); int http_client_use_chainpem(struct http_cli *cli, const char *chain, int len_chain); int http_client_set_session_reuse(struct http_cli *cli, bool enabled); int http_client_set_tls_min_version(struct http_cli *cli, int version); int http_client_set_tls_max_version(struct http_cli *cli, int version); int http_client_disable_verify_server(struct http_cli *cli); #endif /* Server */ struct http_sock; struct http_conn; enum re_https_verify_msg { HTTPS_MSG_OK = 0, HTTPS_MSG_REQUEST_CERT = 1, HTTPS_MSG_IGNORE = 2, }; typedef void (http_req_h)(struct http_conn *conn, const struct http_msg *msg, void *arg); typedef enum re_https_verify_msg (https_verify_msg_h)(struct http_conn *conn, const struct http_msg *msg, void *arg); int http_listen_fd(struct http_sock **sockp, re_sock_t fd, http_req_h *reqh, void *arg); int http_listen(struct http_sock **sockp, const struct sa *laddr, http_req_h *reqh, void *arg); int https_listen(struct http_sock **sockp, const struct sa *laddr, const char *cert, http_req_h *reqh, void *arg); int https_set_verify_msgh(struct http_sock *sock, https_verify_msg_h *verifyh); void http_set_max_body_size(struct http_sock *sock, size_t limit); struct tcp_sock *http_sock_tcp(struct http_sock *sock); struct tls *http_sock_tls(const struct http_sock *conn); const struct sa *http_conn_peer(const struct http_conn *conn); struct tcp_conn *http_conn_tcp(struct http_conn *conn); struct tls_conn *http_conn_tls(struct http_conn *conn); void http_conn_reset_timeout(struct http_conn *conn); void http_conn_close(struct http_conn *conn); int http_reply(struct http_conn *conn, uint16_t scode, const char *reason, const char *fmt, ...); int http_creply(struct http_conn *conn, uint16_t scode, const char *reason, const char *ctype, const char *fmt, ...); int http_ereply(struct http_conn *conn, uint16_t scode, const char *reason); /* Authentication */ struct http_auth { const char *realm; bool stale; }; typedef int (http_auth_h)(const struct pl *username, uint8_t *ha1, void *arg); int http_auth_print_challenge(struct re_printf *pf, const struct http_auth *auth); bool http_auth_check(const struct pl *hval, const struct pl *method, struct http_auth *auth, http_auth_h *authh, void *arg); bool http_auth_check_request(const struct http_msg *msg, struct http_auth *auth, http_auth_h *authh, void *arg); /* http_reqconn - HTTP request connection */ struct http_reqconn; int http_reqconn_alloc(struct http_reqconn **pconn, struct http_cli *client, http_resp_h *resph, http_data_h *datah, void* arg); int http_reqconn_set_auth(struct http_reqconn *conn, const struct pl *user, const struct pl *pass); int http_reqconn_set_bearer(struct http_reqconn *conn, const struct pl *bearer); int http_reqconn_set_authtoken(struct http_reqconn *conn, const struct pl *token); int http_reqconn_set_tokentype(struct http_reqconn *conn, const struct pl *tokentype); int http_reqconn_set_method(struct http_reqconn *conn, const struct pl *met); int http_reqconn_set_body(struct http_reqconn *conn, struct mbuf *body); int http_reqconn_set_ctype(struct http_reqconn *conn, const struct pl *ctype); int http_reqconn_add_header(struct http_reqconn *conn, const struct pl *header); int http_reqconn_clr_header(struct http_reqconn *conn); int http_reqconn_send(struct http_reqconn *conn, const struct pl *uri); int http_reqconn_set_peer(struct http_reqconn *conn, const struct sa *peer); #ifdef USE_TLS int http_reqconn_set_tls_hostname(struct http_reqconn *conn, const struct pl *hostname); #endif int http_reqconn_set_req_bodyh(struct http_reqconn *conn, http_bodyh cb, uint64_t len); ================================================ FILE: include/re_httpauth.h ================================================ /** * @file re_httpauth.h Interface to HTTP Authentication * * Copyright (C) 2010 Creytiv.com */ /** HTTP digest request challenge */ struct httpauth_digest_chall_req { char *realm; char *domain; char *nonce; char *opaque; bool stale; char *algorithm; char *qop; /* optional */ char *charset; bool userhash; }; /** HTTP Digest Challenge */ struct httpauth_digest_chall { struct pl realm; struct pl nonce; /* optional */ struct pl opaque; struct pl stale; struct pl algorithm; struct pl qop; struct pl domain; struct pl charset; struct pl userhash; }; struct httpauth_digest_enc_resp { char *realm; char *nonce; char *opaque; char *algorithm; char *qop; /* response specific */ char *response; char *username; char *username_star; char *uri; uint32_t cnonce; uint32_t nc; /* optional */ char *charset; bool userhash; void (*hashh)(const uint8_t *, size_t, uint8_t *); size_t hash_length; }; /** HTTP Digest response */ struct httpauth_digest_resp { struct pl realm; struct pl nonce; struct pl response; struct pl username; struct pl uri; /* optional */ struct pl nc; struct pl cnonce; struct pl qop; struct pl algorithm; struct pl charset; struct pl userhash; void (*hashh)(const uint8_t *, size_t, uint8_t *); size_t hash_length; struct mbuf *mb; }; /** HTTP Basic */ struct httpauth_basic { struct mbuf *mb; struct pl realm; struct pl auth; }; struct httpauth_basic_req { char *realm; /* optional */ char *charset; }; int httpauth_digest_challenge_decode(struct httpauth_digest_chall *chall, const struct pl *hval); int httpauth_digest_response_decode(struct httpauth_digest_resp *resp, const struct pl *hval); int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp, const struct pl *method, const uint8_t *ha1); int httpauth_digest_make_response(struct httpauth_digest_resp **resp, const struct httpauth_digest_chall *chall, const char *path, const char *method, const char *user, const char *pwd, struct mbuf *body); int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, struct mbuf *mb); int httpauth_digest_response_print(struct re_printf *pf, const struct httpauth_digest_enc_resp *resp); int httpauth_digest_response_set_cnonce(struct httpauth_digest_enc_resp *resp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *user, const char *passwd, const char *entitybody, uint32_t cnonce, uint32_t nonce_cnt); int httpauth_digest_response(struct httpauth_digest_enc_resp **presp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *uri, const char *user, const char *passwd, const char *qop, const char *entitybody); int httpauth_digest_response_full(struct httpauth_digest_enc_resp **presp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *uri, const char *user, const char *passwd, const char *qop, const char *entitybody, const char *charset, const bool userhash); int httpauth_digest_verify(struct httpauth_digest_chall_req *req, const struct pl *hval, const struct pl *method, const char *etag, const char *user, const char *passwd, const char *entitybody); int httpauth_digest_chall_req_print(struct re_printf *pf, const struct httpauth_digest_chall_req *req); int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, const char *realm, const char *etag, const char *qop); int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, const char *real, const char *domain, const char *etag, const char *opaque, const bool stale, const char *algo, const char *qop, const char *charset, const bool userhash); struct httpauth_basic *httpauth_basic_alloc(void); int httpauth_basic_decode(struct httpauth_basic *basic, const struct pl *hval); int httpauth_basic_make_response(struct httpauth_basic *basic, const char *user, const char *pwd); int httpauth_basic_encode(const struct httpauth_basic *basic, struct mbuf *mb); int httpauth_basic_request_print(struct re_printf *pf, const struct httpauth_basic_req *req); int httpauth_basic_verify(const struct pl *hval, const char *user, const char *passwd); int httpauth_basic_request(struct httpauth_basic_req **preq, const char *realm, const char *charset); ================================================ FILE: include/re_ice.h ================================================ /** * @file re_ice.h Interface to Interactive Connectivity Establishment (ICE) * * Copyright (C) 2010 Creytiv.com */ /** ICE Role */ enum ice_role { ICE_ROLE_UNKNOWN = 0, ICE_ROLE_CONTROLLING, ICE_ROLE_CONTROLLED }; /** ICE Transport **/ enum ice_transp { ICE_TRANSP_NONE = -1, ICE_TRANSP_UDP = IPPROTO_UDP }; /** ICE Component ID */ enum ice_compid { ICE_COMPID_RTP = 1, ICE_COMPID_RTCP = 2 }; /** ICE Candidate type */ enum ice_cand_type { ICE_CAND_TYPE_HOST, /**< Host candidate */ ICE_CAND_TYPE_SRFLX, /**< Server Reflexive candidate */ ICE_CAND_TYPE_PRFLX, /**< Peer Reflexive candidate */ ICE_CAND_TYPE_RELAY /**< Relayed candidate */ }; /** ICE TCP protocol type */ enum ice_tcptype { ICE_TCP_ACTIVE, /**< Active TCP client */ ICE_TCP_PASSIVE, /**< Passive TCP server */ ICE_TCP_SO /**< Simultaneous-open TCP client/server */ }; /** Candidate pair states */ enum ice_candpair_state { ICE_CANDPAIR_FROZEN = 0, /**< Frozen state (default) */ ICE_CANDPAIR_WAITING, /**< Waiting to become highest on list */ ICE_CANDPAIR_INPROGRESS, /**< In-Progress state;transac. in progress */ ICE_CANDPAIR_SUCCEEDED, /**< Succeeded state; successful result */ ICE_CANDPAIR_FAILED /**< Failed state; check failed */ }; /** ICE local candidate policy */ enum ice_policy { ICE_POLICY_ALL, /**< Allow all local candidates */ ICE_POLICY_RELAY, /**< Use only RELAY candidates */ }; struct ice; struct ice_cand; struct icem; struct turnc; /** ICE Configuration */ struct ice_conf { uint32_t rto; /**< STUN Retransmission TimeOut */ uint32_t rc; /**< STUN Retransmission Count */ enum ice_policy policy; /**< ICE Local Candidate Policy */ bool debug; /**< Enable ICE debugging */ }; typedef void (ice_connchk_h)(int err, bool update, void *arg); /* ICE Media */ int icem_alloc(struct icem **icemp, enum ice_role role, int proto, int layer, uint64_t tiebrk, const char *lufrag, const char *lpwd, ice_connchk_h *chkh, void *arg); struct ice_conf *icem_conf(struct icem *icem); enum ice_role icem_local_role(const struct icem *icem); void icem_set_conf(struct icem *icem, const struct ice_conf *conf); void icem_set_role(struct icem *icem, enum ice_role role); void icem_set_name(struct icem *icem, const char *name); int icem_comp_add(struct icem *icem, unsigned compid, void *sock); bool icem_verify_support(struct icem *icem, unsigned compid, const struct sa *raddr); int icem_conncheck_start(struct icem *icem); void icem_conncheck_stop(struct icem *icem, int err); int icem_add_chan(struct icem *icem, unsigned compid, const struct sa *raddr); bool icem_mismatch(const struct icem *icem); void icem_update(struct icem *icem); int ice_sdp_decode(struct icem *ice, const char *name, const char *value); int icem_sdp_decode(struct icem *icem, const char *name, const char *value); int icem_debug(struct re_printf *pf, const struct icem *icem); struct list *icem_lcandl(const struct icem *icem); struct list *icem_rcandl(const struct icem *icem); struct list *icem_checkl(const struct icem *icem); struct list *icem_validl(const struct icem *icem); bool icem_rcand_ready(struct icem *icem); const struct sa *icem_cand_default(struct icem *icem, unsigned compid); const struct sa *icem_selected_laddr(const struct icem *icem, unsigned compid); const struct ice_cand *icem_selected_lcand(const struct icem *icem, unsigned compid); const struct ice_cand *icem_selected_rcand(const struct icem *icem, unsigned compid); void ice_candpair_set_states(struct icem *icem); void icem_cand_redund_elim(struct icem *icem); int icem_comps_set_default_cand(struct icem *icem); struct stun *icem_stun(struct icem *icem); int icem_set_turn_client(struct icem *icem, unsigned compid, struct turnc *turnc); bool ice_remotecands_avail(const struct icem *icem); int ice_cand_encode(struct re_printf *pf, const struct ice_cand *cand); int ice_remotecands_encode(struct re_printf *pf, const struct icem *icem); struct ice_cand *icem_cand_find(const struct list *lst, unsigned compid, const struct sa *addr); int icem_lcand_add_base(struct icem *icem, enum ice_cand_type type, unsigned compid, uint16_t lprio, const char *ifname, enum ice_transp transp, const struct sa *addr); int icem_lcand_add(struct icem *icem, struct ice_cand *base, enum ice_cand_type type, const struct sa *addr); struct ice_cand *icem_lcand_base(struct ice_cand *lcand); const struct sa *icem_lcand_addr(const struct ice_cand *cand); enum ice_cand_type icem_cand_type(const struct ice_cand *cand); extern const char ice_attr_cand[]; extern const char ice_attr_lite[]; extern const char ice_attr_mismatch[]; extern const char ice_attr_pwd[]; extern const char ice_attr_remote_cand[]; extern const char ice_attr_ufrag[]; const char *ice_cand_type2name(enum ice_cand_type type); enum ice_cand_type ice_cand_name2type(const char *name); const char *ice_role2name(enum ice_role role); const char *ice_candpair_state2name(enum ice_candpair_state st); uint32_t ice_cand_calc_prio(enum ice_cand_type type, uint16_t lpref, unsigned compid); /** Defines an SDP candidate attribute */ struct ice_cand_attr { char foundation[32]; /**< Foundation string */ unsigned compid; /**< Component ID (1-256) */ int proto; /**< Transport protocol */ uint32_t prio; /**< Priority of this candidate */ struct sa addr; /**< Transport address */ enum ice_cand_type type; /**< Candidate type */ struct sa rel_addr; /**< Related transport address (optional) */ enum ice_tcptype tcptype; /**< TCP candidate type (TCP-only) */ }; int ice_cand_attr_encode(struct re_printf *pf, const struct ice_cand_attr *cand); int ice_cand_attr_decode(struct ice_cand_attr *cand, const char *val); ================================================ FILE: include/re_json.h ================================================ /** * @file re_json.h Interface to JavaScript Object Notation (JSON) -- RFC 7159 * * Copyright (C) 2010 - 2015 Creytiv.com */ enum json_typ { JSON_STRING, JSON_INT, JSON_DOUBLE, JSON_BOOL, JSON_NULL, }; struct json_value { union { char *str; int64_t integer; double dbl; bool boolean; } v; enum json_typ type; }; struct json_handlers; typedef int (json_object_entry_h)(const char *name, const struct json_value *value, void *arg); typedef int (json_array_entry_h)(unsigned idx, const struct json_value *value, void *arg); typedef int (json_object_h)(const char *name, unsigned idx, struct json_handlers *h); typedef int (json_array_h)(const char *name, unsigned idx, struct json_handlers *h); struct json_handlers { json_object_h *oh; json_array_h *ah; json_object_entry_h *oeh; json_array_entry_h *aeh; void *arg; }; int json_decode(const char *str, size_t len, unsigned maxdepth, json_object_h *oh, json_array_h *ah, json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg); int json_decode_odict(struct odict **op, uint32_t hash_size, const char *str, size_t len, unsigned maxdepth); int json_encode_odict(struct re_printf *pf, const struct odict *o); ================================================ FILE: include/re_list.h ================================================ /** * @file re_list.h Interface to Linked List * * Copyright (C) 2010 Creytiv.com */ /** Linked-list element */ struct le { struct le *prev; /**< Previous element */ struct le *next; /**< Next element */ struct list *list; /**< Parent list (NULL if not linked-in) */ void *data; /**< User-data */ }; /** List Element Initializer */ #define LE_INIT {NULL, NULL, NULL, NULL} /** Defines a linked list */ struct list { struct le *head; /**< First list element */ struct le *tail; /**< Last list element */ size_t cnt; /**< Number of elements */ }; /** Linked list Initializer */ #define LIST_INIT {NULL, NULL, 0} /** * Defines the list apply handler * * @param le List element * @param arg Handler argument * * @return true to stop traversing, false to continue */ typedef bool (list_apply_h)(struct le *le, void *arg); /** * Defines the list sort handler * * @param le1 Current list element * @param le2 Next list element * @param arg Handler argument * * @return true if sorted, otherwise false */ typedef bool (list_sort_h)(struct le *le1, struct le *le2, void *arg); void list_init(struct list *list); void list_flush(struct list *list); void list_clear(struct list *list); void list_append(struct list *list, struct le *le, void *data); void list_prepend(struct list *list, struct le *le, void *data); void list_insert_before(struct list *list, struct le *le, struct le *ile, void *data); void list_insert_after(struct list *list, struct le *le, struct le *ile, void *data); void list_insert_sorted(struct list *list, list_sort_h *sh, void *arg, struct le *ile, void *data); void list_unlink(struct le *le); void list_sort(struct list *list, list_sort_h *sh, void *arg); struct le *list_apply(const struct list *list, bool fwd, list_apply_h *ah, void *arg); struct le *list_head(const struct list *list); struct le *list_tail(const struct list *list); uint32_t list_count(const struct list *list); /** * Get the user-data from a list element * * @param le List element * * @return Pointer to user-data */ static inline void *list_ledata(const struct le *le) { return le ? le->data : NULL; } static inline bool list_contains(const struct list *list, const struct le *le) { return le ? le->list == list : false; } static inline bool list_isempty(const struct list *list) { return list ? list->head == NULL : true; } /** * @def LIST_FOREACH * @brief Iterates over each element in a list * * @param list The list to iterate * @param le Iterator variable */ #define LIST_FOREACH(list, le) \ for ((le) = list_head((list)); (le); (le) = (le)->next) /** * @def LIST_FOREACH_SAFE * @brief Safe list iteration allowing element removal * * @param list The list to iterate * @param le Iterator variable * @param le_tmp Temporary variable for next element */ #define LIST_FOREACH_SAFE(list, le, le_tmp) \ for ((le) = list_head((list)); (le) && ((le_tmp = le->next) || 1); \ (le) = le_tmp) /** * Move element to another linked list * * @param le List element to move * @param list Destination list */ static inline void list_move(struct le *le, struct list *list) { list_unlink(le); list_append(list, le, le->data); } ================================================ FILE: include/re_main.h ================================================ /** * @file re_main.h Interface to main polling routine * * Copyright (C) 2010 Creytiv.com */ #include "re_async.h" struct re; struct re_fhs; enum { #ifndef FD_READ FD_READ = 1<<0, #endif #ifndef FD_WRITE FD_WRITE = 1<<1, #endif FD_EXCEPT = 1<<2 }; /** * File descriptor event handler * * @param flags Event flags * @param arg Handler argument */ typedef void (fd_h)(int flags, void *arg); /** * Thread-safe signal handler * * @param sig Signal number */ typedef void (re_signal_h)(int sig); int fd_listen(struct re_fhs **fhsp, re_sock_t fd, int flags, fd_h *fh, void *arg); struct re_fhs *fd_close(struct re_fhs *fhs); int fd_setsize(int maxfds); int libre_init(void); void libre_close(void); void libre_exception_btrace(bool enable); int re_main(re_signal_h *signalh); void re_cancel(void); int re_debug(struct re_printf *pf, void *unused); int re_nfds(void); int re_alloc(struct re **rep); int re_thread_attach(struct re *re); void re_thread_detach(void); int re_thread_init(void); void re_thread_close(void); void re_thread_enter(void); void re_thread_leave(void); int re_thread_check(bool debug); int re_thread_async_init(uint16_t workers); void re_thread_async_close(void); int re_thread_async(re_async_work_h *work, re_async_h *cb, void *arg); int re_thread_async_main(re_async_work_h *work, re_async_h *cb, void *arg); int re_thread_async_id(intptr_t id, re_async_work_h *work, re_async_h *cb, void *arg); int re_thread_async_main_id(intptr_t id, re_async_work_h *work, re_async_h *cb, void *arg); void re_thread_async_cancel(intptr_t id); void re_thread_async_main_cancel(intptr_t id); void re_set_mutex(void *mutexp); void re_fhs_flush(void); struct tmrl *re_tmrl_get(void); /** Polling methods */ enum poll_method { METHOD_NULL = 0, METHOD_SELECT, METHOD_EPOLL, METHOD_KQUEUE, /* sep */ METHOD_MAX }; int poll_method_set(enum poll_method method); enum poll_method poll_method_get(void); enum poll_method poll_method_best(void); const char *poll_method_name(enum poll_method method); int poll_method_type(enum poll_method *method, const struct pl *name); ================================================ FILE: include/re_mbuf.h ================================================ /** * @file re_mbuf.h Interface to memory buffers * * Copyright (C) 2010 Creytiv.com */ #include #ifndef RELEASE #define MBUF_DEBUG 1 /**< Mbuf debugging (0 or 1) */ #endif #if MBUF_DEBUG /** Check that mbuf position does not exceed end */ #define MBUF_CHECK_POS(mb) \ if ((mb) && (mb)->pos > (mb)->end) { \ RE_BREAKPOINT; \ } /** Check that mbuf end does not exceed size */ #define MBUF_CHECK_END(mb) \ if ((mb) && (mb)->end > (mb)->size) { \ RE_BREAKPOINT; \ } #else #define MBUF_CHECK_POS(mb) #define MBUF_CHECK_END(mb) #endif /** * Defines a memory buffer. * * This is a dynamic and linear buffer for storing raw bytes. * It is designed for network protocols, and supports automatic * resizing of the buffer. * * - Writing to the buffer * - Reading from the buffer * - Automatic growing of buffer size * - Print function for formatting printing */ struct mbuf { uint8_t *buf; /**< Buffer memory */ size_t size; /**< Size of buffer */ size_t pos; /**< Position in buffer */ size_t end; /**< End of buffer */ }; struct pl; struct re_printf; struct mbuf *mbuf_alloc(size_t size); struct mbuf *mbuf_dup(struct mbuf *mbd); struct mbuf *mbuf_alloc_ref(struct mbuf *mbr); void mbuf_init(struct mbuf *mb); void mbuf_reset(struct mbuf *mb); int mbuf_resize(struct mbuf *mb, size_t size); void mbuf_trim(struct mbuf *mb); int mbuf_shift(struct mbuf *mb, ssize_t shift); int mbuf_write_mem(struct mbuf *mb, const uint8_t *buf, size_t size); int mbuf_write_ptr(struct mbuf *mb, intptr_t v); int mbuf_write_u8(struct mbuf *mb, uint8_t v); int mbuf_write_u16(struct mbuf *mb, uint16_t v); int mbuf_write_u32(struct mbuf *mb, uint32_t v); int mbuf_write_u64(struct mbuf *mb, uint64_t v); int mbuf_write_str(struct mbuf *mb, const char *str); int mbuf_write_pl(struct mbuf *mb, const struct pl *pl); int mbuf_read_mem(struct mbuf *mb, uint8_t *buf, size_t size); intptr_t mbuf_read_ptr(struct mbuf *mb); uint8_t mbuf_read_u8(struct mbuf *mb); uint16_t mbuf_read_u16(struct mbuf *mb); uint32_t mbuf_read_u32(struct mbuf *mb); uint64_t mbuf_read_u64(struct mbuf *mb); int mbuf_read_str(struct mbuf *mb, char *str, size_t size); int mbuf_strdup(struct mbuf *mb, char **strp, size_t len); int mbuf_vprintf(struct mbuf *mb, const char *fmt, va_list ap); #ifdef HAVE_RE_ARG #define mbuf_printf(mb, fmt, ...) \ _mbuf_printf_s((mb), (fmt), RE_VA_ARGS(__VA_ARGS__)) #else #define mbuf_printf _mbuf_printf #endif int _mbuf_printf(struct mbuf *mb, const char *fmt, ...); int _mbuf_printf_s(struct mbuf *mb, const char *fmt, ...); int mbuf_write_pl_skip(struct mbuf *mb, const struct pl *pl, const struct pl *skip); int mbuf_fill(struct mbuf *mb, uint8_t c, size_t n); void mbuf_set_posend(struct mbuf *mb, size_t pos, size_t end); int mbuf_debug(struct re_printf *pf, const struct mbuf *mb); /** * Get the buffer from the current position * * @param mb Memory buffer * * @return Current buffer */ static inline uint8_t *mbuf_buf(const struct mbuf *mb) { return mb ? mb->buf + mb->pos : (uint8_t *)NULL; } /** * Get number of bytes left in a memory buffer, from current position to end * * @param mb Memory buffer * * @return Number of bytes left */ static inline size_t mbuf_get_left(const struct mbuf *mb) { return (mb && (mb->end > mb->pos)) ? (mb->end - mb->pos) : 0; } /** * Get available space in buffer (size - pos) * * @param mb Memory buffer * * @return Number of bytes available in buffer */ static inline size_t mbuf_get_space(const struct mbuf *mb) { return (mb && (mb->size > mb->pos)) ? (mb->size - mb->pos) : 0; } /** * Set absolute position * * @param mb Memory buffer * @param pos Position */ static inline void mbuf_set_pos(struct mbuf *mb, size_t pos) { if (!mb) return; mb->pos = pos; MBUF_CHECK_POS(mb); } /** * Set absolute end * * @param mb Memory buffer * @param end End position */ static inline void mbuf_set_end(struct mbuf *mb, size_t end) { if (!mb) return; mb->end = end; MBUF_CHECK_END(mb); } /** * Advance position +/- N bytes * * @param mb Memory buffer * @param n Number of bytes to advance */ static inline void mbuf_advance(struct mbuf *mb, ssize_t n) { if (!mb) return; mb->pos += n; MBUF_CHECK_POS(mb); } /** * Rewind position and end to the beginning of buffer * * @param mb Memory buffer */ static inline void mbuf_rewind(struct mbuf *mb) { if (!mb) return; mb->pos = mb->end = 0; } /** * Set position to the end of the buffer * * @param mb Memory buffer */ static inline void mbuf_skip_to_end(struct mbuf *mb) { if (!mb) return; mb->pos = mb->end; } /** * Get the current MBUF position * * @param mb Memory buffer * * @return Current position */ static inline size_t mbuf_pos(const struct mbuf *mb) { return mb ? mb->pos : 0; } /** * Get the current MBUF end position * * @param mb Memory buffer * * @return Current end position */ static inline size_t mbuf_end(const struct mbuf *mb) { return mb ? mb->end : 0; } ================================================ FILE: include/re_md5.h ================================================ /** * @file re_md5.h Interface to MD5 functions * * Copyright (C) 2010 Creytiv.com */ /** MD5 values */ enum { MD5_SIZE = 16, /**< Number of bytes in MD5 hash */ MD5_STR_SIZE = 2*MD5_SIZE + 1 /**< Number of bytes in MD5 string */ }; void md5(const uint8_t *d, size_t n, uint8_t *md); int md5_printf(uint8_t *md, const char *fmt, ...); ================================================ FILE: include/re_mem.h ================================================ /** * @file re_mem.h Interface to Memory management with reference counting * * Copyright (C) 2010 Creytiv.com */ /** * Defines the memory destructor handler, which is called when the reference * of a memory object goes down to zero * * @param data Pointer to memory object */ typedef void (mem_destroy_h)(void *data); /** Memory Statistics */ struct memstat { size_t bytes_cur; /**< Current bytes allocated */ size_t blocks_cur; /**< Current blocks allocated */ }; void *mem_alloc(size_t size, mem_destroy_h *dh); void *mem_zalloc(size_t size, mem_destroy_h *dh); void *mem_realloc(void *data, size_t size); void *mem_reallocarray(void *ptr, size_t nmemb, size_t membsize, mem_destroy_h *dh); void mem_destructor(void *data, mem_destroy_h *dh); void *mem_ref(void *data); void *mem_deref(void *data); uint32_t mem_nrefs(const void *data); void mem_debug(void); void mem_debug_tail(uint32_t last_n); void mem_threshold_set(ssize_t n); struct re_printf; int mem_status(struct re_printf *pf, void *unused); int mem_get_stat(struct memstat *mstat); /* Secure memory functions */ int mem_seccmp(const uint8_t *s1, const uint8_t *s2, size_t n); void mem_secclean(void *data, size_t size); /* Mem Pool */ struct mem_pool; struct mem_pool_entry; int mem_pool_alloc(struct mem_pool **poolp, size_t nmemb, size_t membsize, mem_destroy_h *dh); int mem_pool_extend(struct mem_pool *pool, size_t num); struct mem_pool_entry *mem_pool_borrow(struct mem_pool *pool); struct mem_pool_entry *mem_pool_borrow_extend(struct mem_pool *pool); void *mem_pool_release(struct mem_pool *pool, struct mem_pool_entry *e); void *mem_pool_member(const struct mem_pool_entry *entry); void mem_pool_flush(struct mem_pool *pool); ================================================ FILE: include/re_mod.h ================================================ /** * @file re_mod.h Interface to loadable modules * * Copyright (C) 2010 Creytiv.com */ /** * @def MOD_EXT * * Module Extension */ #if defined (WIN32) #define MOD_EXT ".dll" #else #define MOD_EXT ".so" #endif /** Symbol to enable exporting of functions from a module */ #ifdef WIN32 #define EXPORT_SYM __declspec(dllexport) #else #define EXPORT_SYM #endif /* ----- Module API ----- */ /** * Defines the module initialisation handler * * @return 0 for success, otherwise errorcode */ typedef int (mod_init_h)(void); /** * Defines the module close handler * * @return 0 for success, otherwise errorcode */ typedef int (mod_close_h)(void); struct mod; struct re_printf; /** Defines the module export */ struct mod_export { const char *name; /**< Module name */ const char *type; /**< Module type */ mod_init_h *init; /**< Module init handler */ mod_close_h *close; /**< Module close handler */ }; /* ----- Application API ----- */ void mod_init(void); void mod_close(void); int mod_load(struct mod **mp, const char *name); int mod_add(struct mod **mp, const struct mod_export *me); struct mod *mod_find(const char *name); const struct mod_export *mod_export(const struct mod *m); struct list *mod_list(void); int mod_debug(struct re_printf *pf, void *unused); ================================================ FILE: include/re_mqueue.h ================================================ /** * @file re_mqueue.h Thread Safe Message Queue * * Copyright (C) 2010 Creytiv.com */ struct mqueue; typedef void (mqueue_h)(int id, void *data, void *arg); int mqueue_alloc(struct mqueue **mqp, mqueue_h *h, void *arg); int mqueue_push(struct mqueue *mq, int id, void *data); ================================================ FILE: include/re_msg.h ================================================ /** * @file re_msg.h Interface to generic message components * * Copyright (C) 2010 Creytiv.com */ /** Content-Type */ struct msg_ctype { struct pl type; struct pl subtype; struct pl params; }; int msg_ctype_decode(struct msg_ctype *ctype, const struct pl *pl); bool msg_ctype_cmp(const struct msg_ctype *ctype, const char *type, const char *subtype); int msg_param_decode(const struct pl *pl, const char *name, struct pl *val); int msg_param_exists(const struct pl *pl, const char *name, struct pl *end); ================================================ FILE: include/re_net.h ================================================ /** * @file re_net.h Interface to Networking module. * * Copyright (C) 2010 Creytiv.com */ #if defined(WIN32) #include #include #else #include #ifndef _BSD_SOCKLEN_T_ #define _BSD_SOCKLEN_T_ int /**< Defines the BSD socket length type */ #endif #include #include #include #endif /** Length of IPv4 address string */ #ifndef INET_ADDRSTRLEN #define INET_ADDRSTRLEN 16 #endif /** Length of IPv6 address string */ #ifndef INET6_ADDRSTRLEN #define INET6_ADDRSTRLEN 46 #endif /* forward declarations */ struct sa; /* Net generic */ int net_dst_source_addr_get(const struct sa *dst, struct sa *ip); int net_default_source_addr_get(int af, struct sa *ip); int net_default_gateway_get(int af, struct sa *gw); /* Net sockets */ int net_sock_init(void); void net_sock_close(void); /* Net socket options */ int net_sockopt_blocking_set(re_sock_t fd, bool blocking); int net_sockopt_reuse_set(re_sock_t fd, bool reuse); int net_sockopt_v6only(re_sock_t fd, bool only); /* Net interface (if.c) */ /** * Defines the interface address handler - called once per interface * * @param ifname Name of the interface * @param sa IP address of the interface * @param arg Handler argument * * @return true to stop traversing, false to continue */ typedef bool (net_ifaddr_h)(const char *ifname, const struct sa *sa, void *arg); int net_if_getname(char *ifname, size_t sz, int af, const struct sa *ip); int net_if_getaddr(const char *ifname, int af, struct sa *ip); int net_if_list(net_ifaddr_h *ifh, void *arg); int net_if_apply(net_ifaddr_h *ifh, void *arg); int net_if_debug(struct re_printf *pf, void *unused); int net_if_getlinklocal(const char *ifname, int af, struct sa *ip); /* Net interface (ifaddrs.c) */ int net_getifaddrs(net_ifaddr_h *ifh, void *arg); int net_netlink_addrs(net_ifaddr_h *ifh, void *arg); /* Net route */ /** * Defines the routing table handler - called once per route entry * * @param ifname Interface name * @param dst Destination IP address/network * @param dstlen Prefix length of destination * @param gw Gateway IP address * @param arg Handler argument * * @return true to stop traversing, false to continue */ typedef bool (net_rt_h)(const char *ifname, const struct sa *dst, int dstlen, const struct sa *gw, void *arg); int net_rt_list(net_rt_h *rth, void *arg); int net_rt_default_get(int af, char *ifname, size_t size); int net_rt_debug(struct re_printf *pf, void *unused); /* Net strings */ const char *net_proto2name(int proto); const char *net_af2name(int af); ================================================ FILE: include/re_odict.h ================================================ /** * @file re_odict.h Interface to Ordered Dictionary * * Copyright (C) 2010 - 2015 Creytiv.com */ enum odict_type { ODICT_ERR = -1, ODICT_OBJECT, ODICT_ARRAY, ODICT_STRING, ODICT_INT, ODICT_DOUBLE, ODICT_BOOL, ODICT_NULL }; struct odict { struct list lst; struct hash *ht; }; struct odict_entry; int odict_alloc(struct odict **op, uint32_t hash_size); const struct odict_entry *odict_lookup(const struct odict *o, const char *key); size_t odict_count(const struct odict *o, bool nested); int odict_debug(struct re_printf *pf, const struct odict *o); int odict_entry_add(struct odict *o, const char *key, int type, ...); int odict_pl_add(struct odict *od, const char *key, const struct pl *val); void odict_entry_del(struct odict *o, const char *key); int odict_entry_debug(struct re_printf *pf, const struct odict_entry *e); bool odict_compare(const struct odict *dict1, const struct odict *dict2, bool ignore_order); bool odict_type_iscontainer(enum odict_type type); bool odict_type_isreal(enum odict_type type); const char *odict_type_name(enum odict_type type); /* Odict Helpers */ const struct odict_entry *odict_get_type(const struct odict *o, enum odict_type type, const char *key); const char *odict_string(const struct odict *o, const char *key); bool odict_get_number(const struct odict *o, uint64_t *num, const char *key); bool odict_get_boolean(const struct odict *o, bool *value, const char *key); struct odict *odict_get_object(const struct odict *o, const char *key); struct odict *odict_get_array(const struct odict *o, const char *key); /* Entry Helpers */ enum odict_type odict_entry_type(const struct odict_entry *e); const char *odict_entry_key(const struct odict_entry *e); struct odict *odict_entry_object(const struct odict_entry *e); struct odict *odict_entry_array(const struct odict_entry *e); char *odict_entry_str(const struct odict_entry *e); int64_t odict_entry_int(const struct odict_entry *e); double odict_entry_dbl(const struct odict_entry *e); bool odict_entry_boolean(const struct odict_entry *e); bool odict_value_compare(const struct odict_entry *e1, const struct odict_entry *e2, bool ignore_order); ================================================ FILE: include/re_pcp.h ================================================ /** * @file re_pcp.h PCP - Port Control Protocol (RFC 6887) * * Copyright (C) 2010 - 2014 Alfred E. Heggestad */ /* * The following specifications are implemented: * * RFC 6887 * draft-ietf-pcp-description-option-02 * draft-cheshire-pcp-unsupp-family * */ /** PCP Version numbers */ enum { PCP_VERSION = 2, }; /* PCP port numbers */ enum { PCP_PORT_CLI = 5350, /* for ANNOUNCE notifications */ PCP_PORT_SRV = 5351, }; /** PCP Protocol sizes */ enum { PCP_HDR_SZ = 24, PCP_NONCE_SZ = 12, PCP_MAP_SZ = 36, PCP_PEER_SZ = 56, PCP_MIN_PACKET = 24, PCP_MAX_PACKET = 1100 }; enum pcp_opcode { PCP_ANNOUNCE = 0, PCP_MAP = 1, PCP_PEER = 2, }; enum pcp_result { PCP_SUCCESS = 0, PCP_UNSUPP_VERSION = 1, PCP_NOT_AUTHORIZED = 2, PCP_MALFORMED_REQUEST = 3, PCP_UNSUPP_OPCODE = 4, PCP_UNSUPP_OPTION = 5, PCP_MALFORMED_OPTION = 6, PCP_NETWORK_FAILURE = 7, PCP_NO_RESOURCES = 8, PCP_UNSUPP_PROTOCOL = 9, PCP_USER_EX_QUOTA = 10, PCP_CANNOT_PROVIDE_EXTERNAL = 11, PCP_ADDRESS_MISMATCH = 12, PCP_EXCESSIVE_REMOTE_PEERS = 13, }; enum pcp_option_code { PCP_OPTION_THIRD_PARTY = 1, PCP_OPTION_PREFER_FAILURE = 2, PCP_OPTION_FILTER = 3, PCP_OPTION_DESCRIPTION = 128, /* RFC 7220 */ }; /* forward declarations */ struct udp_sock; /** Defines a PCP option */ struct pcp_option { struct le le; enum pcp_option_code code; union { struct sa third_party; /* Internal IP-address */ struct pcp_option_filter { uint8_t prefix_length; struct sa remote_peer; } filter; char *description; } u; }; /** * Defines a complete and decoded PCP request/response. * * A PCP message consist of a header, and optional payload and options: * * [ Header ] * ( Opcode Payload ) * ( PCP Options ) * */ struct pcp_msg { /** PCP Common Header */ struct pcp_hdr { uint8_t version; /**< PCP Protocol version 2 */ unsigned resp:1; /**< R-bit; 0=Request, 1=Response */ uint8_t opcode; /**< A 7-bit opcode */ uint32_t lifetime; /**< Lifetime in [seconds] */ /* request: */ struct sa cli_addr; /**< Client's IP Address (SA_ADDR) */ /* response: */ enum pcp_result result; /**< Result code for this response */ uint32_t epoch; /**< Server's Epoch Time [seconds] */ } hdr; /** PCP Opcode-specific payload */ union pcp_payload { struct pcp_map { uint8_t nonce[PCP_NONCE_SZ]; /**< Mapping Nonce */ uint8_t proto; /**< IANA protocol */ uint16_t int_port; /**< Internal Port */ struct sa ext_addr; /**< External Address */ } map; struct pcp_peer { struct pcp_map map; /**< Common with MAP */ struct sa remote_addr; /**< Remote address */ } peer; } pld; /** List of PCP Options (struct pcp_option) */ struct list optionl; }; /** PCP request configuration */ struct pcp_conf { uint32_t irt; /**< Initial retransmission time [seconds] */ uint32_t mrc; /**< Maximum retransmission count */ uint32_t mrt; /**< Maximum retransmission time [seconds] */ uint32_t mrd; /**< Maximum retransmission duration [seconds] */ }; /* request */ struct pcp_request; typedef void (pcp_resp_h)(int err, struct pcp_msg *msg, void *arg); int pcp_request(struct pcp_request **reqp, const struct pcp_conf *conf, const struct sa *pcp_server, enum pcp_opcode opcode, uint32_t lifetime, const void *payload, pcp_resp_h *resph, void *arg, uint32_t optionc, ...); void pcp_force_refresh(struct pcp_request *req); /* reply */ int pcp_reply(struct udp_sock *us, const struct sa *dst, struct mbuf *req, enum pcp_opcode opcode, enum pcp_result result, uint32_t lifetime, uint32_t epoch_time, const void *payload); /* msg */ typedef bool (pcp_option_h)(const struct pcp_option *opt, void *arg); int pcp_msg_decode(struct pcp_msg **msgp, struct mbuf *mb); int pcp_msg_printhdr(struct re_printf *pf, const struct pcp_msg *msg); int pcp_msg_print(struct re_printf *pf, const struct pcp_msg *msg); struct pcp_option *pcp_msg_option(const struct pcp_msg *msg, enum pcp_option_code code); struct pcp_option *pcp_msg_option_apply(const struct pcp_msg *msg, pcp_option_h *h, void *arg); const void *pcp_msg_payload(const struct pcp_msg *msg); /* option */ int pcp_option_encode(struct mbuf *mb, enum pcp_option_code code, const void *v); int pcp_option_decode(struct pcp_option **optp, struct mbuf *mb); int pcp_option_print(struct re_printf *pf, const struct pcp_option *opt); /* encode */ int pcp_msg_req_vencode(struct mbuf *mb, enum pcp_opcode opcode, uint32_t lifetime, const struct sa *cli_addr, const void *payload, uint32_t optionc, va_list ap); int pcp_msg_req_encode(struct mbuf *mb, enum pcp_opcode opcode, uint32_t lifetime, const struct sa *cli_addr, const void *payload, uint32_t optionc, ...); /* pcp */ int pcp_ipaddr_encode(struct mbuf *mb, const struct sa *sa); int pcp_ipaddr_decode(struct mbuf *mb, struct sa *sa); const char *pcp_result_name(enum pcp_result result); const char *pcp_opcode_name(enum pcp_opcode opcode); const char *pcp_proto_name(int proto); ================================================ FILE: include/re_rtmp.h ================================================ /** * @file re_rtmp.h Interface to Real Time Messaging Protocol (RTMP) * * Copyright (C) 2010 Creytiv.com */ /** RTMP Protocol values */ enum { RTMP_PORT = 1935, }; /** RTMP Stream IDs */ enum { /* User Control messages SHOULD use message stream ID 0 (known as the control stream) */ RTMP_CONTROL_STREAM_ID = 0 }; /** RTMP Packet types */ enum rtmp_packet_type { RTMP_TYPE_SET_CHUNK_SIZE = 1, /**< Set Chunk Size */ RTMP_TYPE_ACKNOWLEDGEMENT = 3, /**< Acknowledgement */ RTMP_TYPE_USER_CONTROL_MSG = 4, /**< User Control Messages */ RTMP_TYPE_WINDOW_ACK_SIZE = 5, /**< Window Acknowledgement Size */ RTMP_TYPE_SET_PEER_BANDWIDTH = 6, /**< Set Peer Bandwidth */ RTMP_TYPE_AUDIO = 8, /**< Audio Message */ RTMP_TYPE_VIDEO = 9, /**< Video Message */ RTMP_TYPE_DATA = 18, /**< Data Message */ RTMP_TYPE_AMF0 = 20, /**< Action Message Format (AMF) */ }; /** RTMP AMF types */ enum rtmp_amf_type { RTMP_AMF_TYPE_ROOT = -1, /**< Special internal type */ RTMP_AMF_TYPE_NUMBER = 0x00, /**< Number Type */ RTMP_AMF_TYPE_BOOLEAN = 0x01, /**< Boolean Type */ RTMP_AMF_TYPE_STRING = 0x02, /**< String Type */ RTMP_AMF_TYPE_OBJECT = 0x03, /**< Object Type */ RTMP_AMF_TYPE_NULL = 0x05, /**< Null type */ RTMP_AMF_TYPE_ECMA_ARRAY = 0x08, /**< ECMA 'associative' Array */ RTMP_AMF_TYPE_OBJECT_END = 0x09, /**< Object End Type */ RTMP_AMF_TYPE_STRICT_ARRAY = 0x0a, /**< Array with ordinal indices */ }; /** RTMP Event types */ enum rtmp_event_type { RTMP_EVENT_STREAM_BEGIN = 0, /**< Stream begin */ RTMP_EVENT_STREAM_EOF = 1, /**< Stream End-Of-File */ RTMP_EVENT_STREAM_DRY = 2, /**< No more data on the stream */ RTMP_EVENT_SET_BUFFER_LENGTH = 3, /**< Set buffer size in [ms] */ RTMP_EVENT_STREAM_IS_RECORDED = 4, /**< Stream is recorded */ RTMP_EVENT_PING_REQUEST = 6, /**< Ping Request from server */ RTMP_EVENT_PING_RESPONSE = 7, /**< Ping Response to server */ }; /* forward declarations */ struct tls; struct dnsc; struct odict; struct tcp_sock; /* * RTMP High-level API (connection, stream) */ /* conn */ struct rtmp_conn; typedef void (rtmp_estab_h)(void *arg); typedef void (rtmp_command_h)(const struct odict *msg, void *arg); typedef void (rtmp_close_h)(int err, void *arg); int rtmp_connect(struct rtmp_conn **connp, struct dnsc *dnsc, const char *uri, struct tls *tls, rtmp_estab_h *estabh, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg); int rtmp_accept(struct rtmp_conn **connp, struct tcp_sock *ts, struct tls *tls, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg); int rtmp_control(const struct rtmp_conn *conn, enum rtmp_packet_type type, ...); void rtmp_set_handlers(struct rtmp_conn *conn, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg); struct tcp_conn *rtmp_conn_tcpconn(const struct rtmp_conn *conn); const char *rtmp_conn_stream(const struct rtmp_conn *conn); int rtmp_conn_debug(struct re_printf *pf, const struct rtmp_conn *conn); typedef void (rtmp_resp_h)(bool success, const struct odict *msg, void *arg); /* amf */ int rtmp_amf_command(const struct rtmp_conn *conn, uint32_t stream_id, const char *command, unsigned body_propc, ...); int rtmp_amf_request(struct rtmp_conn *conn, uint32_t stream_id, const char *command, rtmp_resp_h *resph, void *arg, unsigned body_propc, ...); int rtmp_amf_reply(struct rtmp_conn *conn, uint32_t stream_id, bool success, const struct odict *req, unsigned body_propc, ...); int rtmp_amf_data(const struct rtmp_conn *conn, uint32_t stream_id, const char *command, unsigned body_propc, ...); /* stream */ struct rtmp_stream; typedef void (rtmp_control_h)(enum rtmp_event_type event, struct mbuf *mb, void *arg); typedef void (rtmp_audio_h)(uint32_t timestamp, const uint8_t *pld, size_t len, void *arg); typedef void (rtmp_video_h)(uint32_t timestamp, const uint8_t *pld, size_t len, void *arg); int rtmp_stream_alloc(struct rtmp_stream **strmp, struct rtmp_conn *conn, uint32_t stream_id, rtmp_command_h *cmdh, rtmp_control_h *ctrlh, rtmp_audio_h *auh, rtmp_video_h *vidh, rtmp_command_h *datah, void *arg); int rtmp_stream_create(struct rtmp_stream **strmp, struct rtmp_conn *conn, rtmp_resp_h *resph, rtmp_command_h *cmdh, rtmp_control_h *ctrlh, rtmp_audio_h *auh, rtmp_video_h *vidh, rtmp_command_h *datah, void *arg); int rtmp_play(struct rtmp_stream *strm, const char *name); int rtmp_publish(struct rtmp_stream *strm, const char *name); int rtmp_meta(struct rtmp_stream *strm); int rtmp_send_audio(struct rtmp_stream *strm, uint32_t timestamp, const uint8_t *pld, size_t len); int rtmp_send_video(struct rtmp_stream *strm, uint32_t timestamp, const uint8_t *pld, size_t len); struct rtmp_stream *rtmp_stream_find(const struct rtmp_conn *conn, uint32_t stream_id); const char *rtmp_event_name(enum rtmp_event_type event); ================================================ FILE: include/re_rtp.h ================================================ /** * @file re_rtp.h Interface to Real-time Transport Protocol and RTCP * * Copyright (C) 2010 Creytiv.com */ /** RTP protocol values */ enum { RTP_VERSION = 2, /**< Defines the RTP version we support */ RTCP_VERSION = 2, /**< Supported RTCP Version */ RTP_HEADER_SIZE = 12 /**< Number of bytes in RTP Header */ }; /** Defines the RTP header */ struct rtp_header { uint8_t ver; /**< RTP version number */ bool pad; /**< Padding bit */ bool ext; /**< Extension bit */ uint8_t cc; /**< CSRC count */ bool m; /**< Marker bit */ uint8_t pt; /**< Payload type */ uint16_t seq; /**< Sequence number */ uint32_t ts; /**< Timestamp */ uint64_t ts_arrive; /**< Arrival Timestamp */ uint32_t ssrc; /**< Synchronization source */ uint32_t csrc[16]; /**< Contributing sources */ struct { uint16_t type; /**< Defined by profile */ uint16_t len; /**< Number of 32-bit words */ } x; }; /** RTCP Packet Types */ enum rtcp_type { RTCP_FIR = 192, /**< Full INTRA-frame Request (RFC 2032) */ RTCP_NACK = 193, /**< Negative Acknowledgement (RFC 2032) */ RTCP_SR = 200, /**< Sender Report */ RTCP_RR = 201, /**< Receiver Report */ RTCP_SDES = 202, /**< Source Description */ RTCP_BYE = 203, /**< Goodbye */ RTCP_APP = 204, /**< Application-defined */ RTCP_RTPFB = 205, /**< Transport layer FB message (RFC 4585) */ RTCP_PSFB = 206, /**< Payload-specific FB message (RFC 4585) */ RTCP_XR = 207, /**< Extended Report (RFC 3611) */ RTCP_AVB = 208, /**< AVB RTCP Packet (IEEE1733) */ }; /** SDES Types */ enum rtcp_sdes_type { RTCP_SDES_END = 0, /**< End of SDES list */ RTCP_SDES_CNAME = 1, /**< Canonical name */ RTCP_SDES_NAME = 2, /**< User name */ RTCP_SDES_EMAIL = 3, /**< User's electronic mail address */ RTCP_SDES_PHONE = 4, /**< User's phone number */ RTCP_SDES_LOC = 5, /**< Geographic user location */ RTCP_SDES_TOOL = 6, /**< Name of application or tool */ RTCP_SDES_NOTE = 7, /**< Notice about the source */ RTCP_SDES_PRIV = 8 /**< Private extension */ }; /** Transport Layer Feedback Messages */ enum rtcp_rtpfb { RTCP_RTPFB_GNACK = 1, /**< Generic NACK */ RTCP_RTPFB_TWCC = 15 /**< transport-wide-cc-extensions-01 */ }; /** Payload-Specific Feedback Messages */ enum rtcp_psfb { RTCP_PSFB_PLI = 1, /**< Picture Loss Indication (PLI) */ RTCP_PSFB_SLI = 2, /**< Slice Loss Indication (SLI) */ RTCP_PSFB_FIR = 4, /**< Full INTRA-frame Request (FIR) (RFC 5104) */ RTCP_PSFB_AFB = 15, /**< Application layer Feedback Messages */ }; /** Extended Report Block */ enum rtcp_xr { RTCP_XR_LRRB = 1, /**< Loss RLE Report Block */ RTCP_XR_DULRR = 2, /**< Duplicate RLE Report Block */ RTCP_XR_PRTR = 3, /**< Packet Receipt Times Report Block */ RTCP_XR_RRTR = 4, /**< Receiver Reference Time Report Block */ RTCP_XR_DLRR = 5, /**< DLRR Report Block */ RTCP_XR_SSR = 6, /**< Statistics Summary Report Block */ RTCP_XR_VMR = 7, /**< VoIP Metrics Report Block */ }; /** Reception report block */ struct rtcp_rr { uint32_t ssrc; /**< Data source being reported */ unsigned int fraction:8; /**< Fraction lost since last SR/RR */ signed int lost:24; /**< Cumul. no. pkts lost (signed!) */ uint32_t last_seq; /**< Extended last seq. no. received */ uint32_t jitter; /**< Interarrival jitter */ uint32_t lsr; /**< Last SR packet from this source */ uint32_t dlsr; /**< Delay since last SR packet */ }; /** SDES item */ struct rtcp_sdes_item { enum rtcp_sdes_type type; /**< Type of item (enum rtcp_sdes_type) */ uint8_t length; /**< Length of item (in octets) */ char *data; /**< Text, not null-terminated */ }; /** One RTCP Message */ struct rtcp_msg { /** RTCP Header */ struct rtcp_hdr { unsigned int version:2; /**< Protocol version */ unsigned int p:1; /**< Padding flag */ unsigned int count:5; /**< Varies by packet type */ unsigned int pt:8; /**< RTCP packet type */ uint16_t length; /**< Packet length in words */ } hdr; union { /** Sender report (SR) */ struct { uint32_t ssrc; /**< Sender generating report */ uint32_t ntp_sec; /**< NTP timestamp - seconds */ uint32_t ntp_frac; /**< NTP timestamp - fractions */ uint32_t rtp_ts; /**< RTP timestamp */ uint32_t psent; /**< RTP packets sent */ uint32_t osent; /**< RTP octets sent */ struct rtcp_rr *rrv; /**< Reception report blocks */ } sr; /** Reception report (RR) */ struct { uint32_t ssrc; /**< Receiver generating report*/ struct rtcp_rr *rrv; /**< Reception report blocks */ } rr; /** Source Description (SDES) */ struct rtcp_sdes { uint32_t src; /**< First SSRC/CSRC */ struct rtcp_sdes_item *itemv; /**< SDES items */ uint32_t n; /**< Number of SDES items */ } *sdesv; /** BYE */ struct { uint32_t *srcv; /**< List of sources */ char *reason; /**< Reason for leaving (opt.) */ } bye; /** Application-defined (APP) */ struct { uint32_t src; /**< SSRC/CSRC */ char name[4]; /**< Name (ASCII) */ uint8_t *data; /**< Application data (32 bits) */ size_t data_len; /**< Number of data bytes */ } app; /** Full INTRA-frame Request (FIR) packet */ struct { uint32_t ssrc; /**< SSRC for sender of this packet */ } fir; /** Negative ACKnowledgements (NACK) packet */ struct { uint32_t ssrc; /**< SSRC for sender of this packet */ uint16_t fsn; /**< First Sequence Number lost */ uint16_t blp; /**< Bitmask of lost packets */ } nack; /** Feedback (RTPFB or PSFB) packet */ struct { uint32_t ssrc_packet; uint32_t ssrc_media; uint32_t n; /** Feedback Control Information (FCI) */ union { struct gnack { uint16_t pid; uint16_t blp; } *gnackv; struct sli { uint16_t first; uint16_t number; uint8_t picid; } *sliv; struct fir_rfc5104 { uint32_t ssrc; uint8_t seq_n; } *firv; struct twcc { uint16_t seq; uint16_t count; uint32_t reftime; uint8_t fbcount; struct mbuf *chunks; struct mbuf *deltas; } *twccv; struct mbuf *afb; void *p; } fci; } fb; /** Extended Report (XR) packet */ /** https://datatracker.ietf.org/doc/html/rfc3611#section-4 */ struct { uint32_t ssrc; uint8_t bt; /**< Block type */ uint16_t block_len; /**< Number of 32-bit words */ /** Report blocks (RB) */ union { struct { uint32_t ntp_msw; uint32_t ntp_lsw; } rrtrb; struct { uint32_t ssrc; uint32_t lrr; uint32_t dlrr; } dlrrb; } rb; } xr; } r; }; /** RTCP Statistics */ struct rtcp_stats { struct { uint32_t sent; /**< Tx RTP Packets */ int lost; /**< Tx RTP Packets Lost */ uint32_t jit; /**< Tx Inter-arrival Jitter in [us] */ } tx; struct { uint32_t sent; /**< Rx RTP Packets */ int lost; /**< Rx RTP Packets Lost */ uint32_t jit; /**< Rx Inter-Arrival Jitter in [us] */ } rx; uint32_t rtt; /**< Current Round-Trip Time in [us] */ }; struct sa; struct re_printf; struct rtp_sock; /** * Defines the callback handler for received RTP packets * * @param src Source network address * @param hdr RTP header * @param mb RTP payload * @param arg Handler argument */ typedef void (rtp_recv_h)(const struct sa *src, const struct rtp_header *hdr, struct mbuf *mb, void *arg); /** * Defines the callback handler for received RTCP packets * * @param src Source network address * @param msg RTCP packet * @param arg Handler argument */ typedef void (rtcp_recv_h)(const struct sa *src, struct rtcp_msg *msg, void *arg); /* RTP api */ int rtp_alloc(struct rtp_sock **rsp); int rtp_listen(struct rtp_sock **rsp, int proto, const struct sa *ip, uint16_t min_port, uint16_t max_port, bool enable_rtcp, rtp_recv_h *recvh, rtcp_recv_h *rtcph, void *arg); int rtp_listen_single(struct rtp_sock **rsp, const struct sa *ip, uint16_t port, rtp_recv_h *recvh, void *arg); int rtp_open(struct rtp_sock **rsp, int af); int rtp_hdr_encode(struct mbuf *mb, const struct rtp_header *hdr); int rtp_hdr_decode(struct rtp_header *hdr, struct mbuf *mb); int rtp_encode(struct rtp_sock *rs, bool ext, bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb); int rtp_encode_seq(struct rtp_sock *rs, uint16_t seq, bool ext, bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb); int rtp_decode(struct rtp_sock *rs, struct mbuf *mb, struct rtp_header *hdr); int rtp_send(struct rtp_sock *rs, const struct sa *dst, bool ext, bool marker, uint8_t pt, uint32_t ts, uint64_t jfs_rt, struct mbuf *mb); int rtp_resend(struct rtp_sock *rs, uint16_t seq, const struct sa *dst, bool ext, bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb); int rtp_debug(struct re_printf *pf, const struct rtp_sock *rs); void *rtp_sock(const struct rtp_sock *rs); uint32_t rtp_sess_ssrc(const struct rtp_sock *rs); uint16_t rtp_sess_seq(const struct rtp_sock *rs); const struct sa *rtp_local(const struct rtp_sock *rs); int rtp_clear(struct rtp_sock *rs); /* RTCP session api */ void rtcp_start(struct rtp_sock *rs, const char *cname, const struct sa *peer); void rtcp_enable_mux(struct rtp_sock *rs, bool enabled); void rtcp_set_interval(struct rtp_sock *rs, uint32_t n); void rtcp_set_srate(struct rtp_sock *rs, uint32_t sr_tx, uint32_t sr_rx); void rtcp_set_srate_tx(struct rtp_sock *rs, uint32_t srate_tx); void rtcp_set_srate_rx(struct rtp_sock *rs, uint32_t srate_rx); int rtcp_send(struct rtp_sock *rs, struct mbuf *mb); int rtcp_send_app(struct rtp_sock *rs, const char name[4], const uint8_t *data, size_t len); int rtcp_send_fir(struct rtp_sock *rs, uint32_t ssrc); int rtcp_send_nack(struct rtp_sock *rs, uint16_t fsn, uint16_t blp); int rtcp_send_gnack(struct rtp_sock *rs, uint32_t ssrc, uint16_t fsn, uint16_t blp); int rtcp_send_twcc(struct rtp_sock *rs, uint32_t ssrc, struct twcc *twcc); int rtcp_send_pli(struct rtp_sock *rs, uint32_t fb_ssrc); int rtcp_send_fir_rfc5104(struct rtp_sock *rs, uint32_t ssrc, uint8_t fir_seqn); int rtcp_debug(struct re_printf *pf, const struct rtp_sock *rs); void *rtcp_sock(const struct rtp_sock *rs); int rtcp_stats(struct rtp_sock *rs, uint32_t ssrc, struct rtcp_stats *stats); int rtcp_send_bye_packet(struct rtp_sock *rs); /* RTCP utils */ int rtcp_encode(struct mbuf *mb, enum rtcp_type type, uint32_t count, ...); int rtcp_decode(struct rtcp_msg **msgp, struct mbuf *mb); int rtcp_msg_print(struct re_printf *pf, const struct rtcp_msg *msg); int rtcp_sdes_encode(struct mbuf *mb, uint32_t src, uint32_t itemc, ...); const char *rtcp_type_name(enum rtcp_type type); const char *rtcp_sdes_name(enum rtcp_sdes_type sdes); bool rtp_is_rtcp_packet(const struct mbuf *mb); void rtcp_calc_rtt(uint32_t *rtt, uint32_t lsr, uint32_t dlsr); /** * Check if a payload type is RTCP * * @param pt Payload type * * @return True if RTCP, otherwise false */ static inline bool rtp_pt_is_rtcp(uint8_t pt) { return 64 <= pt && pt <= 95; } /** * Calculate difference between two sequence numbers * * @param x First sequence number * @param y Second sequence number * * @return Difference between the two sequence numbers */ static inline int16_t rtp_seq_diff(uint16_t x, uint16_t y) { return (int16_t)(y - x); } /** * Compare two RTP sequence numbers * * @param x First sequence number * @param y Second sequence number * * @return true if x is less than y; false otherwise */ static inline bool rtp_seq_less(uint16_t x, uint16_t y) { return ((int16_t)(x - y)) < 0; } /** NTP Time */ struct rtp_ntp_time { uint32_t hi; /**< Seconds since 0h UTC on 1 January 1900 */ uint32_t lo; /**< Fraction of seconds */ }; /** Per-source state information */ struct rtp_source { struct sa rtp_peer; /**< IP-address of the RTP source */ uint16_t max_seq; /**< Highest seq. number seen */ 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 */ uint32_t received; /**< Packets received */ uint32_t expected_prior; /**< Packet expected at last interval */ uint32_t received_prior; /**< Packet received at last interval */ int transit; /**< Relative trans time for prev pkt */ uint32_t jitter; /**< Estimated jitter */ size_t rtp_rx_bytes; /**< Number of RTP bytes received */ uint64_t sr_recv; /**< When the last SR was received */ struct rtp_ntp_time last_sr;/**< NTP Timestamp from last SR recvd */ uint32_t rtp_ts; /**< RTP timestamp */ uint32_t last_rtp_ts; /**< Last RTP timestamp */ uint32_t psent; /**< RTP packets sent */ uint32_t osent; /**< RTP octets sent */ }; /* Source */ void rtp_source_init_seq(struct rtp_source *s, uint16_t seq); int rtp_source_update_seq(struct rtp_source *s, uint16_t seq); void rtp_source_calc_jitter(struct rtp_source *s, uint32_t rtp_ts, uint32_t arrival); int rtp_source_calc_lost(const struct rtp_source *s); uint8_t rtp_source_calc_fraction_lost(struct rtp_source *s); /** RTP Extensions for Transport-wide Congestion Control */ enum twcc_packet_state { TWCC_PK_NOT_RECEIVED = 0,/**< Packet not received */ TWCC_PK_RECEIVED, /**< Packet received, small delta */ TWCC_PK_LARGE_DELTA /**< Packet received, large or neg. delta */ }; struct rtcp_twcc_packet { struct le le; uint64_t ts; int32_t delta; enum twcc_packet_state state; uint16_t tseq; }; ================================================ FILE: include/re_rtpext.h ================================================ /** * @file re_rtpext.h Interface to RTP Header Extensions * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ /* * RTP Header Extensions */ #define RTPEXT_HDR_SIZE 4 #define RTPEXT_TYPE_MAGIC 0xbede /* One-Byte header */ #define RTPEXT_TYPE_MAGIC_LONG 0x1000 /* Two-Byte header */ enum { RTPEXT_ID_MIN = 1, RTPEXT_ID_MAX = 14, }; enum { RTPEXT_LEN_MIN = 1, RTPEXT_LEN_MAX = 16, RTPEXT_LEN_MAX_LONG = 256, }; /** Defines an RTP header extension */ struct rtpext { uint8_t id; /**< Identifier */ uint8_t len; /**< Length of data [bytes] */ uint8_t data[RTPEXT_LEN_MAX_LONG]; /**< Data field */ }; int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes); int rtpext_hdr_encode_long(struct mbuf *mb, size_t num_bytes); int rtpext_encode(struct mbuf *mb, uint8_t id, size_t len, const uint8_t *data); int rtpext_encode_long(struct mbuf *mb, uint8_t id, uint8_t len, const uint8_t *data); int rtpext_decode(struct rtpext *ext, struct mbuf *mb); int rtpext_decode_long(struct rtpext *ext, struct mbuf *mb); const struct rtpext *rtpext_find(const struct rtpext *extv, size_t extc, uint8_t id); ================================================ FILE: include/re_sa.h ================================================ /** * @file re_sa.h Interface to Socket Address * * Copyright (C) 2010 Creytiv.com */ #if defined(WIN32) #include #include #if !defined(UNIX_PATH_MAX) #define UNIX_PATH_MAX 108 typedef struct sockaddr_un { ADDRESS_FAMILY sun_family; char sun_path[UNIX_PATH_MAX]; } SOCKADDR_UN, *PSOCKADDR_UN; #endif #else #include #include #include #include #endif struct pl; /** Socket Address flags */ enum sa_flag { SA_ADDR = 1<<0, SA_PORT = 1<<1, SA_ALL = SA_ADDR | SA_PORT }; /** Defines a Socket Address */ struct sa { union { struct sockaddr sa; struct sockaddr_in in; struct sockaddr_in6 in6; #if !defined(HAVE_UNIXSOCK) || HAVE_UNIXSOCK == 1 struct sockaddr_un un; #endif } u; socklen_t len; }; void sa_init(struct sa *sa, int af); int sa_set(struct sa *sa, const struct pl *addr, uint16_t port); int sa_set_str(struct sa *sa, const char *addr, uint16_t port); void sa_set_in(struct sa *sa, uint32_t addr, uint16_t port); void sa_set_in6(struct sa *sa, const uint8_t *addr, uint16_t port); int sa_set_sa(struct sa *sa, const struct sockaddr *s); void sa_set_port(struct sa *sa, uint16_t port); int sa_decode(struct sa *sa, const char *str, size_t len); int sa_af(const struct sa *sa); uint32_t sa_in(const struct sa *sa); void sa_in6(const struct sa *sa, uint8_t *addr); int sa_addrinfo(const char *addr, struct sa *sa); int sa_ntop(const struct sa *sa, char *buf, int size); int sa_pton(const char *addr, struct sa *sa); uint16_t sa_port(const struct sa *sa); bool sa_isset(const struct sa *sa, int flag); uint32_t sa_hash(const struct sa *sa, int flag); void sa_cpy(struct sa *dst, const struct sa *src); bool sa_cmp(const struct sa *l, const struct sa *r, int flag); bool sa_is_linklocal(const struct sa *sa); bool sa_is_loopback(const struct sa *sa); bool sa_is_multicast(const struct sa *sa); bool sa_is_any(const struct sa *sa); void sa_set_scopeid(struct sa *sa, uint32_t scopeid); uint32_t sa_scopeid(const struct sa *sa); size_t sa_struct_get_size(void); struct re_printf; int sa_print_addr(struct re_printf *pf, const struct sa *sa); ================================================ FILE: include/re_sdp.h ================================================ /** * @file re_sdp.h Interface to Session Description Protocol (SDP) * * Copyright (C) 2010 Creytiv.com */ enum { SDP_VERSION = 0 }; /** SDP Direction */ enum sdp_dir { SDP_INACTIVE = 0, SDP_RECVONLY = 1, SDP_SENDONLY = 2, SDP_SENDRECV = 3, }; /** SDP Bandwidth type */ enum sdp_bandwidth { SDP_BANDWIDTH_MIN = 0, SDP_BANDWIDTH_CT = 0, /**< [kbit/s] Conference Total */ SDP_BANDWIDTH_AS, /**< [kbit/s] Application Specific */ SDP_BANDWIDTH_RS, /**< [bit/s] RTCP Senders (RFC 3556) */ SDP_BANDWIDTH_RR, /**< [bit/s] RTCP Receivers (RFC 3556) */ SDP_BANDWIDTH_TIAS, /**< [bit/s] Transport Independent Application Specific Maximum (RFC 3890) */ SDP_BANDWIDTH_MAX, }; struct sdp_format; typedef int(sdp_media_enc_h)(struct mbuf *mb, bool offer, void *arg); typedef int(sdp_fmtp_enc_h)(struct mbuf *mb, const struct sdp_format *fmt, bool offer, void *data); typedef bool(sdp_fmtp_cmp_h)(const char *params1, const char *params2, void *data); typedef bool(sdp_format_h)(struct sdp_format *fmt, void *arg); typedef bool(sdp_attr_h)(const char *name, const char *value, void *arg); /** SDP Format */ struct sdp_format { struct le le; char *id; char *params; char *rparams; char *name; sdp_fmtp_enc_h *ench; sdp_fmtp_cmp_h *cmph; void *data; bool ref; bool sup; int pt; uint32_t srate; uint8_t ch; }; /* session */ struct sdp_session; int sdp_session_alloc(struct sdp_session **sessp, const struct sa *laddr); void sdp_session_set_laddr(struct sdp_session *sess, const struct sa *laddr); const struct sa *sdp_session_laddr(struct sdp_session *sess); void sdp_session_set_lbandwidth(struct sdp_session *sess, enum sdp_bandwidth type, int32_t bw); int sdp_session_set_lattr(struct sdp_session *sess, bool replace, const char *name, const char *value, ...); void sdp_session_del_lattr(struct sdp_session *sess, const char *name); int32_t sdp_session_lbandwidth(const struct sdp_session *sess, enum sdp_bandwidth type); int32_t sdp_session_rbandwidth(const struct sdp_session *sess, enum sdp_bandwidth type); const char *sdp_session_rattr(const struct sdp_session *sess, const char *name); const char *sdp_session_rattr_apply(const struct sdp_session *sess, const char *name, sdp_attr_h *attrh, void *arg); const struct list *sdp_session_medial(const struct sdp_session *sess, bool local); int sdp_session_debug(struct re_printf *pf, const struct sdp_session *sess); /* media */ struct sdp_media; int sdp_media_add(struct sdp_media **mp, struct sdp_session *sess, const char *name, uint16_t port, const char *proto); int sdp_media_set_alt_protos(struct sdp_media *m, unsigned protoc, ...); void sdp_media_set_encode_handler(struct sdp_media *m, sdp_media_enc_h *ench, void *arg); void sdp_media_set_fmt_ignore(struct sdp_media *m, bool fmt_ignore); bool sdp_media_disabled(struct sdp_media *m); void sdp_media_set_disabled(struct sdp_media *m, bool disabled); void sdp_media_set_lport(struct sdp_media *m, uint16_t port); void sdp_media_set_laddr(struct sdp_media *m, const struct sa *laddr); void sdp_media_set_lbandwidth(struct sdp_media *m, enum sdp_bandwidth type, int32_t bw); void sdp_media_set_lport_rtcp(struct sdp_media *m, uint16_t port); void sdp_media_set_laddr_rtcp(struct sdp_media *m, const struct sa *laddr); void sdp_media_set_ldir(struct sdp_media *m, enum sdp_dir dir); int sdp_media_set_lattr(struct sdp_media *m, bool replace, const char *name, const char *value, ...); void sdp_media_del_lattr(struct sdp_media *m, const char *name); const char *sdp_media_proto(const struct sdp_media *m); uint16_t sdp_media_rport(const struct sdp_media *m); const struct sa *sdp_media_raddr(const struct sdp_media *m); const struct sa *sdp_media_laddr(const struct sdp_media *m); void sdp_media_raddr_rtcp(const struct sdp_media *m, struct sa *raddr); int32_t sdp_media_rbandwidth(const struct sdp_media *m, enum sdp_bandwidth type); enum sdp_dir sdp_media_ldir(const struct sdp_media *m); enum sdp_dir sdp_media_rdir(const struct sdp_media *m); enum sdp_dir sdp_media_dir(const struct sdp_media *m); const struct sdp_format *sdp_media_lformat(const struct sdp_media *m, int pt); const struct sdp_format *sdp_media_rformat(const struct sdp_media *m, const char *name); struct sdp_format *sdp_media_format(const struct sdp_media *m, bool local, const char *id, int pt, const char *name, int32_t srate, int8_t ch); struct sdp_format *sdp_media_format_apply(const struct sdp_media *m, bool local, const char *id, int pt, const char *name, int32_t srate, int8_t ch, sdp_format_h *fmth, void *arg); const struct list *sdp_media_format_lst(const struct sdp_media *m, bool local); const char *sdp_media_rattr(const struct sdp_media *m, const char *name); const char *sdp_media_session_rattr(const struct sdp_media *m, const struct sdp_session *sess, const char *name); const char *sdp_media_lattr_apply(const struct sdp_media *m, const char *name, sdp_attr_h *attrh, void *arg); const char *sdp_media_rattr_apply(const struct sdp_media *m, const char *name, sdp_attr_h *attrh, void *arg); const char *sdp_media_name(const struct sdp_media *m); int sdp_media_debug(struct re_printf *pf, const struct sdp_media *m); /* format */ int sdp_format_add(struct sdp_format **fmtp, struct sdp_media *m, bool prepend, const char *id, const char *name, uint32_t srate, uint8_t ch, sdp_fmtp_enc_h *ench, sdp_fmtp_cmp_h *cmph, void *data, bool ref, const char *params, ...); int sdp_format_set_params(struct sdp_format *fmt, const char *params, ...); bool sdp_format_cmp(const struct sdp_format *fmt1, const struct sdp_format *fmt2); int sdp_format_debug(struct re_printf *pf, const struct sdp_format *fmt); /* encode/decode */ int sdp_encode(struct mbuf **mbp, struct sdp_session *sess, bool offer); int sdp_decode(struct sdp_session *sess, struct mbuf *mb, bool offer); /* strings */ const char *sdp_dir_name(enum sdp_dir dir); const char *sdp_bandwidth_name(enum sdp_bandwidth type); extern const char sdp_attr_fmtp[]; extern const char sdp_attr_maxptime[]; extern const char sdp_attr_ptime[]; extern const char sdp_attr_rtcp[]; extern const char sdp_attr_rtpmap[]; extern const char sdp_media_audio[]; extern const char sdp_media_video[]; extern const char sdp_media_text[]; extern const char sdp_proto_rtpavp[]; extern const char sdp_proto_rtpsavp[]; /* utility functions */ enum sdp_dir sdp_dir_decode(const struct pl *pl); /** RTP Header Extensions, as defined in RFC 5285 */ struct sdp_extmap { struct pl name; struct pl attrs; enum sdp_dir dir; bool dir_set; uint32_t id; }; int sdp_extmap_decode(struct sdp_extmap *ext, const char *val); ================================================ FILE: include/re_sha.h ================================================ /** * @file re_sha.h Interface to SHA (Secure Hash Standard) functions * * Copyright (C) 2010 Creytiv.com */ /** SHA-1 Digest size in bytes */ #define SHA1_DIGEST_SIZE 20 #define SHA256_DIGEST_SIZE 32 #define SHA512_DIGEST_SIZE 64 #ifndef SHA_DIGEST_LENGTH /** SHA-1 Digest size in bytes (OpenSSL compat) */ #define SHA_DIGEST_LENGTH SHA1_DIGEST_SIZE #endif #ifndef SHA256_DIGEST_LENGTH /** SHA-256 Digest size in bytes (OpenSSL compat) */ #define SHA256_DIGEST_LENGTH SHA256_DIGEST_SIZE #endif #ifndef SHA512_DIGEST_LENGTH /** SHA-512 Digest size in bytes (OpenSSL compat) */ #define SHA512_DIGEST_LENGTH SHA512_DIGEST_SIZE #endif void sha1(const uint8_t *d, size_t n, uint8_t *md); void sha256(const uint8_t *d, size_t n, uint8_t *md); int sha256_printf(uint8_t md[32], const char *fmt, ...); ================================================ FILE: include/re_shim.h ================================================ /** * @file re_shim.h Interface to SHIM layer * * Copyright (C) 2015 - 2022 Alfred E. Heggestad */ /* RFC 4571 */ enum { SHIM_HDR_SIZE = 2 }; struct shim; typedef bool (shim_frame_h)(struct mbuf *mb, void *arg); int shim_insert(struct shim **shimp, struct tcp_conn *tc, int layer, shim_frame_h *frameh, void *arg); int shim_debug(struct re_printf *pf, const struct shim *shim); ================================================ FILE: include/re_sip.h ================================================ /** * @file re_sip.h Session Initiation Protocol * * Copyright (C) 2010 Creytiv.com */ /* forward declarations */ struct tls; enum { SIP_PORT = 5060, SIP_PORT_TLS = 5061, }; /** SIP Transport */ enum sip_transp { SIP_TRANSP_NONE = -1, SIP_TRANSP_UDP = 0, SIP_TRANSP_TCP, SIP_TRANSP_TLS, SIP_TRANSP_WS, SIP_TRANSP_WSS, SIP_TRANSPC, }; /** SIP Header ID (perfect hash value) */ enum sip_hdrid { SIP_HDR_ACCEPT = 3186, SIP_HDR_ACCEPT_CONTACT = 232, SIP_HDR_ACCEPT_ENCODING = 708, SIP_HDR_ACCEPT_LANGUAGE = 2867, SIP_HDR_ACCEPT_RESOURCE_PRIORITY = 1848, SIP_HDR_ALERT_INFO = 274, SIP_HDR_ALLOW = 2429, SIP_HDR_ALLOW_EVENTS = 66, SIP_HDR_ANSWER_MODE = 2905, SIP_HDR_AUTHENTICATION_INFO = 3144, SIP_HDR_AUTHORIZATION = 2503, SIP_HDR_CALL_ID = 3095, SIP_HDR_CALL_INFO = 586, SIP_HDR_CONTACT = 229, SIP_HDR_CONTENT_DISPOSITION = 1425, SIP_HDR_CONTENT_ENCODING = 580, SIP_HDR_CONTENT_LANGUAGE = 3371, SIP_HDR_CONTENT_LENGTH = 3861, SIP_HDR_CONTENT_TYPE = 809, SIP_HDR_CSEQ = 746, SIP_HDR_DATE = 1027, SIP_HDR_ENCRYPTION = 3125, SIP_HDR_ERROR_INFO = 21, SIP_HDR_EVENT = 3286, SIP_HDR_EXPIRES = 1983, SIP_HDR_FLOW_TIMER = 584, SIP_HDR_FROM = 1963, SIP_HDR_HIDE = 283, SIP_HDR_HISTORY_INFO = 2582, SIP_HDR_IDENTITY = 2362, SIP_HDR_IDENTITY_INFO = 980, SIP_HDR_IN_REPLY_TO = 1577, SIP_HDR_JOIN = 3479, SIP_HDR_MAX_BREADTH = 3701, SIP_HDR_MAX_FORWARDS = 3549, SIP_HDR_MIME_VERSION = 3659, SIP_HDR_MIN_EXPIRES = 1121, SIP_HDR_MIN_SE = 2847, SIP_HDR_ORGANIZATION = 3247, SIP_HDR_P_ACCESS_NETWORK_INFO = 1662, SIP_HDR_P_ANSWER_STATE = 42, SIP_HDR_P_ASSERTED_IDENTITY = 1233, SIP_HDR_P_ASSOCIATED_URI = 900, SIP_HDR_P_CALLED_PARTY_ID = 3347, SIP_HDR_P_CHARGING_FUNCTION_ADDRESSES = 2171, SIP_HDR_P_CHARGING_VECTOR = 25, SIP_HDR_P_DCS_TRACE_PARTY_ID = 3027, SIP_HDR_P_DCS_OSPS = 1788, SIP_HDR_P_DCS_BILLING_INFO = 2017, SIP_HDR_P_DCS_LAES = 693, SIP_HDR_P_DCS_REDIRECT = 1872, SIP_HDR_P_EARLY_MEDIA = 2622, SIP_HDR_P_MEDIA_AUTHORIZATION = 1035, SIP_HDR_P_PREFERRED_IDENTITY = 1263, SIP_HDR_P_PROFILE_KEY = 1904, SIP_HDR_P_REFUSED_URI_LIST = 1047, SIP_HDR_P_SERVED_USER = 1588, SIP_HDR_P_USER_DATABASE = 2827, SIP_HDR_P_VISITED_NETWORK_ID = 3867, SIP_HDR_PATH = 2741, SIP_HDR_PERMISSION_MISSING = 1409, SIP_HDR_PRIORITY = 3520, SIP_HDR_PRIV_ANSWER_MODE = 2476, SIP_HDR_PRIVACY = 3150, SIP_HDR_PROXY_AUTHENTICATE = 116, SIP_HDR_PROXY_AUTHORIZATION = 2363, SIP_HDR_PROXY_REQUIRE = 3562, SIP_HDR_RACK = 2523, SIP_HDR_REASON = 3732, SIP_HDR_RECORD_ROUTE = 278, SIP_HDR_REFER_SUB = 2458, SIP_HDR_REFER_TO = 1521, SIP_HDR_REFERRED_BY = 3456, SIP_HDR_REJECT_CONTACT = 285, SIP_HDR_REPLACES = 2534, SIP_HDR_REPLY_TO = 2404, SIP_HDR_REQUEST_DISPOSITION = 3715, SIP_HDR_REQUIRE = 3905, SIP_HDR_RESOURCE_PRIORITY = 1643, SIP_HDR_RESPONSE_KEY = 1548, SIP_HDR_RETRY_AFTER = 409, SIP_HDR_ROUTE = 661, SIP_HDR_RSEQ = 445, SIP_HDR_SECURITY_CLIENT = 1358, SIP_HDR_SECURITY_SERVER = 811, SIP_HDR_SECURITY_VERIFY = 519, SIP_HDR_SERVER = 973, SIP_HDR_SERVICE_ROUTE = 1655, SIP_HDR_SESSION_EXPIRES = 1979, SIP_HDR_SIP_ETAG = 1997, SIP_HDR_SIP_IF_MATCH = 3056, SIP_HDR_SUBJECT = 1043, SIP_HDR_SUBSCRIPTION_STATE = 2884, SIP_HDR_SUPPORTED = 119, SIP_HDR_TARGET_DIALOG = 3450, SIP_HDR_TIMESTAMP = 938, SIP_HDR_TO = 1449, SIP_HDR_TRIGGER_CONSENT = 3180, SIP_HDR_UNSUPPORTED = 982, SIP_HDR_USER_AGENT = 4064, SIP_HDR_VIA = 3961, SIP_HDR_WARNING = 2108, SIP_HDR_WWW_AUTHENTICATE = 2763, SIP_HDR_NONE = -1 }; enum rel100_mode { REL100_DISABLED = 0, REL100_ENABLED = 1, REL100_REQUIRED = 2, }; enum { SIP_T1 = 500, SIP_T2 = 4000, SIP_T4 = 5000, }; /** SIP Via header */ #define RE_RFC3261_BRANCH_ID "z9hG4bK" struct sip_via { struct pl sentby; struct sa addr; struct pl params; struct pl branch; struct pl val; enum sip_transp tp; }; /** SIP Address */ struct sip_addr { struct pl dname; struct pl auri; struct uri uri; struct pl params; }; /** SIP Tag address */ struct sip_taddr { struct pl dname; struct pl auri; struct uri uri; struct pl params; struct pl tag; struct pl val; }; /** SIP CSeq header */ struct sip_cseq { struct pl met; uint32_t num; }; /** SIP RAck header (RFC 3262) */ struct sip_rack { struct pl met; uint32_t rel_seq; uint32_t cseq; }; /** SIP Header */ struct sip_hdr { struct le le; /**< Linked-list element */ struct le he; /**< Hash-table element */ struct pl name; /**< SIP Header name */ struct pl val; /**< SIP Header value */ enum sip_hdrid id; /**< SIP Header id (unique) */ }; /** SIP Message */ struct sip_msg { struct sa src; /**< Source network address */ struct sa dst; /**< Destination network address */ struct pl ver; /**< SIP Version number */ struct pl met; /**< Request method */ struct pl ruri; /**< Raw request URI */ struct uri uri; /**< Parsed request URI */ uint16_t scode; /**< Response status code */ struct pl reason; /**< Response reason phrase */ struct list hdrl; /**< List of SIP Headers (struct sip_hdr) */ struct sip_via via; /**< Parsed first Via header */ struct sip_taddr to; /**< Parsed To header */ struct sip_taddr from; /**< Parsed From header */ struct sip_cseq cseq; /**< Parsed CSeq header */ struct sip_rack rack; /**< Parsed RAck header (RFC 3262) */ uint32_t rel_seq; /**< RSeq number (RFC 3262) */ struct msg_ctype ctyp; /**< Content Type */ struct pl callid; /**< Cached Call-ID header */ struct pl maxfwd; /**< Cached Max-Forwards header */ struct pl expires; /**< Cached Expires header */ struct pl clen; /**< Cached Content-Length header */ struct hash *hdrht; /**< Hash-table with all SIP headers */ struct mbuf *mb; /**< Buffer containing the SIP message */ void *sock; /**< Transport socket */ uint64_t tag; /**< Opaque tag */ enum sip_transp tp; /**< SIP Transport */ bool req; /**< True if Request, False if Response */ }; /** SIP Loop-state */ struct sip_loopstate { uint32_t failc; uint16_t last_scode; }; /** SIP Contact */ struct sip_contact { const char *uri; const struct sa *addr; enum sip_transp tp; }; /** SIP connection config */ struct sip_conncfg { struct le he; struct sa paddr; uint16_t srcport; }; /** SIP UAS Authentication */ struct sip_uas_auth { const char *realm; char *nonce; bool stale; }; struct sip; struct sip_lsnr; struct sip_request; struct sip_strans; struct sip_auth; struct sip_dialog; struct sip_keepalive; struct sip_uas_auth; struct dnsc; typedef bool(sip_msg_h)(const struct sip_msg *msg, void *arg); typedef int(sip_send_h)(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg); typedef int(sip_conn_h)(struct sa *src, const struct sa *dst, struct mbuf *mb, void *arg); typedef void(sip_resp_h)(int err, const struct sip_msg *msg, void *arg); typedef void(sip_cancel_h)(void *arg); typedef void(sip_exit_h)(void *arg); typedef int(sip_auth_h)(char **username, char **password, const char *realm, void *arg); typedef bool(sip_hdr_h)(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg); typedef void(sip_keepalive_h)(int err, void *arg); typedef int(digest_printf_h)(uint8_t *md, const char *fmt, ...); #define LIBRE_HAVE_SIPTRACE 1 typedef void(sip_trace_h)(bool tx, enum sip_transp tp, const struct sa *src, const struct sa *dst, const uint8_t *pkt, size_t len, void *arg); typedef int (sip_uas_auth_h)(uint8_t *ha1, const struct pl *user, const char *realm, void *arg); /* sip */ int sip_alloc(struct sip **sipp, struct dnsc *dnsc, uint32_t ctsz, uint32_t stsz, uint32_t tcsz, const char *software, sip_exit_h *exith, void *arg); void sip_close(struct sip *sip, bool force); int sip_listen(struct sip_lsnr **lsnrp, struct sip *sip, bool req, sip_msg_h *msgh, void *arg); int sip_debug(struct re_printf *pf, const struct sip *sip); int sip_send(struct sip *sip, void *sock, enum sip_transp tp, const struct sa *dst, struct mbuf *mb); int sip_send_conn(struct sip *sip, void *sock, enum sip_transp tp, const struct sa *dst, char *host, struct mbuf *mb, sip_conn_h *connh, void *arg); void sip_set_trace_handler(struct sip *sip, sip_trace_h *traceh); /* transport */ int sip_transp_add(struct sip *sip, enum sip_transp tp, const struct sa *laddr, ...); int sip_transp_add_sock(struct sip *sip, enum sip_transp tp, bool listen, const struct sa *laddr, ...); int sip_transp_add_websock(struct sip *sip, enum sip_transp tp, const struct sa *laddr, bool server, const char *cert, struct tls *tls); int sip_transp_add_ccert(struct sip *sip, const struct uri *uri, const char *ccertfile); void sip_transp_flush(struct sip *sip); bool sip_transp_isladdr(const struct sip *sip, enum sip_transp tp, const struct sa *laddr); const char *sip_transp_name(enum sip_transp tp); const char *sip_transp_param(enum sip_transp tp); enum sip_transp sip_transp_decode(const struct pl *pl); uint16_t sip_transp_port(enum sip_transp tp, uint16_t port); int sip_transp_laddr(struct sip *sip, struct sa *laddr, enum sip_transp tp, const struct sa *dst); int sip_transp_set_default(struct sip *sip, enum sip_transp tp); void sip_transp_rmladdr(struct sip *sip, const struct sa *laddr); int sip_settos(struct sip *sip, uint8_t tos); /* request */ int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, int metl, const char *uri, int uril, const struct uri *route, struct mbuf *mb, size_t sortkey, sip_send_h *sendh, sip_resp_h *resph, void *arg); int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, const char *uri, const struct uri *route, struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph, void *arg, const char *fmt, ...); int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, struct sip_dialog *dlg, uint32_t cseq, struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph, void *arg, const char *fmt, ...); void sip_request_cancel(struct sip_request *req); bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode); void sip_loopstate_reset(struct sip_loopstate *ls); bool sip_request_provrecv(const struct sip_request *req); /* reply */ int sip_strans_alloc(struct sip_strans **stp, struct sip *sip, const struct sip_msg *msg, sip_cancel_h *cancelh, void *arg); int sip_strans_reply(struct sip_strans **stp, struct sip *sip, const struct sip_msg *msg, const struct sa *dst, uint16_t scode, struct mbuf *mb); int sip_treplyf(struct sip_strans **stp, struct mbuf **mbp, struct sip *sip, const struct sip_msg *msg, bool rec_route, uint16_t scode, const char *reason, const char *fmt, ...); int sip_treply(struct sip_strans **stp, struct sip *sip, const struct sip_msg *msg, uint16_t scode, const char *reason); int sip_replyf(struct sip *sip, const struct sip_msg *msg, uint16_t scode, const char *reason, const char *fmt, ...); int sip_reply(struct sip *sip, const struct sip_msg *msg, uint16_t scode, const char *reason); void sip_reply_addr(struct sa *addr, const struct sip_msg *msg, bool rport); const struct sip_msg *sip_strans_cancel_msg(struct sip_strans *st); /* auth */ int sip_auth_authenticate(struct sip_auth *auth, const struct sip_msg *msg); int sip_auth_alloc(struct sip_auth **authp, sip_auth_h *authh, void *arg, bool ref); void sip_auth_reset(struct sip_auth *auth); int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met, const char *uri); /* contact */ void sip_contact_set(struct sip_contact *contact, const char *uri, const struct sa *addr, enum sip_transp tp); int sip_contact_print(struct re_printf *pf, const struct sip_contact *contact); /* dialog */ int sip_dialog_alloc(struct sip_dialog **dlgp, const char *uri, const char *to_uri, const char *from_name, const char *from_uri, const char *routev[], uint32_t routec); int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg); int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg); int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg, const struct sip_msg *msg); int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg); bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg); const char *sip_dialog_callid(const struct sip_dialog *dlg); int sip_dialog_set_callid(struct sip_dialog *dlg, const char *callid); void sip_dialog_set_srcport(struct sip_dialog *dlg, uint16_t srcport); uint16_t sip_dialog_srcport(struct sip_dialog *dlg); const char *sip_dialog_uri(const struct sip_dialog *dlg); const char *sip_dialog_ltag(const struct sip_dialog *dlg); const char *sip_dialog_rtag(const struct sip_dialog *dlg); uint32_t sip_dialog_lseq(const struct sip_dialog *dlg); uint32_t sip_dialog_lseqinv(const struct sip_dialog *dlg); enum sip_transp sip_dialog_tp(const struct sip_dialog *dlg); bool sip_dialog_established(const struct sip_dialog *dlg); bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg); bool sip_dialog_cmp_half(const struct sip_dialog *dlg, const struct sip_msg *msg); /* msg */ int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb); const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg, enum sip_hdrid id); const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg, bool fwd, enum sip_hdrid id, sip_hdr_h *h, void *arg); const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg, const char *name); const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg, bool fwd, const char *name, sip_hdr_h *h, void *arg); uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id); uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name); bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id, const char *value); bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name, const char *value); struct tcp_conn *sip_msg_tcpconn(const struct sip_msg *msg); void sip_msg_dump(const struct sip_msg *msg); int sip_addr_decode(struct sip_addr *addr, const struct pl *pl); int sip_via_decode(struct sip_via *via, const struct pl *pl); int sip_cseq_decode(struct sip_cseq *cseq, const struct pl *pl); int sip_rack_decode(struct sip_rack *rack, const struct pl *pl); /* keepalive */ int sip_keepalive_start(struct sip_keepalive **kap, struct sip *sip, const struct sip_msg *msg, uint32_t interval, sip_keepalive_h *kah, void *arg); /* sip_conncfg */ int sip_conncfg_set(struct sip *sip, const struct sa *paddr, const struct sip_conncfg *conncfg); /* sip_uas_auth */ int sip_uas_auth_gen(struct sip_uas_auth **authp, const struct sip_msg *msg, const char *realm); int sip_uas_auth_print(struct re_printf *pf, const struct sip_uas_auth *auth); int sip_uas_auth_check(struct sip_uas_auth *auth, const struct sip_msg *msg, sip_uas_auth_h *authh, void *arg); ================================================ FILE: include/re_sipevent.h ================================================ /** * @file re_sipevent.h SIP Event Framework * * Copyright (C) 2010 Creytiv.com */ /* Message Components */ struct sipevent_event { struct pl event; struct pl params; struct pl id; }; enum sipevent_subst { SIPEVENT_ACTIVE = 0, SIPEVENT_PENDING, SIPEVENT_TERMINATED, }; enum sipevent_reason { SIPEVENT_DEACTIVATED = 0, SIPEVENT_PROBATION, SIPEVENT_REJECTED, SIPEVENT_TIMEOUT, SIPEVENT_GIVEUP, SIPEVENT_NORESOURCE, }; struct sipevent_substate { enum sipevent_subst state; enum sipevent_reason reason; struct pl expires; struct pl retry_after; struct pl params; }; int sipevent_event_decode(struct sipevent_event *se, const struct pl *pl); int sipevent_substate_decode(struct sipevent_substate *ss, const struct pl *pl); const char *sipevent_substate_name(enum sipevent_subst state); const char *sipevent_reason_name(enum sipevent_reason reason); /* Listener Socket */ struct sipevent_sock; int sipevent_listen(struct sipevent_sock **sockp, struct sip *sip, uint32_t htsize_not, uint32_t htsize_sub, sip_msg_h *subh, void *arg); /* Notifier */ struct sipnot; typedef void (sipnot_close_h)(int err, const struct sip_msg *msg, void *arg); int sipevent_accept(struct sipnot **notp, struct sipevent_sock *sock, const struct sip_msg *msg, struct sip_dialog *dlg, const struct sipevent_event *event, uint16_t scode, const char *reason, uint32_t expires_min, uint32_t expires_dfl, uint32_t expires_max, const char *cuser, const char *ctype, sip_auth_h *authh, void *aarg, bool aref, sipnot_close_h *closeh, void *arg, const char *fmt, ...); int sipevent_notify(struct sipnot *sipnot, struct mbuf *mb, enum sipevent_subst state, enum sipevent_reason reason, uint32_t retry_after); int sipevent_notifyf(struct sipnot *sipnot, struct mbuf **mbp, enum sipevent_subst state, enum sipevent_reason reason, uint32_t retry_after, const char *fmt, ...); /* Subscriber */ struct sipsub; typedef int (sipsub_fork_h)(struct sipsub **subp, struct sipsub *osub, const struct sip_msg *msg, void *arg); typedef void (sipsub_notify_h)(struct sip *sip, const struct sip_msg *msg, void *arg); typedef void (sipsub_close_h)(int err, const struct sip_msg *msg, const struct sipevent_substate *substate, void *arg); int sipevent_subscribe(struct sipsub **subp, struct sipevent_sock *sock, const char *uri, const char *from_name, const char *from_uri, const char *event, const char *id, uint32_t expires, const char *cuser, const char *routev[], uint32_t routec, sip_auth_h *authh, void *aarg, bool aref, sipsub_fork_h *forkh, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...); int sipevent_dsubscribe(struct sipsub **subp, struct sipevent_sock *sock, struct sip_dialog *dlg, const char *event, const char *id, uint32_t expires, const char *cuser, sip_auth_h *authh, void *aarg, bool aref, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...); int sipevent_refer(struct sipsub **subp, struct sipevent_sock *sock, const char *uri, const char *from_name, const char *from_uri, const char *cuser, const char *routev[], uint32_t routec, sip_auth_h *authh, void *aarg, bool aref, sipsub_fork_h *forkh, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...); int sipevent_drefer(struct sipsub **subp, struct sipevent_sock *sock, struct sip_dialog *dlg, const char *cuser, sip_auth_h *authh, void *aarg, bool aref, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...); int sipevent_fork(struct sipsub **subp, struct sipsub *osub, const struct sip_msg *msg, sip_auth_h *authh, void *aarg, bool aref, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg); ================================================ FILE: include/re_sipreg.h ================================================ /** * @file re_sipreg.h SIP Registration * * Copyright (C) 2010 Creytiv.com */ struct sipreg; void sipreg_unregister(struct sipreg *reg); int sipreg_alloc(struct sipreg **regp, struct sip *sip, const char *reg_uri, const char *to_uri, const char *from_name, const char *from_uri, uint32_t expires, const char *cuser, const char *routev[], uint32_t routec, int regid, sip_auth_h *authh, void *aarg, bool aref, sip_resp_h *resph, void *arg, const char *params, const char *fmt, ...); int sipreg_send(struct sipreg *reg); int sipreg_set_rwait(struct sipreg *reg, uint32_t rwait); const struct sa *sipreg_laddr(const struct sipreg *reg); uint32_t sipreg_proxy_expires(const struct sipreg *reg); bool sipreg_registered(const struct sipreg *reg); bool sipreg_failed(const struct sipreg *reg); void sipreg_incfailc(struct sipreg *reg); int sipreg_set_fbregint(struct sipreg *reg, uint32_t fbregint); void sipreg_set_srcport(struct sipreg *reg, uint16_t srcport); int sipreg_set_contact_params(struct sipreg *reg, const char *cparams); ================================================ FILE: include/re_sipsess.h ================================================ /** * @file re_sipsess.h SIP Session * * Copyright (C) 2010 Creytiv.com */ struct sipsess_sock; struct sipsess; /** SDP Negotiation state */ enum sdp_neg_state { SDP_NEG_NONE = 0, SDP_NEG_LOCAL_OFFER, /**< SDP offer sent */ SDP_NEG_REMOTE_OFFER, /**< SDP offer received */ SDP_NEG_PREVIEW_ANSWER, /**< SDP preview answer sent */ SDP_NEG_DONE /**< SDP negotiation done */ }; typedef void (sipsess_conn_h)(const struct sip_msg *msg, void *arg); typedef int (sipsess_desc_h)(struct mbuf **descp, const struct sa *src, const struct sa *dst, void *arg); typedef int (sipsess_offer_h)(struct mbuf **descp, const struct sip_msg *msg, void *arg); typedef int (sipsess_answer_h)(const struct sip_msg *msg, void *arg); typedef void (sipsess_progr_h)(const struct sip_msg *msg, void *arg); typedef void (sipsess_estab_h)(const struct sip_msg *msg, void *arg); typedef void (sipsess_info_h)(struct sip *sip, const struct sip_msg *msg, void *arg); typedef void (sipsess_refer_h)(struct sip *sip, const struct sip_msg *msg, void *arg); typedef void (sipsess_close_h)(int err, const struct sip_msg *msg, void *arg); typedef void (sipsess_redirect_h)(const struct sip_msg *msg, const char *uri, void *arg); typedef void (sipsess_prack_h)(const struct sip_msg *msg, void *arg); int sipsess_listen(struct sipsess_sock **sockp, struct sip *sip, int htsize, sipsess_conn_h *connh, void *arg); int sipsess_connect(struct sipsess **sessp, struct sipsess_sock *sock, const char *to_uri, const char *from_name, const char *from_uri, const char *cuser, const char *routev[], uint32_t routec, const char *ctype, sip_auth_h *authh, void *aarg, bool aref, const char *callid, sipsess_desc_h *desch, sipsess_offer_h *offerh, sipsess_answer_h *answerh, sipsess_progr_h *progrh, sipsess_estab_h *estabh, sipsess_info_h *infoh, sipsess_refer_h *referh, sipsess_close_h *closeh, void *arg, const char *fmt, ...); int sipsess_accept(struct sipsess **sessp, struct sipsess_sock *sock, const struct sip_msg *msg, uint16_t scode, const char *reason, enum rel100_mode rel100, const char *cuser, const char *ctype, struct mbuf *desc, sip_auth_h *authh, void *aarg, bool aref, sipsess_offer_h *offerh, sipsess_answer_h *answerh, sipsess_estab_h *estabh, sipsess_info_h *infoh, sipsess_refer_h *referh, sipsess_close_h *closeh, void *arg, const char *fmt, ...); int sipsess_set_redirect_handler(struct sipsess *sess, sipsess_redirect_h *redirecth); int sipsess_set_prack_handler(struct sipsess *sess, sipsess_prack_h *prackh); int sipsess_progress(struct sipsess *sess, uint16_t scode, const char *reason, enum rel100_mode rel100, struct mbuf *desc, const char *fmt, ...); int sipsess_answer(struct sipsess *sess, uint16_t scode, const char *reason, struct mbuf *desc, const char *fmt, ...); int sipsess_reject(struct sipsess *sess, uint16_t scode, const char *reason, const char *fmt, ...); int sipsess_modify(struct sipsess *sess, struct mbuf *desc); int sipsess_info(struct sipsess *sess, const char *ctype, struct mbuf *body, sip_resp_h *resph, void *arg); int sipsess_set_close_headers(struct sipsess *sess, const char *hdrs, ...); bool sipsess_awaiting_prack(const struct sipsess *sess); bool sipsess_refresh_allowed(const struct sipsess *sess); void sipsess_close_all(struct sipsess_sock *sock); struct sip_dialog *sipsess_dialog(const struct sipsess *sess); void sipsess_abort(struct sipsess *sess); bool sipsess_ack_pending(const struct sipsess *sess); const struct sip_msg *sipsess_msg(const struct sipsess *sess); enum sdp_neg_state sipsess_sdp_neg_state(const struct sipsess *sess); ================================================ FILE: include/re_srtp.h ================================================ /** * @file re_srtp.h Secure Real-time Transport Protocol (SRTP) * * Copyright (C) 2010 Creytiv.com */ enum srtp_suite { SRTP_AES_CM_128_HMAC_SHA1_32, SRTP_AES_CM_128_HMAC_SHA1_80, SRTP_AES_256_CM_HMAC_SHA1_32, SRTP_AES_256_CM_HMAC_SHA1_80, SRTP_AES_128_GCM, SRTP_AES_256_GCM, }; enum srtp_flags { SRTP_UNENCRYPTED_SRTCP = 1<<1, }; struct srtp; int srtp_alloc(struct srtp **srtpp, enum srtp_suite suite, const uint8_t *key, size_t key_bytes, int flags); int srtp_encrypt(struct srtp *srtp, struct mbuf *mb); int srtp_decrypt(struct srtp *srtp, struct mbuf *mb); int srtcp_encrypt(struct srtp *srtp, struct mbuf *mb); int srtcp_decrypt(struct srtp *srtp, struct mbuf *mb); const char *srtp_suite_name(enum srtp_suite suite); ================================================ FILE: include/re_stun.h ================================================ /** * @file re_stun.h Session Traversal Utilities for (NAT) (STUN) * * Copyright (C) 2010 Creytiv.com */ /** STUN Protocol values */ enum { STUN_PORT = 3478, /**< STUN Port number */ STUNS_PORT = 5349, /**< STUNS Port number */ STUN_HEADER_SIZE = 20, /**< Number of bytes in header */ STUN_ATTR_HEADER_SIZE = 4, /**< Size of attribute header */ STUN_TID_SIZE = 12, /**< Number of bytes in transaction ID */ STUN_DEFAULT_RTO = 500, /**< Default Retrans Timeout in [ms] */ STUN_DEFAULT_RC = 7, /**< Default number of retransmits */ STUN_DEFAULT_RM = 16, /**< Wait time after last request is sent */ STUN_DEFAULT_TI = 39500 /**< Reliable timeout */ }; /** STUN Address Family */ enum stun_af { STUN_AF_IPv4 = 0x01, /**< IPv4 Address Family */ STUN_AF_IPv6 = 0x02 /**< IPv6 Address Family */ }; /** STUN Transport */ enum stun_transp { STUN_TRANSP_UDP = IPPROTO_UDP, /**< UDP-transport (struct udp_sock) */ STUN_TRANSP_TCP = IPPROTO_TCP, /**< TCP-transport (struct tcp_conn) */ STUN_TRANSP_DTLS, /**< DTLS-transport (struct tls_conn) */ }; /** STUN Methods */ enum stun_method { STUN_METHOD_BINDING = 0x001, STUN_METHOD_ALLOCATE = 0x003, STUN_METHOD_REFRESH = 0x004, STUN_METHOD_SEND = 0x006, STUN_METHOD_DATA = 0x007, STUN_METHOD_CREATEPERM = 0x008, STUN_METHOD_CHANBIND = 0x009, }; /** STUN Message class */ enum stun_msg_class { STUN_CLASS_REQUEST = 0x0, /**< STUN Request */ STUN_CLASS_INDICATION = 0x1, /**< STUN Indication */ STUN_CLASS_SUCCESS_RESP = 0x2, /**< STUN Success Response */ STUN_CLASS_ERROR_RESP = 0x3 /**< STUN Error Response */ }; /** STUN Attributes */ enum stun_attrib { /* Comprehension-required range (0x0000-0x7FFF) */ STUN_ATTR_MAPPED_ADDR = 0x0001, STUN_ATTR_CHANGE_REQ = 0x0003, STUN_ATTR_USERNAME = 0x0006, STUN_ATTR_MSG_INTEGRITY = 0x0008, STUN_ATTR_ERR_CODE = 0x0009, STUN_ATTR_UNKNOWN_ATTR = 0x000a, STUN_ATTR_CHANNEL_NUMBER = 0x000c, STUN_ATTR_LIFETIME = 0x000d, STUN_ATTR_XOR_PEER_ADDR = 0x0012, STUN_ATTR_DATA = 0x0013, STUN_ATTR_REALM = 0x0014, STUN_ATTR_NONCE = 0x0015, STUN_ATTR_XOR_RELAY_ADDR = 0x0016, STUN_ATTR_REQ_ADDR_FAMILY = 0x0017, STUN_ATTR_EVEN_PORT = 0x0018, STUN_ATTR_REQ_TRANSPORT = 0x0019, STUN_ATTR_DONT_FRAGMENT = 0x001a, STUN_ATTR_XOR_MAPPED_ADDR = 0x0020, STUN_ATTR_RSV_TOKEN = 0x0022, STUN_ATTR_PRIORITY = 0x0024, STUN_ATTR_USE_CAND = 0x0025, STUN_ATTR_RESP_PORT = 0x0027, /* Comprehension-optional range (0x8000-0xFFFF) */ STUN_ATTR_SOFTWARE = 0x8022, STUN_ATTR_ALT_SERVER = 0x8023, STUN_ATTR_FINGERPRINT = 0x8028, STUN_ATTR_CONTROLLED = 0x8029, STUN_ATTR_CONTROLLING = 0x802a, STUN_ATTR_RESP_ORIGIN = 0x802b, STUN_ATTR_OTHER_ADDR = 0x802c, }; struct stun_change_req { bool ip; bool port; }; struct stun_errcode { uint16_t code; char *reason; }; struct stun_unknown_attr { uint16_t typev[8]; uint32_t typec; }; struct stun_even_port { bool r; }; /** Defines a STUN attribute */ struct stun_attr { struct le le; uint16_t type; union { /* generic types */ struct sa sa; char *str; uint64_t uint64; uint32_t uint32; uint16_t uint16; uint8_t uint8; struct mbuf mb; /* actual attributes */ struct sa mapped_addr; struct stun_change_req change_req; char *username; uint8_t msg_integrity[20]; struct stun_errcode err_code; struct stun_unknown_attr unknown_attr; uint16_t channel_number; uint32_t lifetime; struct sa xor_peer_addr; struct mbuf data; char *realm; char *nonce; struct sa xor_relay_addr; uint8_t req_addr_family; struct stun_even_port even_port; uint8_t req_transport; struct sa xor_mapped_addr; uint64_t rsv_token; uint32_t priority; uint16_t resp_port; char *software; struct sa alt_server; uint32_t fingerprint; uint64_t controlled; uint64_t controlling; struct sa resp_origin; struct sa other_addr; } v; }; /** STUN Configuration */ struct stun_conf { uint32_t rto; /**< RTO Retransmission TimeOut [ms] */ uint32_t rc; /**< Rc Retransmission count (default 7) */ uint32_t rm; /**< Rm Max retransmissions (default 16) */ uint32_t ti; /**< Ti Timeout for reliable transport [ms] */ uint8_t tos; /**< Type-of-service field */ }; extern const char *stun_software; struct stun; struct stun_msg; struct stun_ctrans; typedef void(stun_resp_h)(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg); typedef void(stun_ind_h)(struct stun_msg *msg, void *arg); typedef bool(stun_attr_h)(const struct stun_attr *attr, void *arg); int stun_alloc(struct stun **stunp, const struct stun_conf *conf, stun_ind_h *indh, void *arg); struct stun_conf *stun_conf(struct stun *stun); int stun_send(int proto, void *sock, const struct sa *dst, struct mbuf *mb); int stun_recv(struct stun *stun, struct mbuf *mb); int stun_ctrans_recv(struct stun *stun, const struct stun_msg *msg, const struct stun_unknown_attr *ua); struct re_printf; int stun_debug(struct re_printf *pf, const struct stun *stun); int stun_request(struct stun_ctrans **ctp, struct stun *stun, int proto, void *sock, const struct sa *dst, size_t presz, uint16_t method, const uint8_t *key, size_t keylen, bool fp, stun_resp_h *resph, void *arg, uint32_t attrc, ...); int stun_reply(int proto, void *sock, const struct sa *dst, size_t presz, const struct stun_msg *req, const uint8_t *key, size_t keylen, bool fp, uint32_t attrc, ...); int stun_ereply(int proto, void *sock, const struct sa *dst, size_t presz, const struct stun_msg *req, uint16_t scode, const char *reason, const uint8_t *key, size_t keylen, bool fp, uint32_t attrc, ...); int stun_indication(int proto, void *sock, const struct sa *dst, size_t presz, uint16_t method, const uint8_t *key, size_t keylen, bool fp, uint32_t attrc, ...); int stun_msg_vencode(struct mbuf *mb, uint16_t method, uint8_t cls, const uint8_t *tid, const struct stun_errcode *ec, const uint8_t *key, size_t keylen, bool fp, uint8_t padding, uint32_t attrc, va_list ap); int stun_msg_encode(struct mbuf *mb, uint16_t method, uint8_t cls, const uint8_t *tid, const struct stun_errcode *ec, const uint8_t *key, size_t keylen, bool fp, uint8_t padding, uint32_t attrc, ...); int stun_msg_decode(struct stun_msg **msgpp, struct mbuf *mb, struct stun_unknown_attr *ua); uint16_t stun_msg_type(const struct stun_msg *msg); uint16_t stun_msg_class(const struct stun_msg *msg); uint16_t stun_msg_method(const struct stun_msg *msg); bool stun_msg_mcookie(const struct stun_msg *msg); const uint8_t *stun_msg_tid(const struct stun_msg *msg); struct stun_attr *stun_msg_attr(const struct stun_msg *msg, uint16_t type); struct stun_attr *stun_msg_attr_apply(const struct stun_msg *msg, stun_attr_h *h, void *arg); int stun_msg_chk_mi(const struct stun_msg *msg, const uint8_t *key, size_t keylen); int stun_msg_chk_fingerprint(const struct stun_msg *msg); void stun_msg_dump(const struct stun_msg *msg); const char *stun_class_name(uint16_t cls); const char *stun_method_name(uint16_t method); const char *stun_attr_name(uint16_t type); const char *stun_transp_name(enum stun_transp tp); void stun_generate_tid(uint8_t tid[STUN_TID_SIZE]); /* DNS Discovery of a STUN Server */ extern const char *stun_proto_udp; extern const char *stun_proto_tcp; extern const char *stun_usage_binding; extern const char *stuns_usage_binding; extern const char *stun_usage_relay; extern const char *stuns_usage_relay; /** * Defines the STUN Server Discovery handler * * @param err Errorcode * @param srv IP Address and port of STUN Server * @param arg Handler argument */ typedef void (stun_dns_h)(int err, const struct sa *srv, void *arg); struct stun_dns; struct dnsc; int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc, const char *service, const char *proto, int af, const char *domain, uint16_t port, stun_dns_h *dnsh, void *arg); /* NAT Keepalives */ struct stun_keepalive; /** * Defines the STUN Keepalive Mapped-Address handler * * @param err Errorcode * @param map Mapped Address * @param arg Handler argument */ typedef void (stun_mapped_addr_h)(int err, const struct sa *map, void *arg); int stun_keepalive_alloc(struct stun_keepalive **skap, int proto, void *sock, int layer, const struct sa *dst, const struct stun_conf *conf, stun_mapped_addr_h *mah, void *arg); void stun_keepalive_enable(struct stun_keepalive *ska, uint32_t interval); /* STUN Reason Phrase */ extern const char *stun_reason_300; extern const char *stun_reason_400; extern const char *stun_reason_401; extern const char *stun_reason_403; extern const char *stun_reason_420; extern const char *stun_reason_437; extern const char *stun_reason_438; extern const char *stun_reason_440; extern const char *stun_reason_441; extern const char *stun_reason_442; extern const char *stun_reason_443; extern const char *stun_reason_486; extern const char *stun_reason_500; extern const char *stun_reason_508; ================================================ FILE: include/re_sys.h ================================================ /** * @file re_sys.h Interface to system module * * Copyright (C) 2010 Creytiv.com */ #include #ifndef RE_VERSION #define RE_VERSION "?" #endif /** * @def ARCH * * Architecture */ #ifndef ARCH #define ARCH "?" #endif /** * @def OS * * Operating System */ #ifndef OS #ifdef WIN32 #define OS "win32" #else #define OS "?" #endif #endif struct re_printf; struct mbuf; int sys_kernel_get(struct re_printf *pf, void *unused); int sys_build_get(struct re_printf *pf, void *unused); const char *sys_arch_get(void); const char *sys_os_get(void); const char *sys_libre_version_get(void); const char *sys_username(void); int sys_getenv(char **env, const char *name); int sys_coredump_set(bool enable); int sys_daemon(void); void sys_usleep(unsigned int us); static inline void sys_msleep(unsigned int ms) { sys_usleep(ms * 1000); } uint16_t sys_htols(uint16_t v); uint32_t sys_htoll(uint32_t v); uint16_t sys_ltohs(uint16_t v); uint32_t sys_ltohl(uint32_t v); uint64_t sys_htonll(uint64_t v); uint64_t sys_ntohll(uint64_t v); /* Random */ uint16_t rand_u16(void); uint32_t rand_u32(void); uint64_t rand_u64(void); char rand_char(void); void rand_str(char *str, size_t size); void rand_bytes(uint8_t *p, size_t size); /* File-System */ int fs_mkdir(const char *path, uint16_t mode); int fs_gethome(char *path, size_t sz); bool fs_isdir(const char *path); bool fs_isfile(const char *file); int fs_fopen(FILE **fp, const char *file, const char *mode); int fs_fread(struct mbuf **mbp, const char *path); void fs_stdio_hide(void); void fs_stdio_restore(void); ================================================ FILE: include/re_tcp.h ================================================ /** * @file re_tcp.h Interface to Transport Control Protocol * * Copyright (C) 2010 Creytiv.com */ struct sa; struct tcp_sock; struct tcp_conn; /** * Defines the incoming TCP connection handler * * @param peer Network address of peer * @param arg Handler argument */ typedef void (tcp_conn_h)(const struct sa *peer, void *arg); /** * Defines the TCP connection established handler * * @param arg Handler argument */ typedef void (tcp_estab_h)(void *arg); /** * Defines the TCP connection data send handler * * @param arg Handler argument */ typedef void (tcp_send_h)(void *arg); /** * Defines the TCP connection data receive handler * * @param mb Buffer with data * @param arg Handler argument */ typedef void (tcp_recv_h)(struct mbuf *mb, void *arg); /** * Defines the TCP connection close handler * * @param err Error code * @param arg Handler argument */ typedef void (tcp_close_h)(int err, void *arg); /* TCP Socket */ int tcp_sock_alloc(struct tcp_sock **tsp, const struct sa *local, tcp_conn_h *ch, void *arg); struct tcp_sock *tcp_sock_dup(struct tcp_sock *tso); int tcp_sock_bind(struct tcp_sock *ts, const struct sa *local); int tcp_sock_listen(struct tcp_sock *ts, int backlog); int tcp_accept(struct tcp_conn **tcp, struct tcp_sock *ts, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg); void tcp_reject(struct tcp_sock *ts); int tcp_sock_local_get(const struct tcp_sock *ts, struct sa *local); int tcp_settos(struct tcp_sock *ts, uint32_t tos); int tcp_conn_settos(struct tcp_conn *tc, uint32_t tos); /* TCP Connection */ int tcp_sock_alloc_fd(struct tcp_sock **tsp, re_sock_t fd, tcp_conn_h *ch, void *arg); int tcp_conn_alloc(struct tcp_conn **tcp, const struct sa *peer, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg); int tcp_conn_bind(struct tcp_conn *tc, const struct sa *local); int tcp_conn_connect(struct tcp_conn *tc, const struct sa *peer); int tcp_send(struct tcp_conn *tc, struct mbuf *mb); int tcp_set_send(struct tcp_conn *tc, tcp_send_h *sendh); void tcp_set_handlers(struct tcp_conn *tc, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg); void tcp_conn_rxsz_set(struct tcp_conn *tc, size_t rxsz); void tcp_conn_txqsz_set(struct tcp_conn *tc, size_t txqsz); int tcp_conn_local_get(const struct tcp_conn *tc, struct sa *local); int tcp_conn_peer_get(const struct tcp_conn *tc, struct sa *peer); size_t tcp_conn_txqsz(const struct tcp_conn *tc); /* High-level API */ int tcp_listen(struct tcp_sock **tsp, const struct sa *local, tcp_conn_h *ch, void *arg); int tcp_connect(struct tcp_conn **tcp, const struct sa *peer, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg); int tcp_connect_bind(struct tcp_conn **tcp, const struct sa *peer, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, const struct sa *local, void *arg); int tcp_local_get(const struct tcp_sock *ts, struct sa *local); /* Helper API */ typedef bool (tcp_helper_estab_h)(int *err, bool active, void *arg); typedef bool (tcp_helper_send_h)(int *err, struct mbuf *mb, void *arg); typedef bool (tcp_helper_recv_h)(int *err, struct mbuf *mb, bool *estab, void *arg); struct tcp_helper; int tcp_register_helper(struct tcp_helper **thp, struct tcp_conn *tc, int layer, tcp_helper_estab_h *eh, tcp_helper_send_h *sh, tcp_helper_recv_h *rh, void *arg); int tcp_send_helper(struct tcp_conn *tc, struct mbuf *mb, struct tcp_helper *th); bool tcp_sendq_used(struct tcp_conn *tc); ================================================ FILE: include/re_telev.h ================================================ /** * @file re_telev.h Interface to Telephony Events (RFC 4733) * * Copyright (C) 2010 Creytiv.com */ enum { TELEV_PTIME = 50, TELEV_SRATE = 8000 }; struct telev; extern const char telev_rtpfmt[]; int telev_alloc(struct telev **tp, uint32_t ptime); int telev_set_srate(struct telev *tel, uint32_t srate); int telev_send(struct telev *tel, int event, bool end); int telev_recv(struct telev *tel, struct mbuf *mb, int *event, bool *end); int telev_poll(struct telev *tel, bool *marker, struct mbuf *mb); bool telev_is_empty(const struct telev *tel); int telev_digit2code(int digit); int telev_code2digit(int code); ================================================ FILE: include/re_thread.h ================================================ /** * @file re_thread.h Thread support * * Inspired by C11 thread support this provides a cross platform interfaces to * thread, mutex and condition handling (C11, POSIX and Windows Threads). * * Preferred order: * * - C11 threads (glibc>=2.28, musl, FreeBSD>=10) * - Windows Thread API * - POSIX PTHREAD (Linux/UNIX) * * Copyright (C) 2022 Sebastian Reimers */ #ifndef RE_H_THREAD__ #define RE_H_THREAD__ #if defined(HAVE_THREADS) #include #else #if defined(WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include struct thrd_win32 { HANDLE hdl; DWORD id; }; #define ONCE_FLAG_INIT INIT_ONCE_STATIC_INIT typedef INIT_ONCE once_flag; typedef struct thrd_win32 thrd_t; typedef CONDITION_VARIABLE cnd_t; typedef CRITICAL_SECTION mtx_t; typedef DWORD tss_t; #else #include #include #ifndef ONCE_FLAG_INIT #define ONCE_FLAG_INIT PTHREAD_ONCE_INIT typedef pthread_once_t once_flag; #endif typedef pthread_t thrd_t; typedef pthread_cond_t cnd_t; typedef pthread_mutex_t mtx_t; typedef pthread_key_t tss_t; #endif enum { mtx_plain = 0, mtx_try = 1, mtx_timed = 2, mtx_recursive = 4 }; /* Exit and error codes. */ enum { thrd_success = 0, thrd_busy = 1, thrd_error = 2, thrd_nomem = 3, thrd_timedout = 4 }; typedef void (*tss_dtor_t)(void *); typedef int (*thrd_start_t)(void *); /****************************************************************************** * Thread functions *****************************************************************************/ /** * Creates a new thread * * @param thr Pointer to new thread * @param func Function to execute * @param arg Argument to pass to the function * * @return thrd_success on success, otherwise thrd_error */ int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); /** * Checks whether `lhs` and `rhs` refer to the same thread. * * @param lhs Left hand side thread * @param rhs Right hand side thread * * @return Non-zero value if lhs and rhs refer to the same value, 0 otherwise. */ int thrd_equal(thrd_t lhs, thrd_t rhs); /** * Return the identifier of the calling thread. * * @return Current thread */ thrd_t thrd_current(void); /** * Detaches the thread identified by `thr` from the current environment. * * @param thr Thread * * @return thrd_success on success, otherwise thrd_error */ int thrd_detach(thrd_t thr); /** * Blocks the current thread until the thread identified by `thr` finishes * execution * * @param thr Thread * @param res Result code location * * @return thrd_success on success, otherwise thrd_error */ int thrd_join(thrd_t thr, int *res); /** * Calls a function exactly once * * @param flag Pointer to object initialized by ONCE_FLAG_INIT * @param func The function to execute only once */ void call_once(once_flag *flag, void (*func)(void)); /** * Terminates the calling thread * * @param res The result value to return */ void thrd_exit(int res); /****************************************************************************** * Condition functions *****************************************************************************/ /** * Initializes new condition variable * * @param cnd Pointer to a variable to store condition variable * * @return thrd_success on success, otherwise thrd_error */ int cnd_init(cnd_t *cnd); /** * Unblocks one thread blocked on a condition variable * * @param cnd Pointer to condition variable * * @return thrd_success on success, otherwise thrd_error */ int cnd_signal(cnd_t *cnd); /** * Unblocks all thrds blocked on a condition variable * * @param cnd Pointer to condition variable * * @return thrd_success on success, otherwise thrd_error */ int cnd_broadcast(cnd_t *cnd); /** * Blocks on a condition variable * * @param cnd Pointer to condition variable * @param mtx Lock mutex pointer * * @return thrd_success on success, otherwise thrd_error */ int cnd_wait(cnd_t *cnd, mtx_t *mtx); /** * Blocks on a condition variable with timeout (TIME_UTC based) * * @param cnd Pointer to condition variable * @param mtx Lock mutex pointer * @param abstime Pointer to timeout time * * @return thrd_success on success, thrd_timedout if the timeout time * has been reached before the mutex is locked, otherwise thrd_error */ int cnd_timedwait(cnd_t *cnd, mtx_t *mtx, const struct timespec *abstime); /** * Destroys the condition variable pointed to by cnd. * If there are thrds waiting on cnd, the behavior is undefined. * * @param cnd pointer to the condition variable to destroy */ void cnd_destroy(cnd_t *cnd); /****************************************************************************** * Mutex functions *****************************************************************************/ /** * Creates a new mutex object with type. The object pointed to by mutex is set * to an identifier of the newly created mutex. * * @param mtx Pointer to the mutex to initialize * @param type The type of the mutex * * @return thrd_success on success, otherwise thrd_error */ int mtx_init(mtx_t *mtx, int type); /** * Blocks the current thread until the mutex pointed to by mutex is locked. * The behavior is undefined if the current thread has already locked the * mutex and the mutex is not recursive. * * @param mtx Pointer to the mutex * * @return thrd_success on success, otherwise thrd_error */ int mtx_lock(mtx_t *mtx); /** * Tries to lock the mutex pointed to by mutex without blocking. * Returns immediately if the mutex is already locked. * * @param mtx Pointer to the mutex * * @return thrd_success on success, thrd_busy if already locked, * otherwise thrd_error */ int mtx_trylock(mtx_t *mtx); /** * Unlocks the mutex pointed to by mutex. * * @param mtx Pointer to the mutex * * @return thrd_success on success, otherwise thrd_error */ int mtx_unlock(mtx_t *mtx); /** * Destroys the mutex pointed to by mutex. * If there are threads waiting on mutex, the behavior is undefined. * * @param mtx Pointer to the mutex */ void mtx_destroy(mtx_t *mtx); /****************************************************************************** * Thread-local storage functions *****************************************************************************/ int tss_create(tss_t *key, tss_dtor_t destructor); void *tss_get(tss_t key); int tss_set(tss_t key, void *val); void tss_delete(tss_t key); #endif /* C11 threads fallback */ /****************************************************************************** * Extra - non C11 helpers * (We avoid tss_ mtx_ cnd_ prefixes since these reserved for functions with * different return values) *****************************************************************************/ /* Ideas: */ /* int thread_prio(enum thrd_prio prio) */ /* void thread_print(struct re_printf *pf, void *unused); */ /** * Allocates and initializes a new mutex * * @param mtx Pointer to new mutex * * @return 0 if success, otherwise errorcode */ int mutex_alloc(mtx_t **mtx); int mutex_alloc_tp(mtx_t **mtx, int type); /** * Creates a new thread with name * * @param thr Pointer to new thread * @param name Unique name for a thread * @param func Function to execute * @param arg Argument to pass to the function * * @return 0 if success, otherwise errorcode */ int thread_create_name(thrd_t *thr, const char *name, thrd_start_t func, void *arg); #endif /* RE_H_THREAD__ */ ================================================ FILE: include/re_tls.h ================================================ /** * @file re_tls.h Interface to Transport Layer Security * * Copyright (C) 2010 Creytiv.com */ struct tls; struct tls_conn; struct tcp_conn; struct udp_sock; /** Defines the TLS method */ enum tls_method { TLS_METHOD_TLS, TLS_METHOD_SSLV23, /* deprecated - fallback to TLS_METHOD_TLS */ TLS_METHOD_DTLS, /* DTLS 1.0 and 1.2 */ TLS_METHOD_DTLSV1, /* deprecated - fallback to TLS_METHOD_DTLS */ TLS_METHOD_DTLSV1_2, /* deprecated - fallback to TLS_METHOD_DTLS */ }; enum tls_fingerprint { TLS_FINGERPRINT_SHA256, }; enum tls_keytype { TLS_KEYTYPE_RSA, TLS_KEYTYPE_EC, }; enum tls_resume_mode { TLS_RESUMPTION_NONE = 0, TLS_RESUMPTION_IDS = (1 << 0), TLS_RESUMPTION_TICKETS = (1 << 1), TLS_RESUMPTION_ALL = TLS_RESUMPTION_IDS | TLS_RESUMPTION_TICKETS, }; struct tls_conn_d { int (*verifyh) (int ok, void *arg); void *arg; }; int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile, const char *pwd); int tls_add_ca(struct tls *tls, const char *cafile); int tls_add_cafile_path(struct tls *tls, const char *cafile, const char *capath); int tls_add_capem(const struct tls *tls, const char *capem); int tls_add_crlpem(const struct tls *tls, const char *pem); int tls_set_selfsigned_ec(struct tls *tls, const char *cn, const char *curve_n); int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert, const char *key, size_t len_key); int tls_set_certificate(struct tls *tls, const char *cert, size_t len); int tls_set_certificate_chain_pem(struct tls *tls, const char *chain, size_t len_chain); int tls_set_certificate_chain(struct tls *tls, const char *path); void tls_set_verify_client_trust_all(struct tls *tls); int tls_set_verify_client_handler(struct tls_conn *tc, int depth, int (*verifyh) (int ok, void *arg), void *arg); int tls_set_srtp(struct tls *tls, const char *suites); int tls_fingerprint(const struct tls *tls, enum tls_fingerprint type, uint8_t *md, size_t size); int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type, uint8_t *md, size_t size); int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size); int tls_set_verify_purpose(struct tls *tls, const char *purpose); int tls_peer_verify(const struct tls_conn *tc); int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite, uint8_t *cli_key, size_t cli_key_size, uint8_t *srv_key, size_t srv_key_size); const char *tls_cipher_name(const struct tls_conn *tc); int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count); int tls_set_verify_server(struct tls_conn *tc, const char *host); int tls_verify_client(struct tls_conn *tc); int tls_get_issuer(struct tls *tls, struct mbuf *mb); int tls_get_subject(struct tls *tls, struct mbuf *mb); void tls_disable_verify_server(struct tls *tls); void tls_enable_verify_client(struct tls *tls, bool enable); int tls_set_resumption(struct tls *tls, enum tls_resume_mode mode); int tls_set_min_proto_version(struct tls *tls, int version); int tls_set_max_proto_version(struct tls *tls, int version); int tls_set_session_reuse(struct tls *tls, int enabled); bool tls_get_session_reuse(const struct tls_conn *tc); int tls_reuse_session(const struct tls_conn *tc); bool tls_session_reused(const struct tls_conn *tc); int tls_update_sessions(const struct tls_conn *tc); void tls_set_posthandshake_auth(struct tls *tls, int value); /* TCP */ int tls_conn_change_cert(struct tls_conn *tc, const char *file); int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp, int layer); int tls_verify_client_post_handshake(struct tls_conn *tc); const struct tcp_conn *tls_get_tcp_conn(const struct tls_conn *tc); /* UDP (DTLS) */ typedef void (dtls_conn_h)(const struct sa *peer, void *arg); typedef void (dtls_estab_h)(void *arg); typedef void (dtls_recv_h)(struct mbuf *mb, void *arg); typedef void (dtls_close_h)(int err, void *arg); struct dtls_sock; int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr, struct udp_sock *us, uint32_t htsize, int layer, dtls_conn_h *connh, void *arg); struct udp_sock *dtls_udp_sock(struct dtls_sock *sock); void dtls_set_mtu(struct dtls_sock *sock, size_t mtu); int dtls_connect(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, const struct sa *peer, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg); int dtls_accept(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg); int dtls_send(struct tls_conn *tc, struct mbuf *mb); void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg); const struct sa *dtls_peer(const struct tls_conn *tc); void dtls_set_peer(struct tls_conn *tc, const struct sa *peer); void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src, struct mbuf *mb); void dtls_set_single(struct dtls_sock *sock, bool single); struct x509_st; struct evp_pkey_st; int tls_set_certificate_openssl(struct tls *tls, struct x509_st *cert, struct evp_pkey_st *pkey, bool up_ref); int tls_add_certf(struct tls *tls, const char *certf, const char *host); ================================================ FILE: include/re_tmr.h ================================================ /** * @file re_tmr.h Interface to timer implementation * * Copyright (C) 2010 Creytiv.com */ #include "re_thread.h" #include "re_atomic.h" /** * Defines the timeout handler * * @param arg Handler argument */ typedef void (tmr_h)(void *arg); struct tmrl; /** Defines a timer */ struct tmr { struct le le; /**< Linked list element */ RE_ATOMIC uintptr_t llock; /**< List Mutex lock */ tmr_h *th; /**< Timeout handler */ void *arg; /**< Handler argument */ uint64_t jfs; /**< Jiffies for timeout */ const char *file; int line; }; #define TMR_INIT {.le = LE_INIT} int tmrl_alloc(struct tmrl **tmrl); void tmr_poll(struct tmrl *tmrl); uint64_t tmr_jiffies_usec(void); uint64_t tmr_jiffies(void); uint64_t tmr_jiffies_rt_usec(void); int tmr_timespec_get(struct timespec *tp, uint64_t offset); uint64_t tmr_next_timeout(struct tmrl *tmrl); void tmr_debug(void); int tmr_status(struct re_printf *pf, void *unused); void tmr_init(struct tmr *tmr); void tmr_start_dbg(struct tmr *tmr, uint64_t delay, tmr_h *th, void *arg, const char *file, int line); void tmr_continue_dbg(struct tmr *tmr, uint64_t delay, tmr_h *th, void *arg, const char *file, int line); uint32_t tmrl_count(struct tmrl *tmrl); /** * @def tmr_start(tmr, delay, th, arg) * * Start a timer * * @param tmr Timer to start * @param delay Timer delay in [ms] * @param th Timeout handler * @param arg Handler argument */ #define tmr_start(tmr, delay, th, arg) \ tmr_start_dbg(tmr, delay, th, arg, __FILE__, __LINE__) /** * @def tmr_continue(tmr, delay, th, arg) * * Continue a previously started timer with exactly added delay * * @param tmr Timer to start * @param delay Timer delay in [ms] * @param th Timeout handler * @param arg Handler argument */ #define tmr_continue(tmr, delay, th, arg) \ tmr_continue_dbg(tmr, delay, th, arg, __FILE__, __LINE__) void tmr_cancel(struct tmr *tmr); uint64_t tmr_get_expire(const struct tmr *tmr); /** * Check if the timer is running * * @param tmr Timer to check * * @return true if running, false if not running */ static inline bool tmr_isrunning(const struct tmr *tmr) { return tmr ? NULL != tmr->th : false; } ================================================ FILE: include/re_trace.h ================================================ /** * @file re_trace.h RE_TRACE helpers * JSON traces (chrome://tracing) */ struct pl; struct mbuf; typedef enum { RE_TRACE_ARG_NONE, RE_TRACE_ARG_INT, RE_TRACE_ARG_STRING_CONST, RE_TRACE_ARG_STRING_COPY, } re_trace_arg_type; struct re_trace_event_s { const char *name; const char *cat; struct pl *id; uint64_t ts; int pid; unsigned long tid; char ph; re_trace_arg_type arg_type; const char *arg_name; union { const char *a_str; int a_int; } arg; }; typedef void(re_trace_line_h)(const struct re_trace_event_s *e, struct mbuf *json); int re_trace_init(const char *json_file); int re_trace_close(void); int re_trace_flush(void); void re_trace_event(const char *cat, const char *name, char ph, struct pl *id, re_trace_arg_type arg_type, const char *arg_name, void *arg_value); void re_set_trace_line_h(re_trace_line_h *trace_h); #ifdef RE_TRACE_ENABLED #define RE_TRACE_BEGIN(c, n) \ re_trace_event(c, n, 'B', NULL, RE_TRACE_ARG_NONE, NULL, NULL) #define RE_TRACE_END(c, n) \ re_trace_event(c, n, 'E', NULL, RE_TRACE_ARG_NONE, NULL, NULL) #define RE_TRACE_ID_BEGIN(c, n, id) \ re_trace_event(c, n, 'B', id, RE_TRACE_ARG_NONE, NULL, NULL) #define RE_TRACE_ID_END(c, n, id) \ re_trace_event(c, n, 'E', id, RE_TRACE_ARG_NONE, NULL, NULL) #define RE_TRACE_INSTANT(c, n) \ re_trace_event(c, n, 'I', NULL, RE_TRACE_ARG_NONE, NULL, NULL) #define RE_TRACE_INSTANT_C(c, n, vname, str) \ re_trace_event(c, n, 'I', NULL, RE_TRACE_ARG_STRING_CONST, \ vname, (void *)(str)) #define RE_TRACE_INSTANT_I(c, n, i) \ re_trace_event(c, n, 'I', NULL, RE_TRACE_ARG_INT, \ n, (void *)(intptr_t)i) #define RE_TRACE_ID_INSTANT(c, n, id) \ re_trace_event(c, n, 'I', id, RE_TRACE_ARG_NONE, NULL, NULL) #define RE_TRACE_ID_INSTANT_C(c, n, vname, str, id) \ re_trace_event(c, n, 'I', id, RE_TRACE_ARG_STRING_CONST, \ vname, (void *)(str)) #define RE_TRACE_ID_INSTANT_I(c, n, i, id) \ re_trace_event(c, n, 'I', id, RE_TRACE_ARG_INT, \ n, (void *)(intptr_t)i) #define RE_TRACE_PROCESS_NAME(n) \ re_trace_event("", "process_name", 'M', NULL, \ RE_TRACE_ARG_STRING_COPY, \ "name", (void *)(n)) #define RE_TRACE_THREAD_NAME(n) \ re_trace_event("", "thread_name", 'M', NULL, \ RE_TRACE_ARG_STRING_COPY, \ "name", (void *)(n)) #else #define RE_TRACE_BEGIN(c, n) #define RE_TRACE_END(c, n) #define RE_TRACE_ID_BEGIN(c, n, id) #define RE_TRACE_ID_END(c, n, id) #define RE_TRACE_INSTANT(c, n) #define RE_TRACE_INSTANT_C(c, n, str) #define RE_TRACE_INSTANT_I(c, n, i) #define RE_TRACE_ID_INSTANT(c, n, id) #define RE_TRACE_ID_INSTANT_C(c, n, str, id) #define RE_TRACE_ID_INSTANT_I(c, n, i, id) #define RE_TRACE_PROCESS_NAME(n) #define RE_TRACE_THREAD_NAME(n) #endif #define RE_TRACE_BEGIN_FUNC() RE_TRACE_BEGIN(__FILE__, __func__) #define RE_TRACE_END_FUNC() RE_TRACE_END(__FILE__, __func__) ================================================ FILE: include/re_trice.h ================================================ /** * @file re_trice.h Interface to Interactive Connectivity Establishment (ICE) * * Copyright (C) 2010 Alfred E. Heggestad */ /** ICE Configuration */ struct trice_conf { bool debug; /**< Enable ICE debugging */ bool trace; /**< Enable tracing of Connectivity checks */ bool ansi; /**< Enable ANSI colors for debug output */ bool optimize_loopback_pairing;/**< Reduce candidate pairs when using loopback addresses */ }; struct trice; struct ice_lcand; struct ice_candpair; struct stun_conf; /** * Handler for receiving packets on candidate * * @param lcand Local candidate * @param proto Network protocol (UDP or TCP) * @param sock Local socket (struct udp_sock or struct tcp_conn) * @param src Source address * @param mb Data packet * @param arg Handler argument * * @return True if handled, False if not */ typedef bool (ice_cand_recv_h)(struct ice_lcand *lcand, int proto, void *sock, const struct sa *src, struct mbuf *mb, void *arg); /** Local candidate */ struct ice_lcand { struct ice_cand_attr attr; /**< Base class (inheritance) */ struct le le; /**< List element */ struct sa base_addr; /**< IP-address of "base" candidate */ struct udp_sock *us; /**< UDP socket */ struct udp_helper *uh; /**< UDP helper to intercept packets */ struct tcp_sock *ts; /**< TCP for simultaneous-open/passive */ char ifname[32]; /**< Network interface, for diagnostics */ int layer; /**< Protocol layer */ ice_cand_recv_h *recvh; /**< Handler for receiving packets */ void *arg; /**< Handler argument */ struct trice *icem; /**< Pointer to parent */ /** Packet statistics */ struct { size_t n_tx; /**< Number of packets sent */ size_t n_rx; /**< Number of packets received */ } stats; }; /** Remote candidate */ struct ice_rcand { struct ice_cand_attr attr; /**< Base class (inheritance) */ struct le le; /**< List element */ }; /** Defines a candidate pair */ struct ice_candpair { struct le le; /**< List element */ struct ice_lcand *lcand; /**< Local candidate */ struct ice_rcand *rcand; /**< Remote candidate */ enum ice_candpair_state state; /**< Candidate pair state */ uint64_t pprio; /**< Pair priority */ bool valid; /**< Valid flag */ bool nominated; /**< Nominated flag */ bool estab; /**< Pair is established */ bool trigged; /**< Pair was triggered */ int err; /**< Saved error code, if failed */ uint16_t scode; /**< Saved STUN code, if failed */ struct tcp_conn *tc; /**< TCP-connection used */ struct ice_tcpconn *conn; /**< the ICE-TCP-connection used */ }; /** * Handler for established candidate pair * * @param pair Which candidate pair was established * @param msg STUN message * @param arg Handler argument */ typedef void (trice_estab_h)(struct ice_candpair *pair, const struct stun_msg *msg, void *arg); /** * Handler for failed candidate pair * * @param err Posix error code * @param scode STUN status code * @param pair Candidate pair * @param arg Handler argument */ typedef void (trice_failed_h)(int err, uint16_t scode, struct ice_candpair *pair, void *arg); int trice_alloc(struct trice **icemp, const struct trice_conf *conf, enum ice_role role, const char *lufrag, const char *lpwd); int trice_set_remote_ufrag(struct trice *icem, const char *rufrag); int trice_set_remote_pwd(struct trice *icem, const char *rpwd); int trice_set_role(struct trice *trice, enum ice_role role); enum ice_role trice_local_role(const struct trice *icem); int trice_debug(struct re_printf *pf, const struct trice *icem); struct trice_conf *trice_conf(struct trice *icem); /* Candidates (common) */ int trice_cand_print(struct re_printf *pf, const struct ice_cand_attr *cand); enum ice_tcptype ice_tcptype_reverse(enum ice_tcptype type); const char *ice_tcptype_name(enum ice_tcptype tcptype); enum ice_cand_type ice_cand_type_base(enum ice_cand_type type); /* Local candidates */ int trice_lcand_add(struct ice_lcand **lcandp, struct trice *icem, unsigned compid, int proto, uint32_t prio, const struct sa *addr, const struct sa *base_addr, enum ice_cand_type type, const struct sa *rel_addr, enum ice_tcptype tcptype, void *sock, int layer); struct list *trice_lcandl(const struct trice *icem); struct ice_lcand *trice_lcand_find(struct trice *icem, enum ice_cand_type type, unsigned compid, int proto, const struct sa *addr); struct ice_lcand *trice_lcand_find2(const struct trice *icem, enum ice_cand_type type, int af); void *trice_lcand_sock(struct trice *icem, const struct ice_lcand *lcand); void trice_lcand_recv_packet(struct ice_lcand *lcand, const struct sa *src, struct mbuf *mb); /* Remote candidate */ struct list *trice_rcandl(const struct trice *icem); int trice_rcand_add(struct ice_rcand **rcandp, struct trice *icem, unsigned compid, const char *foundation, int proto, uint32_t prio, const struct sa *addr, enum ice_cand_type type, enum ice_tcptype tcptype); struct ice_rcand *trice_rcand_find(struct trice *icem, unsigned compid, int proto, const struct sa *addr); /* ICE Candidate pairs */ struct list *trice_checkl(const struct trice *icem); struct list *trice_validl(const struct trice *icem); struct ice_candpair *trice_candpair_find_state(const struct list *lst, enum ice_candpair_state state); int trice_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp); int trice_candpairs_debug(struct re_printf *pf, bool ansi_output, const struct list *list); /* ICE checklist */ void trice_checklist_set_waiting(struct trice *icem); int trice_checklist_start(struct trice *icem, struct stun *stun, uint32_t interval, trice_estab_h *estabh, trice_failed_h *failh, void *arg); void trice_checklist_stop(struct trice *icem); bool trice_checklist_isrunning(const struct trice *icem); bool trice_checklist_iscompleted(const struct trice *icem); /* ICE Conncheck */ int trice_conncheck_send(struct trice *icem, struct ice_candpair *pair, bool use_cand); /* Port range */ int trice_set_port_range(struct trice *trice, uint16_t min_port, uint16_t max_port); ================================================ FILE: include/re_turn.h ================================================ /** * @file re_turn.h Interface to TURN implementation * * Copyright (C) 2010 Creytiv.com */ /** TURN Protocol values */ enum { TURN_DEFAULT_LIFETIME = 600, /**< Default lifetime is 10 minutes */ TURN_MAX_LIFETIME = 3600 /**< Maximum lifetime is 1 hour */ }; typedef void(turnc_h)(int err, uint16_t scode, const char *reason, const struct sa *relay_addr, const struct sa *mapped_addr, const struct stun_msg *msg, void *arg); typedef void(turnc_perm_h)(void *arg); typedef void(turnc_chan_h)(void *arg); struct turnc; int turnc_alloc(struct turnc **turncp, const struct stun_conf *conf, int proto, void *sock, int layer, const struct sa *srv, const char *username, const char *password, uint32_t lifetime, turnc_h *th, void *arg); int turnc_send(struct turnc *turnc, const struct sa *dst, struct mbuf *mb); int turnc_recv(struct turnc *turnc, struct sa *src, struct mbuf *mb); int turnc_add_perm(struct turnc *turnc, const struct sa *peer, turnc_perm_h *ph, void *arg); int turnc_add_chan(struct turnc *turnc, const struct sa *peer, turnc_chan_h *ch, void *arg); ================================================ FILE: include/re_types.h ================================================ /** * @file re_types.h Defines basic types * * Copyright (C) 2010 Creytiv.com */ #include #include #include #ifdef __cplusplus #define restrict #endif #ifdef _MSC_VER #include #include typedef SSIZE_T ssize_t; #endif /* * Basic integral types and boolean from C99 */ #include #include /* Needed for MS compiler */ #ifdef _MSC_VER #ifndef __cplusplus #define inline _inline #endif #endif /* * Misc macros */ /** Defines the NULL pointer */ #ifndef NULL #define NULL ((void *)0) #endif /** Get number of elements in an array */ #define RE_ARRAY_SIZE(a) ((sizeof(a))/(sizeof((a)[0]))) /** Align a value to the boundary of mask */ #define RE_ALIGN_MASK(x, mask) (((x)+(mask))&~(mask)) /** Check alignment of pointer (p) and byte count (c) **/ #define re_is_aligned(p, c) (((uintptr_t)(const void *)(p)) % (c) == 0) /** Get the minimal value */ #undef MIN #define MIN(a,b) (((a)<(b)) ? (a) : (b)) /** Get the maximal value */ #undef MAX #define MAX(a,b) (((a)>(b)) ? (a) : (b)) #ifndef __cplusplus /** Get the minimal value */ #undef min #define min(x,y) MIN(x, y) /** Get the maximal value */ #undef max #define max(x,y) MAX(x, y) #endif /** Defines a soft breakpoint */ #if (defined(__i386__) || defined(__x86_64__)) #define RE_BREAKPOINT __asm__("int $0x03") #elif defined(__has_builtin) #if __has_builtin(__builtin_debugtrap) #define RE_BREAKPOINT __builtin_debugtrap() #endif #endif #ifndef RE_BREAKPOINT #define RE_BREAKPOINT #endif /* Error return/goto debug helpers */ #ifdef TRACE_ERR #define PRINT_TRACE_ERR(err) \ (void)re_fprintf(stderr, "TRACE_ERR: %s:%u: %s():" \ " %m (%d)\n", \ __FILE__, __LINE__, __func__, \ (err), (err)); #else #define PRINT_TRACE_ERR(err) #endif #define IF_ERR_GOTO_OUT(err) \ if ((err)) { \ PRINT_TRACE_ERR((err)) \ goto out; \ } #define IF_ERR_GOTO_OUT1(err) \ if ((err)) { \ PRINT_TRACE_ERR((err)) \ goto out1; \ } #define IF_ERR_GOTO_OUT2(err) \ if ((err)) { \ PRINT_TRACE_ERR((err)) \ goto out2; \ } #define IF_ERR_RETURN(err) \ if ((err)) { \ PRINT_TRACE_ERR((err)) \ return (err); \ } #define IF_RETURN_EINVAL(exp) \ if ((exp)) { \ PRINT_TRACE_ERR(EINVAL) \ return (EINVAL); \ } #define RETURN_ERR(err) \ if ((err)) { \ PRINT_TRACE_ERR((err)) \ } \ return (err); /* Error codes */ #include /* Duplication of error codes. Values are from linux asm-generic/errno.h */ /** No data available */ #ifndef ENODATA #define ENODATA 200 #endif /** Accessing a corrupted shared library */ #ifndef ELIBBAD #define ELIBBAD 204 #endif /** Destination address required */ #ifndef EDESTADDRREQ #define EDESTADDRREQ 205 #endif /** Protocol not supported */ #ifndef EPROTONOSUPPORT #define EPROTONOSUPPORT 206 #endif /** Operation not supported */ #ifndef ENOTSUP #define ENOTSUP 207 #endif /** Address family not supported by protocol */ #ifndef EAFNOSUPPORT #define EAFNOSUPPORT 208 #endif /** Cannot assign requested address */ #ifndef EADDRNOTAVAIL #define EADDRNOTAVAIL 209 #endif /** Software caused connection abort */ #ifndef ECONNABORTED #define ECONNABORTED 210 #endif /** Connection reset by peer */ #ifndef ECONNRESET #define ECONNRESET 211 #endif /** Transport endpoint is not connected */ #ifndef ENOTCONN #define ENOTCONN 212 #endif /** Connection timed out */ #ifndef ETIMEDOUT #define ETIMEDOUT 213 #endif /** Connection refused */ #ifndef ECONNREFUSED #define ECONNREFUSED 214 #endif /** Operation already in progress */ #ifndef EALREADY #define EALREADY 215 #endif /** Operation now in progress */ #ifndef EINPROGRESS #define EINPROGRESS 216 #endif /** Authentication error */ #ifndef EAUTH #define EAUTH 217 #endif /** No STREAM resources */ #ifndef ENOSR #define ENOSR 218 #endif /** Timer expired */ #ifndef ETIME #define ETIME 219 #endif /** Key was rejected by service */ #ifndef EKEYREJECTED #define EKEYREJECTED 129 #endif /* Cannot send after transport endpoint shutdown */ #ifndef ESHUTDOWN #define ESHUTDOWN 108 #endif /* * Give the compiler a hint which branch is "likely" or "unlikely" (inspired * by linux kernel and C++20/C2X) */ #ifdef __GNUC__ #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #else #define likely(x) x #define unlikely(x) x #endif /* * https://clang.llvm.org/docs/AttributeReference.html#nonstring */ #define re_nonstring #ifdef __GNUC__ #if __has_attribute(nonstring) #undef re_nonstring #define re_nonstring __attribute__((nonstring)) #endif #endif #ifdef WIN32 #define re_restrict __restrict #else #define re_restrict restrict #endif /* Socket helpers */ #ifdef WIN32 #define RE_ERRNO_SOCK WSAGetLastError() #define RE_BAD_SOCK INVALID_SOCKET typedef size_t re_sock_t; #else #define RE_ERRNO_SOCK errno #define RE_BAD_SOCK -1 typedef int re_sock_t; #endif /* re_assert helpers */ /** * @def re_assert(expr) * * If expression is false, prints error and calls abort() (not in * RELEASE/NDEBUG builds) * * @param expr expression */ /** * @def re_assert_se(expr) * * If expression is false, prints error and calls abort(), * in RELEASE/NDEBUG builds expression is always executed and keeps side effect * * @param expr expression */ #if defined(RELEASE) || defined(NDEBUG) #define re_assert(expr) (void)0 #define re_assert_se(expr) do{(void)(expr);} while(false) #else #define re_assert(expr) assert(expr) #define re_assert_se(expr) assert(expr) #endif /* RE_VA_ARG SIZE helpers */ #if !defined(DISABLE_RE_ARG) && \ !defined(__STRICT_ANSI__) && /* Needs ## trailing comma fix, with C23 \ we can use __VA_OPT__ */ \ __STDC_VERSION__ >= 201112L /* _Generic C11 support required */ #define HAVE_RE_ARG 1 #if defined(__clang__) #define RE_ARG_SIZE(type) \ _Generic((type), \ bool: sizeof(int), \ char: sizeof(int), \ unsigned char: sizeof(unsigned int), \ short: sizeof(int), \ unsigned short: sizeof(unsigned int), \ int: sizeof(int), \ unsigned int: sizeof(unsigned int), \ long: sizeof(long), \ unsigned long: sizeof(unsigned long), \ long long: sizeof(long long), \ unsigned long long: sizeof(unsigned long long), \ float: sizeof(double), \ double: sizeof(double), \ char const*: sizeof(char const *), \ char*: sizeof(char *), \ void const*: sizeof(void const *), \ void*: sizeof(void *), \ struct pl: sizeof(struct pl), \ default: sizeof(void*) \ ) #else /* GCC bit fields workaround */ #define RE_ARG_SIZE(type) \ _Generic((0)?(type):(type), \ bool: sizeof(int), \ char: sizeof(int), \ unsigned char: sizeof(unsigned int), \ short: sizeof(int), \ unsigned short: sizeof(unsigned int), \ int: sizeof(int), \ unsigned int: sizeof(unsigned int), \ long: sizeof(long), \ unsigned long: sizeof(unsigned long), \ long long: sizeof(long long), \ unsigned long long: sizeof(unsigned long long), \ float: sizeof(double), \ double: sizeof(double), \ char const*: sizeof(char const *), \ char*: sizeof(char *), \ void const*: sizeof(void const *), \ void*: sizeof(void *), \ struct pl: sizeof(struct pl), \ default: sizeof(void*) \ ) #endif #define RE_ARG_0() 0 #define RE_ARG_1(expr) RE_ARG_SIZE(expr), (expr), 0 #define RE_ARG_2(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_1(__VA_ARGS__) #define RE_ARG_3(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_2(__VA_ARGS__) #define RE_ARG_4(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_3(__VA_ARGS__) #define RE_ARG_5(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_4(__VA_ARGS__) #define RE_ARG_6(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_5(__VA_ARGS__) #define RE_ARG_7(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_6(__VA_ARGS__) #define RE_ARG_8(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_7(__VA_ARGS__) #define RE_ARG_9(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_8(__VA_ARGS__) #define RE_ARG_10(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_9(__VA_ARGS__) #define RE_ARG_11(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_10(__VA_ARGS__) #define RE_ARG_12(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_11(__VA_ARGS__) #define RE_ARG_13(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_12(__VA_ARGS__) #define RE_ARG_14(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_13(__VA_ARGS__) #define RE_ARG_15(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_14(__VA_ARGS__) #define RE_ARG_16(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_15(__VA_ARGS__) #define RE_ARG_17(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_16(__VA_ARGS__) #define RE_ARG_18(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_17(__VA_ARGS__) #define RE_ARG_19(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_18(__VA_ARGS__) #define RE_ARG_20(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_19(__VA_ARGS__) #define RE_ARG_21(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_20(__VA_ARGS__) #define RE_ARG_22(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_21(__VA_ARGS__) #define RE_ARG_23(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_22(__VA_ARGS__) #define RE_ARG_24(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_23(__VA_ARGS__) #define RE_ARG_25(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_24(__VA_ARGS__) #define RE_ARG_26(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_25(__VA_ARGS__) #define RE_ARG_27(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_26(__VA_ARGS__) #define RE_ARG_28(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_27(__VA_ARGS__) #define RE_ARG_29(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_28(__VA_ARGS__) #define RE_ARG_30(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_29(__VA_ARGS__) #define RE_ARG_31(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_30(__VA_ARGS__) #define RE_ARG_32(expr, ...) RE_ARG_SIZE(expr), (expr), RE_ARG_31(__VA_ARGS__) #define RE_ARG_VA_NUM_2(X, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, \ X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, \ X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, \ ...) \ N #define RE_ARG_VA_NUM(...) \ RE_ARG_VA_NUM_2(0, ##__VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, \ 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define RE_ARG_N3(N, ...) RE_ARG_##N(__VA_ARGS__) #define RE_ARG_N2(N, ...) RE_ARG_N3(N, __VA_ARGS__) #define RE_VA_ARGS(...) RE_ARG_N2(RE_ARG_VA_NUM(__VA_ARGS__), __VA_ARGS__) #endif /* End RE_VA_ARG SIZE helpers */ #define RE_VA_ARG(ap, val, type, safe) \ if (likely((safe))) { \ size_t sz = va_arg((ap), size_t); \ if (unlikely(!sz)) { \ err = ENODATA; \ goto out; \ } \ if (unlikely(sz != sizeof(type))) { \ err = EOVERFLOW; \ goto out; \ } \ } \ (val) = va_arg((ap), type) ================================================ FILE: include/re_udp.h ================================================ /** * @file re_udp.h Interface to User Datagram Protocol * * Copyright (C) 2010 Creytiv.com */ struct sa; struct udp_sock; typedef int (udp_send_h)(const struct sa *dst, struct mbuf *mb, void *arg); /** * Defines the UDP Receive handler * * @param src Source address * @param mb Datagram buffer * @param arg Handler argument */ typedef void (udp_recv_h)(const struct sa *src, struct mbuf *mb, void *arg); typedef void (udp_error_h)(int err, void *arg); int udp_listen(struct udp_sock **usp, const struct sa *local, udp_recv_h *rh, void *arg); int udp_alloc_sockless(struct udp_sock **usp, udp_send_h *sendh, udp_recv_h *recvh, void *arg); int udp_alloc_fd(struct udp_sock **usp, re_sock_t fd, udp_recv_h *recvh, void *arg); int udp_connect(struct udp_sock *us, const struct sa *peer); int udp_open(struct udp_sock **usp, int af); int udp_send(struct udp_sock *us, const struct sa *dst, struct mbuf *mb); int udp_local_get(const struct udp_sock *us, struct sa *local); int udp_setsockopt(struct udp_sock *us, int level, int optname, const void *optval, uint32_t optlen); int udp_sockbuf_set(struct udp_sock *us, int size); void udp_rxsz_set(struct udp_sock *us, size_t rxsz); void udp_rxbuf_presz_set(struct udp_sock *us, size_t rx_presz); void udp_handler_set(struct udp_sock *us, udp_recv_h *rh, void *arg); void udp_error_handler_set(struct udp_sock *us, udp_error_h *eh); int udp_thread_attach(struct udp_sock *us); void udp_thread_detach(struct udp_sock *us); re_sock_t udp_sock_fd(const struct udp_sock *us); int udp_multicast_join(struct udp_sock *us, const struct sa *group); int udp_multicast_leave(struct udp_sock *us, const struct sa *group); int udp_settos(struct udp_sock *us, uint8_t tos); void udp_flush(const struct udp_sock *us); void udp_recv_packet(struct udp_sock *us, const struct sa *src, struct mbuf *mb); /* Helper API */ typedef bool (udp_helper_send_h)(int *err, struct sa *dst, struct mbuf *mb, void *arg); typedef bool (udp_helper_recv_h)(struct sa *src, struct mbuf *mb, void *arg); struct udp_helper; int udp_register_helper(struct udp_helper **uhp, struct udp_sock *us, int layer, udp_helper_send_h *sh, udp_helper_recv_h *rh, void *arg); int udp_send_helper(struct udp_sock *us, const struct sa *dst, struct mbuf *mb, struct udp_helper *uh); void udp_recv_helper(struct udp_sock *us, const struct sa *src, struct mbuf *mb, struct udp_helper *uh); struct udp_helper *udp_helper_find(const struct udp_sock *us, int layer); ================================================ FILE: include/re_unixsock.h ================================================ int unixsock_listen_fd(re_sock_t *fdp, const struct sa *sock); ================================================ FILE: include/re_uri.h ================================================ /** * @file re_uri.h Interface to URI module * * Copyright (C) 2010 Creytiv.com */ /** Defines a URI - Uniform Resource Identifier */ struct uri { struct pl scheme; /**< URI scheme e.g. "sip:" "sips:" */ struct pl user; /**< Username */ struct pl host; /**< Hostname or IP-address */ int af; /**< Address family of host IP-address */ uint16_t port; /**< Port number */ struct pl path; /**< Optional URI-path */ struct pl params; /**< Optional URI-parameters */ struct pl headers; /**< Optional URI-headers */ }; typedef int (uri_apply_h)(const struct pl *name, const struct pl *val, void *arg); struct re_printf; int uri_encode(struct re_printf *pf, const struct uri *uri); int uri_decode(struct uri *uri, const struct pl *pl); int uri_decode_hostport(const struct pl *hostport, struct pl *host, struct pl *port); int uri_param_get(const struct pl *pl, const struct pl *pname, struct pl *pvalue); int uri_params_apply(const struct pl *pl, uri_apply_h *ah, void *arg); int uri_header_get(const struct pl *pl, const struct pl *hname, struct pl *hvalue); int uri_headers_apply(const struct pl *pl, uri_apply_h *ah, void *arg); /* Special URI escaping/unescaping */ int uri_user_escape(struct re_printf *pf, const struct pl *pl); int uri_user_unescape(struct re_printf *pf, const struct pl *pl); int uri_param_escape(struct re_printf *pf, const struct pl *pl); int uri_param_unescape(struct re_printf *pf, const struct pl *pl); int uri_header_escape(struct re_printf *pf, const struct pl *pl); int uri_header_unescape(struct re_printf *pf, const struct pl *pl); ================================================ FILE: include/re_websock.h ================================================ /** * @file re_websock.h The WebSocket Protocol * * Copyright (C) 2010 Creytiv.com */ enum { WEBSOCK_VERSION = 13, }; enum websock_opcode { /* Data frames */ WEBSOCK_CONT = 0x0, WEBSOCK_TEXT = 0x1, WEBSOCK_BIN = 0x2, /* Control frames */ WEBSOCK_CLOSE = 0x8, WEBSOCK_PING = 0x9, WEBSOCK_PONG = 0xa, }; enum websock_scode { WEBSOCK_NORMAL_CLOSURE = 1000, WEBSOCK_GOING_AWAY = 1001, WEBSOCK_PROTOCOL_ERROR = 1002, WEBSOCK_UNSUPPORTED_DATA = 1003, WEBSOCK_INVALID_PAYLOAD = 1007, WEBSOCK_POLICY_VIOLATION = 1008, WEBSOCK_MESSAGE_TOO_BIG = 1009, WEBSOCK_EXTENSION_ERROR = 1010, WEBSOCK_INTERNAL_ERROR = 1011, }; struct websock_hdr { unsigned fin:1; unsigned rsv1:1; unsigned rsv2:1; unsigned rsv3:1; unsigned opcode:4; unsigned mask:1; uint64_t len; uint8_t mkey[4]; }; struct websock; struct websock_conn; typedef void (websock_estab_h)(void *arg); typedef void (websock_recv_h)(const struct websock_hdr *hdr, struct mbuf *mb, void *arg); typedef void (websock_close_h)(int err, void *arg); int websock_connect(struct websock_conn **connp, struct websock *sock, struct http_cli *cli, const char *uri, unsigned kaint, websock_estab_h *estabh, websock_recv_h *recvh, websock_close_h *closeh, void *arg, const char *fmt, ...); int websock_connect_proto(struct websock_conn **connp, const char *proto, struct websock *sock, struct http_cli *cli, const char *uri, unsigned kaint, websock_estab_h *estabh, websock_recv_h *recvh, websock_close_h *closeh, void *arg, const char *fmt, ...); int websock_accept(struct websock_conn **connp, struct websock *sock, struct http_conn *htconn, const struct http_msg *msg, unsigned kaint, websock_recv_h *recvh, websock_close_h *closeh, void *arg); int websock_accept_proto(struct websock_conn **connp, const char *proto, struct websock *sock, struct http_conn *htconn, const struct http_msg *msg, unsigned kaint, websock_recv_h *recvh, websock_close_h *closeh, void *arg); int websock_send(struct websock_conn *conn, enum websock_opcode opcode, const char *fmt, ...); int websock_close(struct websock_conn *conn, enum websock_scode scode, const char *fmt, ...); struct tcp_conn *websock_tcp(const struct websock_conn *conn); typedef void (websock_shutdown_h)(void *arg); int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth, void *arg); void websock_shutdown(struct websock *sock); ================================================ FILE: include/rem.h ================================================ /** * @file rem.h Wrapper for librem headers * * Copyright (C) 2010 Creytiv.com */ #ifndef REM_H__ #define REM_H__ #ifdef __cplusplus extern "C" { #endif #include "rem_audio.h" #include "rem_video.h" #include "rem_dsp.h" #include "rem_flv.h" #ifdef __cplusplus } #endif #endif ================================================ FILE: include/rem_aac.h ================================================ /** * @file rem_aac.h Advanced Audio Coding * * Copyright (C) 2010 Creytiv.com */ /** Defines the AAC header */ struct aac_header { unsigned sample_rate; /**< Audio sample rate in [Hz] */ unsigned channels; /**< Number of audio channels */ unsigned frame_size; /**< Frame size, 960 or 1024 bits */ }; int aac_header_decode(struct aac_header *hdr, const uint8_t *p, size_t len); ================================================ FILE: include/rem_au.h ================================================ /** * @file rem_au.h Basic audio types * * Copyright (C) 2010 Creytiv.com */ /** Audio formats */ enum aufmt { AUFMT_S16LE, /**< Signed 16-bit PCM */ AUFMT_S32LE, /**< Signed 32-bit PCM */ AUFMT_PCMA, /**< G.711 A-law */ AUFMT_PCMU, /**< G.711 U-law */ AUFMT_FLOAT, /**< Float 32 bit (CPU endian) */ AUFMT_S24_3LE,/**< Signed 24bit Little Endian in 3bytes format */ AUFMT_RAW, /**< RAW PCM */ }; size_t aufmt_sample_size(enum aufmt fmt); const char *aufmt_name(enum aufmt fmt); uint32_t au_calc_nsamp(uint32_t srate, uint8_t channels, uint16_t ptime); ================================================ FILE: include/rem_aubuf.h ================================================ /** * @file rem_aubuf.h Audio Buffer * * Copyright (C) 2010 Creytiv.com */ struct aubuf; enum aubuf_mode { AUBUF_FIXED, AUBUF_ADAPTIVE }; int aubuf_alloc(struct aubuf **abp, size_t min_sz, size_t max_sz); void aubuf_set_id(struct aubuf *ab, struct pl *id); void aubuf_set_live(struct aubuf *ab, bool live); void aubuf_set_mode(struct aubuf *ab, enum aubuf_mode mode); void aubuf_set_silence(struct aubuf *ab, double silence); int aubuf_resize(struct aubuf *ab, size_t min_sz, size_t max_sz); int aubuf_write_auframe(struct aubuf *ab, const struct auframe *af); int aubuf_append_auframe(struct aubuf *ab, struct mbuf *mb, const struct auframe *af); void aubuf_read_auframe(struct aubuf *ab, struct auframe *af); void aubuf_sort_auframe(struct aubuf *ab); int aubuf_get(struct aubuf *ab, uint32_t ptime, uint8_t *p, size_t sz); void aubuf_flush(struct aubuf *ab); int aubuf_debug(struct re_printf *pf, const struct aubuf *ab); size_t aubuf_cur_size(const struct aubuf *ab); size_t aubuf_maxsz(const struct aubuf *ab); bool aubuf_started(const struct aubuf *ab); void aubuf_drop_auframe(struct aubuf *ab, const struct auframe *af); static inline int aubuf_append(struct aubuf *ab, struct mbuf *mb) { return aubuf_append_auframe(ab, mb, NULL); } static inline int aubuf_get_samp(struct aubuf *ab, uint32_t ptime, int16_t *sampv, size_t sampc) { return aubuf_get(ab, ptime, (uint8_t *)sampv, sampc * 2); } #ifndef __cplusplus static inline int aubuf_write(struct aubuf *ab, const uint8_t *p, size_t sz) { struct auframe af = { .fmt = AUFMT_RAW, .srate = 0, .sampv = (uint8_t *)p, .sampc = sz, .timestamp = 0, .level = AULEVEL_UNDEF }; return aubuf_write_auframe(ab, &af); } static inline int aubuf_write_samp(struct aubuf *ab, const int16_t *sampv, size_t sampc) { struct auframe af = { .fmt = AUFMT_S16LE, .srate = 0, .sampv = (uint8_t *)sampv, .sampc = sampc, .timestamp = 0, .level = AULEVEL_UNDEF }; return aubuf_write_auframe(ab, &af); } static inline void aubuf_read(struct aubuf *ab, uint8_t *p, size_t sz) { struct auframe af = { .fmt = AUFMT_RAW, .srate = 0, .sampv = p, .sampc = sz, .timestamp = 0, .level = AULEVEL_UNDEF }; aubuf_read_auframe(ab, &af); } static inline void aubuf_read_samp(struct aubuf *ab, int16_t *sampv, size_t sampc) { struct auframe af = { .fmt = AUFMT_S16LE, .srate = 0, .sampv = (uint8_t *)sampv, .sampc = sampc, .timestamp = 0, .level = AULEVEL_UNDEF }; aubuf_read_auframe(ab, &af); } #endif ================================================ FILE: include/rem_auconv.h ================================================ /** * @file rem_auconv.h Audio sample format conversion * * Copyright (C) 2010 Creytiv.com */ void auconv_from_s16(enum aufmt dst_fmt, void *dst_sampv, const int16_t *src_sampv, size_t sampc); void auconv_to_s16(int16_t *dst_sampv, enum aufmt src_fmt, void *src_sampv, size_t sampc); void auconv_to_float(float *dst_sampv, enum aufmt src_fmt, const void *src_sampv, size_t sampc); ================================================ FILE: include/rem_audio.h ================================================ /** * @file rem_audio.h Wrapper for all Audio header files * * Copyright (C) 2010 Creytiv.com */ #include "rem_au.h" #include "rem_aulevel.h" #include "rem_auframe.h" #include "rem_aubuf.h" #include "rem_auconv.h" #include "rem_aufile.h" #include "rem_autone.h" #include "rem_aumix.h" #include "rem_dtmf.h" #include "rem_fir.h" #include "rem_goertzel.h" #include "rem_auresamp.h" #include "rem_g711.h" #include "rem_aac.h" ================================================ FILE: include/rem_aufile.h ================================================ /** * @file rem_aufile.h Audio File interface * * Copyright (C) 2010 Creytiv.com */ /** Audio file mode */ enum aufile_mode { AUFILE_READ, AUFILE_WRITE, }; /** Audio file parameters */ struct aufile_prm { uint32_t srate; uint8_t channels; enum aufmt fmt; }; struct aufile; int aufile_open(struct aufile **afp, struct aufile_prm *prm, const char *filename, enum aufile_mode mode); int aufile_read(struct aufile *af, uint8_t *p, size_t *sz); int aufile_write(struct aufile *af, const uint8_t *p, size_t sz); size_t aufile_get_size(struct aufile *af); size_t aufile_get_length(struct aufile *af, const struct aufile_prm *prm); int aufile_set_position(struct aufile *af, const struct aufile_prm *prm, size_t pos_ms); ================================================ FILE: include/rem_auframe.h ================================================ /* * Audio frame */ #define AUDIO_TIMEBASE 1000000U /** * Defines a frame of audio samples */ struct auframe { enum aufmt fmt; /**< Sample format (enum aufmt) */ uint32_t srate; /**< Samplerate */ void *sampv; /**< Audio samples (must be mem_ref'd) */ size_t sampc; /**< Total number of audio samples */ uint64_t timestamp; /**< Timestamp in AUDIO_TIMEBASE units */ double level; /**< Audio level in dBov */ uint16_t id; /**< Frame/Channel identifier */ uint8_t ch; /**< Channels */ uint8_t padding[5]; }; void auframe_init(struct auframe *af, enum aufmt fmt, void *sampv, size_t sampc, uint32_t srate, uint8_t ch); /** * Update an audio frame * * @param af Audio frame * @param sampv Audio samples * @param sampc Total number of audio samples * @param timestamp Timestamp in AUDIO_TIMEBASE units */ static inline void auframe_update(struct auframe *af, void *sampv, size_t sampc, uint64_t timestamp) { if (!af) return; af->sampv = sampv; af->sampc = sampc; af->timestamp = timestamp; af->level = AULEVEL_UNDEF; } size_t auframe_size(const struct auframe *af); void auframe_mute(struct auframe *af); double auframe_level(struct auframe *af); uint64_t auframe_bytes_to_timestamp(const struct auframe *af, size_t n); uint64_t auframe_bytes_to_ms(const struct auframe *af, size_t n); size_t auframe_ms_to_bytes(const struct auframe *af, uint16_t ms); ================================================ FILE: include/rem_aulevel.h ================================================ /* * Audio-level */ #define AULEVEL_UNDEF (-128.0) #define AULEVEL_MIN (-96.0) #define AULEVEL_MAX (0.0) double aulevel_calc_dbov(int fmt, const void *sampv, size_t sampc); ================================================ FILE: include/rem_aumix.h ================================================ /** * @file rem_aumix.h Audio Mixer * * Copyright (C) 2010 Creytiv.com */ struct aumix; struct aumix_source; /** * Audio mixer frame handler * * @param sampv Buffer with audio samples * @param sampc Number of samples * @param arg Handler argument */ typedef void (aumix_frame_h)(const int16_t *sampv, size_t sampc, void *arg); typedef void (aumix_record_h)(struct auframe *af); typedef void (aumix_read_h)(struct auframe *af, void *arg); int aumix_alloc(struct aumix **mixp, uint32_t srate, uint8_t ch, uint32_t ptime); void aumix_latency(struct aumix *mix, uint16_t min, uint16_t max); void aumix_recordh(struct aumix *mix, aumix_record_h *recordh); void aumix_record_sumh(struct aumix *mix, aumix_record_h *recordh); int aumix_playfile(struct aumix *mix, const char *filepath); uint32_t aumix_source_count(const struct aumix *mix); int aumix_source_alloc(struct aumix_source **srcp, struct aumix *mix, aumix_frame_h *fh, void *arg); void aumix_source_set_id(struct aumix_source *src, struct pl *id); void aumix_source_enable(struct aumix_source *src, bool enable); void aumix_source_mute(struct aumix_source *src, bool mute); int aumix_source_put(struct aumix_source *src, const int16_t *sampv, size_t sampc); int aumix_source_put_auframe(struct aumix_source *src, struct auframe *af); void aumix_source_readh(struct aumix_source *src, aumix_read_h *readh); void aumix_source_flush(struct aumix_source *src); int aumix_debug(struct re_printf *pf, const struct aumix *mix); ================================================ FILE: include/rem_auresamp.h ================================================ /** * @file rem_auresamp.h Audio Resampling * * Copyright (C) 2010 Creytiv.com */ /** * Defines the audio resampler handler * * @param outv Output samples * @param inv Input samples * @param inc Number of input samples * @param ratio Resample ratio */ typedef void (auresamp_h)(int16_t *outv, const int16_t *inv, size_t inc, unsigned ratio); /** Defines the resampler state */ struct auresamp { struct fir fir; /**< FIR filter state */ auresamp_h *resample; /**< Resample handler */ const int16_t *tapv; /**< FIR filter taps */ size_t tapc; /**< FIR filter tap count */ uint32_t orate, irate; /**< Input/output sample rate */ unsigned och, ich; /**< Input/output channel count */ unsigned ratio; /**< Resample ratio */ bool up; /**< Up/down sample flag */ }; void auresamp_init(struct auresamp *rs); int auresamp_setup(struct auresamp *rs, uint32_t irate, unsigned ich, uint32_t orate, unsigned och); int auresamp(struct auresamp *rs, int16_t *outv, size_t *outc, const int16_t *inv, size_t inc); ================================================ FILE: include/rem_autone.h ================================================ /** * @file rem_autone.h Audio Tones * * Copyright (C) 2010 Creytiv.com */ int autone_sine(struct mbuf *mb, uint32_t srate, uint32_t f1, int l1, uint32_t f2, int l2); int autone_dtmf(struct mbuf *mb, uint32_t srate, int digit); ================================================ FILE: include/rem_avc.h ================================================ /** * @file rem_avc.h Advanced Video Coding * * Copyright (C) 2010 Creytiv.com */ struct avc_config { uint8_t profile_ind; uint8_t profile_compat; uint8_t level_ind; uint16_t sps_len; uint8_t sps[256]; uint16_t pps_len; uint8_t pps[64]; }; int avc_config_encode(struct mbuf *mb, uint8_t profile_ind, uint8_t profile_compat, uint8_t level_ind, uint16_t sps_length, const uint8_t *sps, uint16_t pps_length, const uint8_t *pps); int avc_config_decode(struct avc_config *conf, struct mbuf *mb); ================================================ FILE: include/rem_dsp.h ================================================ /** * @file rem_dsp.h DSP routines * * Copyright (C) 2010 Creytiv.com */ #include #if defined (HAVE_ARMV6) || defined (HAVE_NEON) static inline uint8_t saturate_u8(int32_t a) { uint8_t r; __asm__ __volatile__ ("usat %0, #8, %1" : "=r"(r) : "r"(a)); return r; } static inline int16_t saturate_s16(int32_t a) { __asm__ __volatile__ ("ssat %0, #16, %1 \n\t" : "+r"(a) : "r"(a) ); return a; } static inline int16_t saturate_add16(int32_t a, int32_t b) { __asm__ __volatile__ ("add %0, %1, %2 \n\t" "ssat %0, #16, %0 \n\t" :"+r"(a) :"r"(a), "r"(b) ); return a; } static inline int16_t saturate_sub16(int32_t a, int32_t b) { __asm__ __volatile__ ("sub %0, %1, %2 \n\t" "ssat %0, #16, %0 \n\t" :"+r"(a) :"r"(a), "r"(b) ); return a; } #else static inline uint8_t saturate_u8(int32_t a) { return (a > (int32_t)UINT8_MAX) ? UINT8_MAX : ((a < 0) ? 0 : a); } static inline int16_t saturate_s16(int32_t a) { if (a > INT16_MAX) return INT16_MAX; else if (a < INT16_MIN) return INT16_MIN; else return a; } static inline int16_t saturate_add16(int32_t a, int32_t b) { return saturate_s16(a + b); } static inline int16_t saturate_sub16(int32_t a, int32_t b) { return saturate_s16(a - b); } #endif #ifdef HAVE_NEON static inline int ABS32(int a) { int r; __asm__ __volatile__ ("vmov.s32 d0[0], %1 \t\n" "vabs.s32 d0, d0 \t\n" "vmov.s32 %0, d0[0] \t\n" : "=r"(r) : "r"(a) ); return a; } #else static inline int ABS32(int a) { return a > 0 ? a : -a; } #endif ================================================ FILE: include/rem_dtmf.h ================================================ /** * @file rem_dtmf.h DTMF Decoder * * Copyright (C) 2010 Creytiv.com */ struct dtmf_dec; /** * Defines the DTMF decode handler * * @param digit Decoded DTMF digit * @param arg Handler argument */ typedef void (dtmf_dec_h)(char digit, void *arg); int dtmf_dec_alloc(struct dtmf_dec **decp, unsigned srate, unsigned ch, dtmf_dec_h *dech, void *arg); void dtmf_dec_reset(struct dtmf_dec *dec, unsigned srate, unsigned ch); void dtmf_dec_probe(struct dtmf_dec *dec, const int16_t *sampv, size_t sampc); ================================================ FILE: include/rem_fir.h ================================================ /** * @file rem_fir.h Finite Impulse Response (FIR) functions * * Copyright (C) 2010 Creytiv.com */ /** Defines the fir filter state */ struct fir { int16_t history[256]; /**< Previous samples */ unsigned index; /**< Sample index */ }; void fir_reset(struct fir *fir); void fir_filter(struct fir *fir, int16_t *outv, const int16_t *inv, size_t inc, unsigned ch, const int16_t *tapv, size_t tapc); ================================================ FILE: include/rem_flv.h ================================================ /** * @file rem_flv.h Flash Video File Format * * Copyright (C) 2010 Creytiv.com */ /* * Audio */ enum flv_aucodec { FLV_AUCODEC_PCM = 0, FLV_AUCODEC_MP3 = 2, FLV_AUCODEC_PCM_LE = 3, FLV_AUCODEC_ALAW = 7, FLV_AUCODEC_ULAW = 8, FLV_AUCODEC_AAC = 10, }; enum flv_srate { FLV_SRATE_5500HZ = 0, FLV_SRATE_11000HZ = 1, FLV_SRATE_22000HZ = 2, FLV_SRATE_44000HZ = 3, }; enum flv_aac_packet_type { FLV_AAC_SEQUENCE_HEADER = 0, FLV_AAC_RAW = 1, }; /* * Video */ enum flv_vidframe { FLV_VIDFRAME_KEY = 1, FLV_VIDFRAME_INTER = 2, FLV_VIDFRAME_DISP_INTER = 3, FLV_VIDFRAME_GENERATED_KEY = 4, FLV_VIDFRAME_VIDEO_INFO_CMD = 5, }; enum flv_vidcodec { FLV_VIDCODEC_H263 = 2, FLV_VIDCODEC_H264 = 7, FLV_VIDCODEC_MPEG4 = 9, }; enum flv_avc_packet_type { FLV_AVC_SEQUENCE = 0, FLV_AVC_NALU = 1, FLV_AVC_EOS = 2, }; ================================================ FILE: include/rem_g711.h ================================================ /** * @file rem_g711.h Interface to G.711 codec * * Copyright (C) 2010 Creytiv.com */ extern const uint8_t g711_l2u[4096]; extern const uint8_t g711_l2A[2048]; extern const int16_t g711_u2l[256]; extern const int16_t g711_A2l[256]; /** * Encode one 16-bit PCM sample to U-law format * * @param l Signed PCM sample * * @return U-law byte */ static inline uint8_t g711_pcm2ulaw(int16_t lx) { int32_t l = lx; const uint8_t mask = (l < 0) ? 0x7f : 0xff; if (l < 0) l = -l; if (l < 4) return 0xff & mask; l -= 4; l >>= 3; return g711_l2u[l] & mask; } /** * Encode one 16-bit PCM sample to A-law format * * @param l Signed PCM sample * * @return A-law byte */ static inline uint8_t g711_pcm2alaw(int16_t l) { const uint8_t mask = (l < 0) ? 0x7f : 0xff; if (l < 0) l = ~l; l >>= 4; return g711_l2A[l] & mask; } /** * Decode one U-law sample to 16-bit PCM sample * * @param u U-law byte * * @return Signed PCM sample */ static inline int16_t g711_ulaw2pcm(uint8_t u) { return g711_u2l[u]; } /** * Decode one A-law sample to 16-bit PCM sample * * @param A A-law byte * * @return Signed PCM sample */ static inline int16_t g711_alaw2pcm(uint8_t a) { return g711_A2l[a]; } ================================================ FILE: include/rem_goertzel.h ================================================ /** * @file rem_goertzel.h Goertzel algorithm * * Copyright (C) 2010 Creytiv.com */ /** Defines the goertzel algorithm state */ struct goertzel { double q1; /**< current state */ double q2; /**< previous state */ double coef; /**< coefficient */ }; void goertzel_init(struct goertzel *g, double freq, unsigned srate); void goertzel_reset(struct goertzel *g); double goertzel_result(struct goertzel *g); /** * Process sample * * @param g Goertzel state * @param samp Sample value */ static inline void goertzel_update(struct goertzel *g, int16_t samp) { double q0 = g->coef*g->q1 - g->q2 + (double)samp; g->q2 = g->q1; g->q1 = q0; } ================================================ FILE: include/rem_vid.h ================================================ /** * @file rem_vid.h Basic video types * * Copyright (C) 2010 Creytiv.com */ /** Pixel format */ enum vidfmt { VID_FMT_YUV420P = 0, /* planar YUV 4:2:0 12bpp */ VID_FMT_YUYV422, /* packed YUV 4:2:2 16bpp */ VID_FMT_UYVY422, /* packed YUV 4:2:2 16bpp */ VID_FMT_RGB32, /* packed RGBA 8:8:8:8 32bpp (native endian) */ VID_FMT_ARGB, /* packed RGBA 8:8:8:8 32bpp (big endian) */ VID_FMT_RGB565, /* packed RGB 5:6:5 16bpp (native endian) */ VID_FMT_NV12, /* planar YUV 4:2:0 12bpp UV interleaved */ VID_FMT_NV21, /* planar YUV 4:2:0 12bpp VU interleaved */ VID_FMT_YUV444P, /* planar YUV 4:4:4 24bpp */ VID_FMT_YUV422P, /* planar YUV 4:2:2 16bpp */ /* marker */ VID_FMT_N }; /** Video pixel format component description */ struct vidfmt_compdesc { unsigned plane_index:2; unsigned step:3; }; /** Video pixel format description */ struct vidfmt_desc { const char *name; uint8_t planes; uint8_t compn; struct vidfmt_compdesc compv[4]; }; /** Video orientation */ enum vidorient { VIDORIENT_PORTRAIT, VIDORIENT_PORTRAIT_UPSIDEDOWN, VIDORIENT_LANDSCAPE_LEFT, VIDORIENT_LANDSCAPE_RIGHT, }; /** Video size */ struct vidsz { unsigned w; /**< Width */ unsigned h; /**< Height */ }; /** Video frame */ struct vidframe { uint8_t *data[4]; /**< Video planes */ uint16_t linesize[4]; /**< Array of line-sizes */ struct vidsz size; /**< Frame resolution */ enum vidfmt fmt; /**< Video pixel format */ unsigned xoffs; /**< x offset */ unsigned yoffs; /**< y offset */ }; /** Video point */ struct vidpt { unsigned x; /**< X position */ unsigned y; /**< Y position */ }; /** Video rectangle */ struct vidrect { unsigned x; /**< X position */ unsigned y; /**< Y position */ unsigned w; /**< Width */ unsigned h; /**< Height */ }; static inline bool vidsz_cmp(const struct vidsz *a, const struct vidsz *b) { if (!a || !b) return false; if (a == b) return true; return a->w == b->w && a->h == b->h; } static inline bool vidrect_cmp(const struct vidrect *a, const struct vidrect *b) { if (!a || !b) return false; if (a == b) return true; return a->x == b->x && a->y == b->y && a->w == b->w && a->h == b->h; } static inline int rgb2y(uint8_t r, uint8_t g, uint8_t b) { return ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16; } static inline int rgb2u(uint8_t r, uint8_t g, uint8_t b) { return ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; } static inline int rgb2v(uint8_t r, uint8_t g, uint8_t b) { return ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; } size_t vidframe_size(enum vidfmt fmt, const struct vidsz *sz); void vidframe_init(struct vidframe *vf, enum vidfmt fmt, const struct vidsz *sz, void *data[4], unsigned linesize[4]); void vidframe_init_buf(struct vidframe *vf, enum vidfmt fmt, const struct vidsz *sz, uint8_t *buf); int vidframe_alloc(struct vidframe **vfp, enum vidfmt fmt, const struct vidsz *sz); void vidframe_fill(struct vidframe *vf, uint32_t r, uint32_t g, uint32_t b); void vidframe_copy(struct vidframe *dst, const struct vidframe *src); const char *vidfmt_name(enum vidfmt fmt); static inline bool vidframe_isvalid(const struct vidframe *f) { return f ? f->data[0] != NULL : false; } extern const struct vidfmt_desc vidfmt_descv[VID_FMT_N]; /* draw */ void vidframe_draw_point(struct vidframe *f, unsigned x, unsigned y, uint8_t r, uint8_t g, uint8_t b); void vidframe_draw_hline(struct vidframe *f, unsigned x0, unsigned y0, unsigned w, uint8_t r, uint8_t g, uint8_t b); void vidframe_draw_vline(struct vidframe *f, unsigned x0, unsigned y0, unsigned h, uint8_t r, uint8_t g, uint8_t b); void vidframe_draw_rect(struct vidframe *f, unsigned x0, unsigned y0, unsigned w, unsigned h, uint8_t r, uint8_t g, uint8_t b); ================================================ FILE: include/rem_vidconv.h ================================================ /** * @file rem_vidconv.h Video colorspace conversion * * Copyright (C) 2010 Creytiv.com */ void vidconv(struct vidframe *dst, const struct vidframe *src, struct vidrect *r); void vidconv_aspect(struct vidframe *dst, const struct vidframe *src, struct vidrect *r); void vidconv_center(struct vidframe *dst, const struct vidframe *src, struct vidrect *r); ================================================ FILE: include/rem_video.h ================================================ /** * @file rem_video.h Wrapper for all Video header files * * Copyright (C) 2010 Creytiv.com */ #include "rem_vid.h" #include "rem_vidmix.h" #include "rem_vidconv.h" #include "rem_avc.h" ================================================ FILE: include/rem_vidmix.h ================================================ /** * @file rem_vidmix.h Video Mixer * * Copyright (C) 2010 Creytiv.com */ struct vidmix; struct vidmix_source; /** * Video mixer frame handler * * @param ts Timestamp * @param frame Video frame * @param arg Handler argument */ typedef void (vidmix_frame_h)(uint64_t ts, const struct vidframe *frame, void *arg); int vidmix_alloc(struct vidmix **mixp); void vidmix_set_fmt(struct vidmix *mix, enum vidfmt fmt); int vidmix_source_alloc(struct vidmix_source **srcp, struct vidmix *mix, const struct vidsz *sz, unsigned fps, bool content, vidmix_frame_h *fh, void *arg); bool vidmix_source_isenabled(const struct vidmix_source *src); bool vidmix_source_isrunning(const struct vidmix_source *src); uint32_t vidmix_source_get_pidx(const struct vidmix_source *src); void *vidmix_source_get_focus(const struct vidmix_source *src); void vidmix_source_enable(struct vidmix_source *src, bool enable); int vidmix_source_start(struct vidmix_source *src); void vidmix_source_stop(struct vidmix_source *src); int vidmix_source_set_size(struct vidmix_source *src, const struct vidsz *sz); void vidmix_source_set_rate(struct vidmix_source *src, unsigned fps); void vidmix_source_set_content_hide(struct vidmix_source *src, bool hide); void vidmix_source_toggle_selfview(struct vidmix_source *src); void vidmix_source_set_focus(struct vidmix_source *src, const struct vidmix_source *focus_src, bool focus_full); void vidmix_source_set_focus_idx(struct vidmix_source *src, uint32_t pidx); void vidmix_source_put(struct vidmix_source *src, const struct vidframe *frame); ================================================ FILE: mk/Doxyfile ================================================ # Doxyfile 1.4.7 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- PROJECT_NAME = libre PROJECT_NUMBER = 4.8.0 OUTPUT_DIRECTORY = ../re-dox CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English #USE_WINDOWS_ENCODING = NO BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES MULTILINE_CPP_IS_BRIEF = NO #DETAILS_AT_TOP = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO #BUILTIN_STL_SUPPORT = NO DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = NO EXTRACT_PRIVATE = NO EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES #SHOW_DIRECTORIES = NO FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = include src docs rem FILE_PATTERNS = *.c \ *.h \ *.dox RECURSIVE = YES EXCLUDE = test.c \ src/md5/md5.h src/md5/md5.c EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = */.svn/* EXAMPLE_PATH = . EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES #REFERENCES_LINK_SOURCE = YES #USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES #COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = #HTML_ALIGN_MEMBERS = YES GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO #PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = include INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES #PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- #CLASS_DIAGRAMS = YES HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES #CALL_GRAPH = YES todo: disabled to run faster #CALLER_GRAPH = YES GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = DOT_GRAPH_MAX_NODES = 256 #MAX_DOT_GRAPH_WIDTH = 1024 #MAX_DOT_GRAPH_HEIGHT = 1024 #MAX_DOT_GRAPH_DEPTH = 1000 #DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = NO ================================================ FILE: packaging/CMakeLists.txt ================================================ set(CPACK_PACKAGE_NAME libre) set(CPACK_PACKAGE_CONTACT "sreimers") set(CPACK_PACKAGE_VENDOR baresip) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library for Real-Time Communications") set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_VERBATIM_VARIABLES YES) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") # Debian set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_DEBIAN_LIBRARIES_PACKAGE_NAME "libre") set(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_NAME "libre-dev") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl3, zlib1g, libc6") include(CPack) ================================================ FILE: packaging/libre.pc.in ================================================ prefix="@CMAKE_INSTALL_PREFIX@" exec_prefix=${prefix} libdir=${prefix}/lib includedir=${prefix}/include/re Name: libre Description: @CMAKE_PROJECT_DESCRIPTION@ Version: @PROJECT_VERSION@ URL: @CMAKE_PROJECT_HOMEPAGE_URL@ Libs: -L${libdir} -l@PC_LIBNAME@ Libs.private: @PC_LINKLIBS@ Requires.private: @PC_REQUIRES@ Cflags: -I${includedir} ================================================ FILE: rem/aac/aac.c ================================================ /** * @file aac.c Advanced Audio Coding * * Copyright (C) 2018 Creytiv.com */ #include #include /* * Ref https://wiki.multimedia.cx/index.php/MPEG-4_Audio */ enum { OBJECT_TYPE_AAC_LC = 2 }; static const unsigned aac_sample_rates[13] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; static const unsigned aac_channels[8] = { 0, 1, 2, 3, 4, 5, 6, 8 }; /** * Decode an AAC header * * @param hdr Decoded AAC header * @param p Packet to decode * @param len Packet length * * @return 0 if success, otherwise errorcode */ int aac_header_decode(struct aac_header *hdr, const uint8_t *p, size_t len) { uint8_t object_type; uint8_t srate_index; uint8_t channel_index; if (!hdr || !p || len<2) return EINVAL; object_type = (p[0] >> 3) & 0x1f; if (object_type != OBJECT_TYPE_AAC_LC) return EBADMSG; srate_index = (p[0] & 0x07) << 1; srate_index |= (p[1] & 0x80) >> 7; channel_index = (p[1] >> 3) & 0xf; if (srate_index >= RE_ARRAY_SIZE(aac_sample_rates)) return ENOTSUP; if (channel_index >= RE_ARRAY_SIZE(aac_channels)) return ENOTSUP; hdr->sample_rate = aac_sample_rates[srate_index]; hdr->channels = aac_channels[channel_index]; hdr->frame_size = ((p[1] >> 2) & 1) ? 960 : 1024; return 0; } ================================================ FILE: rem/au/fmt.c ================================================ /** * @file au/fmt.c Audio formats * * Copyright (C) 2010 Creytiv.com */ #include #include /* Number of bytes per sample */ size_t aufmt_sample_size(enum aufmt fmt) { switch (fmt) { case AUFMT_S16LE: return sizeof(int16_t); case AUFMT_S32LE: return sizeof(int32_t); case AUFMT_RAW: return 1; case AUFMT_PCMA: return 1; case AUFMT_PCMU: return 1; case AUFMT_FLOAT: return sizeof(float); case AUFMT_S24_3LE: return 3; default: return 0; } } const char *aufmt_name(enum aufmt fmt) { switch (fmt) { case AUFMT_S16LE: return "S16LE"; case AUFMT_S32LE: return "S32LE"; case AUFMT_PCMA: return "PCMA"; case AUFMT_PCMU: return "PCMU"; case AUFMT_FLOAT: return "FLOAT"; case AUFMT_S24_3LE: return "S24_3LE"; case AUFMT_RAW: return "RAW"; default: return "???"; } } ================================================ FILE: rem/au/util.c ================================================ /** * @file util.c Audio utility functions * * Copyright (C) 2022 Commend.com - c.spielberger@commend.com */ #include #include /** * Calculate number of samples from sample rate, channels and packet time * * @param srate Sample rate in [Hz] * @param channels Number of channels * @param ptime Packet time in [ms] * * @return Number of samples */ uint32_t au_calc_nsamp(uint32_t srate, uint8_t channels, uint16_t ptime) { return srate * channels * ptime / 1000; } ================================================ FILE: rem/aubuf/ajb.c ================================================ /** * @file ajb.c Adaptive Jitter Buffer algorithm * * Copyright (C) 2022 Commend.com - c.spielberger@commend.com * * The adaptive jitter buffer algorithm (ajb) for audio buffer can be activated * by invoking `aubuf_set_mode(ab, AUBUF_ADAPTIVE)`. The ajb algorithm * increases the number of packets in the audio buffer during periods of high * network jitter. It reduces the number of packets if the network condition * improves. * * @section jitter Computing the jitter * * The jitter \f$j\f$ is the moving mean absolute deviation (MAD) of the * time period buffered in `aubuf` \f$D\f$. It is estimated by the iterative * formula * * \f$j_n = j_{n-1} + (|D - \overline{D}| - j_{n-1})\kappa\f$ * * where \f$\kappa\f$ is the weight that influences how fast the jitter value * changes. * * We choose a higher value for the weight \f$\kappa\f$ if * \f$|D - \overline{D}| > j_{n-1}\f$. Thus the jitter rises fast if e.g. * suddenly a network jitter appears. In contrast when the network condition * improves the jitter value slowly shrinks. The reason for different * rising and falling speed is that we have to react fast to avoid buffer * under-runs, whereas reducing of the latency is not so time-critical. * * In the following sections we will describe how the computed jitter is used * to detect situations where the buffered packets should be increased due to * a high jitter. We call this situation **Low** situation. When the jitter * shrinks below some specific value it is a good idea to reduce the buffer in * order to reduce the audio latency. We call this situation **High** * situation. Surely, the Low/High situations have to be decided somehow. * * @section reduce_increase Reduce/Increase buffered packets * * When a Low situation is detected we increase the number of packets in * `aubuf` by holding back a packet during one call to function * `aubuf_read_auframe()`. While when a High situation is detected we reduce * the number of packets by reading another audio frame. This overwrites one * frame. By means of a silence detection `aubuf` is able to drop frames that * are not important for the speech quality. This reduces the audio latency * down to the value before the High situation. * * @section smooth_latency Computing a smooth latency * * The audio frames that are buffered at a concrete point in time in `aubuf` * lead to a temporary latency value \f$D\f$. Let \f$f_0, ..., f_m\f$ be the * audio frames currently stored in `aubuf`. Then * * \f$D = t_m - t_0 + D_p\f$ * * where \f$t_i\f$ is the timestamp of frame \f$f_i\f$ and \f$D_p\f$ is the * packet time `ptime`. The packet time is a constant that is specified at the * beginning of a SIP call. * * The temporary latency \f$D\f$ is discontinuous over time and not adequate * for deciding or detecting Low or High situations. Therefore we again use an * exponential moving average (EMA) to smooth \f$D\f$. Let \f$\kappa\f$ be an * adequate moving average speed factor, then the smoothed latency * * \f$l_n = l_{n-1} + (D - l_{n-1})\kappa\f$. * * We use \f$l_n\f$ to estimate the average buffer time \f$\overline{D}\f$. * Low/High situations are decided when the smoothed latency \f$l_n\f$ runs out * of some boundaries that are computed from the jitter. * * @section low_high Deciding Low/High situations * * During each iteration (each call to `aubuf_write_frame()`) the jitter and * the latency are computed. Additionally we compute the bottom boundary * \f$D_b\f$ and the top boundary \f$D_t\f$ with * * \f$D_b = max(m . l_n, \frac{2}{3} D_p)\f$ and * * \f$D_t = max(M . l_n, D_b + D_p)\f$, * * where \f$1 < m < M\f$. * * Finally we have everything for deciding Low and High situations. That is if * \f$l_n\f$ moves out of the boundaries * * \f$D_b < l_n < D_t\f$, * * then we fire a Low/High. * * @section early_adjustment Early adjustment of the latency * * If we detect a Low/High situation we increase/reduce the number of packets. * Now we immediately increment/decrement the smoothed latency \f$l_n\f$ by * \f$D_p\f$. * Thus early adjustment for a Low situation is * * \f$l_{n+1} = l_n + D_p\f$ * * and for a High situation * * \f$l_{n+1} = l_n - D_p\f$. * * This avoids multiple Low/High detections in a row. * * @section silence Silence detection * * It is preferable to drop an audio frame only if it contains nearly silence. * * @section symbols Math symbols vs. C-variables * * In order to avoid float computation we use micro seconds to measure the time * differences, the jitter and buffer time. Symbols used in this document are * mapped to the C-variables in `src/aubuf/ajb.c` like this table shows: * * Symbol |Variable * --------|-------- * \f$j_n\f$ | `jitter` * \f$D\f$ | `buftime` * \f$l_n\f$ | `avbuftime` * \f$D_b\f$ | `bufmin` * \f$D_t\f$ | `bufmax` * \f$D_p\f$ | `ptime` */ #include #include #include #include #include #include #include "ajb.h" #define DEBUG_LEVEL 5 /** * @defgroup The adaptive jitter computation is done by means of an exponential * moving average (EMA). *j_i = j_{i-1} + a (c - j_{i-1}) * * Where $a$ ist the EMA coefficient and $c$ is the current value. */ enum { JITTER_EMA_COEFF = 512, /* Divisor for jitter EMA coefficient */ JITTER_UP_SPEED = 64, /* 64 times faster up than down */ BUFTIME_EMA_COEFF = 128, /* Divisor for Buftime EMA coeff. */ BUFTIME_LO = 125, /* 125% of jitter */ BUFTIME_HI = 175, /* 175% of jitter */ SKEW_MAX = 10, /* Max skew in [ms] */ }; /** Adaptive jitter buffer statistics */ struct ajb { int32_t jitter; /**< Jitter in [us] */ mtx_t *lock; uint64_t ts; /**< previous timestamp */ uint64_t ts0; /**< reference timestamp */ uint64_t tr0; /**< reference time of arrival */ uint64_t tr00; /**< arrival of first packet */ #ifdef RE_AUBUF_TRACE struct { int32_t d; uint32_t buftime; uint32_t bufmin; uint32_t bufmax; enum ajb_state as; } plot; char buf[136]; /**< Buffer for trace */ #endif enum ajb_state as; /**< computed jitter buffer state */ int32_t avbuftime; /**< average buffered time [us] */ bool started; /**< Started flag */ size_t wish_sz; /**< Wish size of buffer [Bytes] */ uint32_t dropped; /**< Dropped audio frames counter */ double silence; /**< Silence audio level */ }; static void destructor(void *arg) { struct ajb *ajb = arg; mem_deref(ajb->lock); } #ifdef RE_AUBUF_TRACE static void plot_ajb(struct ajb *ajb, uint64_t tr) { uint32_t treal; if (!ajb->tr00) ajb->tr00 = tr; treal = (uint32_t) (tr - ajb->tr00); re_snprintf(ajb->buf, sizeof(ajb->buf), "%s, 0x%p, %u, %i, %u, %u, %u, %i, %i, %u", __func__, /* row 1 - grep */ ajb, /* row 2 - grep optional */ treal, /* row 3 - plot x-axis */ ajb->plot.d, /* row 4 - plot */ ajb->jitter, /* row 5 - plot */ ajb->plot.buftime, /* row 6 - plot */ ajb->avbuftime, /* row 7 - plot */ ajb->plot.bufmin, /* row 8 - plot */ ajb->plot.bufmax, /* row 9 - plot */ ajb->plot.as); /* row 10 - plot */ re_trace_event("ajb", "plot", 'P', NULL, RE_TRACE_ARG_STRING_COPY, "line", ajb->buf); } #endif /** * Initializes the adaptive jitter buffer statistics * * @param silence Silence audio level * @param wish_sz Wish size of buffer [Bytes] * * @return ajb Adaptive jitter buffer statistics */ struct ajb *ajb_alloc(double silence, size_t wish_sz) { struct ajb *ajb; int err; ajb = mem_zalloc(sizeof(*ajb), destructor); if (!ajb) return NULL; err = mutex_alloc(&ajb->lock); if (err) goto out; ajb->ts0 = 0; ajb->tr0 = 0; ajb->as = AJB_GOOD; ajb->silence = silence; ajb->wish_sz = wish_sz; out: if (err) ajb = mem_deref(ajb); return ajb; } void ajb_reset(struct ajb *ajb) { if (!ajb) return; mtx_lock(ajb->lock); ajb->ts = 0; ajb->ts0 = 0; ajb->tr0 = 0; /* We start with wish size. */ ajb->started = false; ajb->as = AJB_GOOD; mtx_unlock(ajb->lock); } /** * Computes the jitter for audio frame arrival. * * @param ajb Adaptive jitter buffer statistics * @param af Audio frame * @param cur_sz Current aubuf size */ void ajb_calc(struct ajb *ajb, const struct auframe *af, size_t cur_sz) { uint64_t tr; /**< Real time in [us] */ uint32_t buftime, bufmax, bufmin; /**< Buffer time in [us] */ uint32_t bufwish; /**< Buffer wish time in [us] */ int32_t d; /**< Time shift in [us] */ int32_t da; /**< Absolut time shift in [us] */ int32_t s; /**< EMA coefficient */ uint64_t ts; /**< Time stamp */ uint64_t ds; /**< Time stamp duration */ uint32_t ptime; /**< Packet time [us] */ size_t szdiv; if (!ajb || !af || !af->srate) return; mtx_lock(ajb->lock); ts = af->timestamp; tr = tmr_jiffies_usec(); if (!ajb->ts0) goto out; ds = ts - ajb->ts0; d = (int32_t) (int64_t) ( (tr - ajb->tr0) - ds ); da = abs(d); szdiv = af->srate * af->ch * aufmt_sample_size(af->fmt) / 1000; buftime = (uint32_t) (cur_sz * 1000 / szdiv); bufwish = (uint32_t) (ajb->wish_sz * 1000 / szdiv); if (ajb->started) { ajb->avbuftime += ((int32_t) buftime - ajb->avbuftime) / BUFTIME_EMA_COEFF; if (ajb->avbuftime < 0) ajb->avbuftime = 0; } else { /* Directly after "filling" of aubuf compute a good start value * fitting to wish size. */ ajb->avbuftime = buftime; ajb->jitter = ajb->avbuftime * 100 * 2 / (BUFTIME_LO + BUFTIME_HI); ajb->started = true; } s = da > ajb->jitter ? JITTER_UP_SPEED : 1; ajb->jitter += (da - ajb->jitter) * s / JITTER_EMA_COEFF; if (ajb->jitter < 0) ajb->jitter = 0; bufmin = (uint32_t) ajb->jitter * BUFTIME_LO / 100; bufmax = (uint32_t) ajb->jitter * BUFTIME_HI / 100; ptime = (uint32_t) (af->sampc * AUDIO_TIMEBASE / (af->srate * af->ch)); bufmin = MAX(bufmin, ptime * 2 / 3); if (bufwish >= ptime) bufmin = MAX(bufmin, bufwish - ptime / 3); bufmax = MAX(bufmax, bufmin + 7 * ptime / 6); /* reset time base if a frame is missing or skew is too high */ if (ts - ajb->ts > ptime || da > SKEW_MAX * 1000) ajb->ts0 = 0; if ((uint32_t) ajb->avbuftime < bufmin) ajb->as = AJB_LOW; else if ((uint32_t) ajb->avbuftime > bufmax) ajb->as = AJB_HIGH; else ajb->as = AJB_GOOD; #ifdef RE_AUBUF_TRACE ajb->plot.d = d; ajb->plot.buftime = buftime; ajb->plot.bufmin = bufmin; ajb->plot.bufmax = bufmax; plot_ajb(ajb, tr / 1000); #endif out: ajb->ts = ts; if (!ajb->ts0) { ajb->ts0 = ts; ajb->tr0 = tr; } mtx_unlock(ajb->lock); } void ajb_set_ts0(struct ajb *ajb, uint64_t timestamp) { if (!ajb) return; mtx_lock(ajb->lock); ajb->ts = timestamp; ajb->ts0 = timestamp; ajb->tr0 = tmr_jiffies_usec(); mtx_unlock(ajb->lock); } /** * Get the state of the Adaptive Jitter Buffer * * @param ajb Adaptive Jitter Buffer state * @param af Audio frame * * @return Computed jitter buffer state */ enum ajb_state ajb_get(struct ajb *ajb, struct auframe *af) { enum ajb_state as = AJB_GOOD; uint32_t ptime; /**< Packet time [us] */ if (!ajb || !af || !af->srate || !af->sampc) return AJB_GOOD; mtx_lock(ajb->lock); /* ptime in [us] */ ptime = (uint32_t) (af->sampc * AUDIO_TIMEBASE / (af->srate * af->ch)); if (!ajb->avbuftime) goto out; if (ajb->as == AJB_GOOD || (ajb->silence < 0. && auframe_level(af) > ajb->silence)) goto out; as = ajb->as; if (as == AJB_HIGH) { /* early adjustment of avbuftime */ ajb->avbuftime -= ptime; ajb->as = AJB_GOOD; #ifdef RE_AUBUF_TRACE ajb->plot.as = AJB_HIGH; plot_ajb(ajb, tmr_jiffies()); ajb->plot.as = AJB_GOOD; #endif } else if (as == AJB_LOW) { /* early adjustment */ ajb->avbuftime += ptime; ajb->as = AJB_GOOD; #ifdef RE_AUBUF_TRACE ajb->plot.as = AJB_LOW; plot_ajb(ajb, tmr_jiffies()); ajb->plot.as = AJB_GOOD; #endif } out: mtx_unlock(ajb->lock); return as; } int32_t ajb_debug(const struct ajb *ajb) { int32_t jitter; if (!ajb) return 0; mtx_lock(ajb->lock); jitter = ajb->jitter; mtx_unlock(ajb->lock); re_printf(" ajb jitter: %d, ajb avbuftime: %d\n", jitter / 1000, ajb->avbuftime); return jitter; } ================================================ FILE: rem/aubuf/ajb.h ================================================ /** * @file ajb.h Adaptive Jitter Buffer interface * * Copyright (C) 2022 Commend.com - c.spielberger@commend.com */ enum ajb_state { AJB_GOOD = 0, AJB_LOW, AJB_HIGH, }; struct ajb; struct ajb *ajb_alloc(double silence, size_t wish_sz); void ajb_reset(struct ajb *ajb); void ajb_calc(struct ajb *ajb, const struct auframe *af, size_t sampc); enum ajb_state ajb_get(struct ajb *ajb, struct auframe *af); int32_t ajb_debug(const struct ajb *ajb); void ajb_set_ts0(struct ajb *ajb, uint64_t timestamp); ================================================ FILE: rem/aubuf/aubuf.c ================================================ /** * @file aubuf.c Audio Buffer * * Copyright (C) 2010 Creytiv.com */ #undef RE_TRACE_ENABLED #if AUBUF_TRACE #define RE_TRACE_ENABLED 1 #endif #include #include #include #include #include #include #include "ajb.h" #define AUBUF_DEBUG 0 enum { POOL_FRAMES = 25 }; /** Locked audio-buffer with almost zero-copy */ struct aubuf { struct list afl; struct mem_pool *pool; struct pl *id; /**< Audio buffer Identifier */ mtx_t *lock; size_t wish_sz; size_t cur_sz; size_t max_sz; size_t fill_sz; /**< To fill size */ size_t pkt_sz; /**< Packet size */ size_t wr_sz; /**< Written size */ bool started; uint64_t ts; struct { size_t or; size_t ur; } stats; enum aubuf_mode mode; struct ajb *ajb; /**< Adaptive jitter buffer statistics */ double silence; /**< Silence volume in negative [dB] */ bool live; /**< Live stream switch */ }; struct frame { struct le le; struct mbuf *mb; struct auframe af; struct mem_pool_entry *e; }; static void frame_destructor(void *data) { struct frame *f = data; list_unlink(&f->le); mem_deref(f->mb); } static void aubuf_destructor(void *arg) { struct aubuf *ab = arg; mem_deref(ab->lock); mem_deref(ab->ajb); mem_deref(ab->id); mem_deref(ab->pool); } static void read_auframe(struct aubuf *ab, struct auframe *af) { struct le *le = ab->afl.head; size_t sample_size = aufmt_sample_size(af->fmt); size_t sz = auframe_size(af); uint8_t *p = af->sampv; bool first = true; while (le) { struct frame *f = le->data; size_t n; le = le->next; n = min(mbuf_get_left(f->mb), sz); (void)mbuf_read_mem(f->mb, p, n); ab->cur_sz -= n; if (first) { af->id = f->af.id; af->srate = f->af.srate; af->ch = f->af.ch; af->timestamp = f->af.timestamp; af->fmt = f->af.fmt; } if (!mbuf_get_left(f->mb)) { mem_pool_release(ab->pool, f->e); } else if (af->srate && af->ch && sample_size) { f->af.timestamp += auframe_bytes_to_timestamp(&f->af, n); } if (n == sz) break; p += n; sz -= n; first = false; } } /** * Allocate a new audio buffer * * @param abp Pointer to allocated audio buffer * @param min_sz Minimum buffer size * @param max_sz Maximum buffer size (0 for no max size) * * @return 0 for success, otherwise error code */ int aubuf_alloc(struct aubuf **abp, size_t min_sz, size_t max_sz) { struct aubuf *ab; int err; if (!abp) return EINVAL; ab = mem_zalloc(sizeof(*ab), aubuf_destructor); if (!ab) return ENOMEM; err = mem_pool_alloc(&ab->pool, POOL_FRAMES, sizeof(struct frame), frame_destructor); if (err) goto out; err = mutex_alloc(&ab->lock); if (err) goto out; ab->wish_sz = min_sz; ab->max_sz = max_sz; ab->fill_sz = min_sz; ab->live = true; out: if (err) mem_deref(ab); else *abp = ab; return err; } /** * Set buffer id. * * @param ab Audio buffer. * @param id Identifier. */ void aubuf_set_id(struct aubuf *ab, struct pl *id) { if (!ab) return; mtx_lock(ab->lock); ab->id = mem_ref(id); mtx_unlock(ab->lock); } /** * Sets the live stream flag on/off. If activated the audio buffer drops old * frames on first read to keep the latency under `min_sz` bytes on startup. * Default: `live` is true. * * @param ab Audio buffer * @param live Live flag */ void aubuf_set_live(struct aubuf *ab, bool live) { if (!ab) return; ab->live = live; } void aubuf_set_mode(struct aubuf *ab, enum aubuf_mode mode) { if (!ab) return; ab->mode = mode; } /** * Sets the volume level for silence * * @param ab Audio buffer * @param silence Volume level in negative [dB] */ void aubuf_set_silence(struct aubuf *ab, double silence) { if (!ab) return; ab->silence = silence; } /** * Resize audio buffer (flushes aubuf) * * @param ab Audio buffer * @param min_sz Minimum buffer size * @param max_sz Maximum buffer size (0 for no max size) * * @return 0 for success, otherwise error code */ int aubuf_resize(struct aubuf *ab, size_t min_sz, size_t max_sz) { if (!ab) return EINVAL; mtx_lock(ab->lock); ab->wish_sz = min_sz; ab->max_sz = max_sz; mtx_unlock(ab->lock); aubuf_flush(ab); return 0; } static bool frame_less_equal(struct le *le1, struct le *le2, void *arg) { struct frame *frame1 = le1->data; struct frame *frame2 = le2->data; (void)arg; return frame1->af.timestamp <= frame2->af.timestamp; } /** * Append a PCM-buffer to the end of the audio buffer * * @param ab Audio buffer * @param mb Mbuffer with PCM samples * @param af Audio frame (optional) * * @return 0 for success, otherwise error code */ int aubuf_append_auframe(struct aubuf *ab, struct mbuf *mb, const struct auframe *af) { struct frame *f; size_t sz; if (!ab || !mb) return EINVAL; struct mem_pool_entry *e = mem_pool_borrow_extend(ab->pool); if (!e) return ENOMEM; f = mem_pool_member(e); f->e = e; f->mb = mem_ref(mb); if (af) f->af = *af; sz = mbuf_get_left(mb); mtx_lock(ab->lock); ab->pkt_sz = sz; if (ab->fill_sz >= ab->pkt_sz) ab->fill_sz -= ab->pkt_sz; if (!f->af.timestamp && f->af.srate && f->af.ch) { f->af.timestamp = auframe_bytes_to_timestamp(&f->af, ab->wr_sz); } list_insert_sorted(&ab->afl, frame_less_equal, NULL, &f->le, f); ab->cur_sz += sz; ab->wr_sz += sz; if (ab->max_sz && ab->cur_sz > ab->max_sz) { ++ab->stats.or; RE_TRACE_ID_INSTANT("aubuf", "overrun", ab->id); f = list_ledata(ab->afl.head); if (f) { ab->cur_sz -= mbuf_get_left(f->mb); mem_pool_release(ab->pool, f->e); } } mtx_unlock(ab->lock); return 0; } /** * Write PCM samples to the audio buffer * * @param ab Audio buffer * @param af Audio frame * * @return 0 for success, otherwise error code */ int aubuf_write_auframe(struct aubuf *ab, const struct auframe *af) { struct mbuf *mb; size_t sz; size_t sample_size; bool ajb; int err; if (!ab || !af) return EINVAL; sample_size = aufmt_sample_size(af->fmt); if (sample_size) sz = af->sampc * aufmt_sample_size(af->fmt); else sz = af->sampc; mb = mbuf_alloc(sz); if (!mb) return ENOMEM; (void)mbuf_write_mem(mb, af->sampv, sz); mb->pos = 0; err = aubuf_append_auframe(ab, mb, af); mtx_lock(ab->lock); mem_deref(mb); ajb = !ab->fill_sz && ab->ajb; mtx_unlock(ab->lock); if (ajb) ajb_calc(ab->ajb, af, ab->cur_sz); return err; } /** * Read PCM samples from the audio buffer. If there is not enough data * in the audio buffer, silence will be read. * * @param ab Audio buffer * @param af Audio frame (af.sampv, af.sampc and af.fmt needed) */ void aubuf_read_auframe(struct aubuf *ab, struct auframe *af) { size_t sz; bool filling; enum ajb_state as; bool drop; if (!ab || !af) return; sz = auframe_size(af); mtx_lock(ab->lock); if (!ab->ajb && ab->mode == AUBUF_ADAPTIVE) ab->ajb = ajb_alloc(ab->silence, ab->wish_sz); as = ajb_get(ab->ajb, af); if (as == AJB_LOW) { #if AUBUF_DEBUG (void)re_printf("aubuf: inc buffer due to high jitter\n"); ajb_debug(ab->ajb); #endif goto out; } RE_TRACE_ID_INSTANT_I("aubuf", "cur_sz_ms", auframe_bytes_to_ms(af, ab->cur_sz), ab->id); if (ab->fill_sz || ab->cur_sz < sz) { if (!ab->fill_sz) { ++ab->stats.ur; RE_TRACE_ID_INSTANT("aubuf", "underrun", ab->id); } if (!ab->fill_sz) ajb_set_ts0(ab->ajb, 0); filling = ab->fill_sz > 0; memset(af->sampv, 0, sz); if (filling) { RE_TRACE_ID_INSTANT("aubuf", "filling", ab->id); goto out; } else ab->fill_sz = ab->wish_sz; } /* on first read drop old frames */ drop = ab->live && !ab->started && ab->wish_sz; while (drop && ab->cur_sz > ab->wish_sz) { struct frame *f = list_ledata(ab->afl.head); if (f) { ab->cur_sz -= mbuf_get_left(f->mb); mem_pool_release(ab->pool, f->e); } } ab->started = true; read_auframe(ab, af); if (as == AJB_HIGH) { #if AUBUF_DEBUG (void)re_printf("aubuf: drop a frame to reduce latency\n"); ajb_debug(ab->ajb); #endif read_auframe(ab, af); } out: if (ab->fill_sz && ab->fill_sz < ab->pkt_sz) { if (ab->fill_sz >= sz) ab->fill_sz -= sz; else ab->fill_sz = 0; } mtx_unlock(ab->lock); } /** * Timed read PCM samples from the audio buffer. If there is not enough data * in the audio buffer, silence will be read. * * @param ab Audio buffer * @param ptime Packet time in [ms] * @param p Buffer where PCM samples are read into * @param sz Number of bytes to read * * @note This does the same as aubuf_read() except that it also takes * timing into consideration. * * @return 0 if valid PCM was read, ETIMEDOUT if no PCM is ready yet */ int aubuf_get(struct aubuf *ab, uint32_t ptime, uint8_t *p, size_t sz) { uint64_t now; int err = 0; if (!ab || !ptime) return EINVAL; mtx_lock(ab->lock); now = tmr_jiffies(); if (!ab->ts) ab->ts = now; if (now < ab->ts) { err = ETIMEDOUT; goto out; } ab->ts += ptime; out: mtx_unlock(ab->lock); if (!err) aubuf_read(ab, p, sz); return err; } /** * Flush the audio buffer * * @param ab Audio buffer */ void aubuf_flush(struct aubuf *ab) { if (!ab) return; mtx_lock(ab->lock); list_clear(&ab->afl); mem_pool_flush(ab->pool); ab->fill_sz = ab->wish_sz; ab->cur_sz = 0; ab->wr_sz = 0; ab->ts = 0; mtx_unlock(ab->lock); ajb_reset(ab->ajb); } /** * Audio buffer debug handler, use with fmt %H * * @param pf Print function * @param ab Audio buffer * * @return 0 if success, otherwise errorcode */ int aubuf_debug(struct re_printf *pf, const struct aubuf *ab) { int err; if (!ab) return 0; mtx_lock(ab->lock); err = re_hprintf(pf, "wish_sz=%zu cur_sz=%zu fill_sz=%zu", ab->wish_sz, ab->cur_sz, ab->fill_sz); err |= re_hprintf(pf, " [overrun=%zu underrun=%zu]", ab->stats.or, ab->stats.ur); mtx_unlock(ab->lock); return err; } /** * Get the current number of bytes in the audio buffer * * @param ab Audio buffer * * @return Number of bytes in the audio buffer */ size_t aubuf_cur_size(const struct aubuf *ab) { size_t sz; if (!ab) return 0; mtx_lock(ab->lock); sz = ab->cur_sz; mtx_unlock(ab->lock); return sz; } /** * Get the maximum number of bytes of the audio buffer * * @param ab Audio buffer * * @return Maximum number of bytes */ size_t aubuf_maxsz(const struct aubuf *ab) { size_t sz; if (!ab) return 0; mtx_lock(ab->lock); sz = ab->max_sz; mtx_unlock(ab->lock); return sz; } /** * Returns true if the minimum size was reached and the read function returned * already the first real data * * @param ab Audio buffer * * @return True if reading was started */ bool aubuf_started(const struct aubuf *ab) { bool started; if (!ab) return false; mtx_lock(ab->lock); started = ab->started; mtx_unlock(ab->lock); return started; } /** * Reorder aubuf by auframe->timestamp * * @param ab Audio buffer */ void aubuf_sort_auframe(struct aubuf *ab) { if (!ab) return; list_sort(&ab->afl, frame_less_equal, NULL); } /** * This function is for reporting that the given audio frame was dropped. Its * timestamp is used to reset the ajb structure to avoid a jump of the computed * jitter value * * @param ab Audio buffer * @param af Audio frame */ void aubuf_drop_auframe(struct aubuf *ab, const struct auframe *af) { if (!ab) return; ajb_set_ts0(ab->ajb, af->timestamp); } ================================================ FILE: rem/auconv/auconv.c ================================================ /** * @file auconv.c Audio sample format converter * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include static inline float ausamp_short2float(int16_t in) { float out; out = (float) (in / (1.0 * 0x8000)); return out; } static inline int16_t ausamp_float2short(float in) { double value; int16_t out; value = in * (8.0 * 0x10000000); if (value >= (1.0 * 0x7fffffff)) { out = 32767; } else if (value <= (-8.0 * 0x10000000)) { out = -32768; } else out = (short) (lrint (value) >> 16); return out; } void auconv_from_s16(enum aufmt dst_fmt, void *dst_sampv, const int16_t *src_sampv, size_t sampc) { float *f; uint8_t *b; size_t i; if (!dst_sampv || !src_sampv || !sampc) return; switch (dst_fmt) { case AUFMT_FLOAT: f = dst_sampv; for (i=0; i> 8; b[3*i+1] = s & 0xff; b[3*i+0] = 0; } break; default: (void)re_fprintf(stderr, "auconv: sample format %d (%s)" " not supported\n", dst_fmt, aufmt_name(dst_fmt)); return; } } void auconv_to_s16(int16_t *dst_sampv, enum aufmt src_fmt, void *src_sampv, size_t sampc) { float *f; uint8_t *b; size_t i; if (!dst_sampv || !src_sampv || !sampc) return; switch (src_fmt) { case AUFMT_FLOAT: f = src_sampv; for (i=0; i #include #include #include #include "aufile.h" /** Audio file state */ struct aufile { struct aufile_prm prm; enum aufile_mode mode; size_t datasize; size_t nread; size_t nwritten; FILE *f; }; static int wavfmt_to_aufmt(enum wavfmt fmt, uint16_t bps) { switch (fmt) { case WAVE_FMT_PCM: if (bps != 16) return -1; return AUFMT_S16LE; case WAVE_FMT_ALAW: if (bps != 8) return -1; return AUFMT_PCMA; case WAVE_FMT_ULAW: if (bps != 8) return -1; return AUFMT_PCMU; default: return -1; } } static enum wavfmt aufmt_to_wavfmt(enum aufmt fmt) { switch (fmt) { case AUFMT_S16LE: return WAVE_FMT_PCM; case AUFMT_PCMA: return WAVE_FMT_ALAW; case AUFMT_PCMU: return WAVE_FMT_ULAW; default: return -1; } } static uint16_t aufmt_to_bps(enum aufmt fmt) { switch (fmt) { case AUFMT_S16LE: return 16; case AUFMT_PCMA: return 8; case AUFMT_PCMU: return 8; default: return 0; } } static void destructor(void *arg) { struct aufile *af = arg; if (!af->f) return; /* Update WAV header in write-mode */ if (af->mode == AUFILE_WRITE && af->nwritten > 0) { rewind(af->f); (void)wav_header_encode(af->f, aufmt_to_wavfmt(af->prm.fmt), af->prm.channels, af->prm.srate, aufmt_to_bps(af->prm.fmt), af->nwritten); } (void)fclose(af->f); } /** * Open a WAVE file for reading or writing * * Supported formats: 16-bit PCM, A-law, U-law * * @param afp Pointer to allocated Audio file * @param prm Audio format of the file * @param filename Filename of the WAV-file to load * @param mode Read or write mode * * @return 0 if success, otherwise errorcode */ int aufile_open(struct aufile **afp, struct aufile_prm *prm, const char *filename, enum aufile_mode mode) { struct wav_fmt fmt; struct aufile *af; int aufmt; int err; if (!afp || !filename || (mode == AUFILE_WRITE && !prm)) return EINVAL; af = mem_zalloc(sizeof(*af), destructor); if (!af) return ENOMEM; af->mode = mode; af->f = fopen(filename, mode == AUFILE_READ ? "rb" : "wb"); if (!af->f) { err = errno; goto out; } switch (mode) { case AUFILE_READ: err = wav_header_decode(&fmt, &af->datasize, af->f); if (err) goto out; aufmt = wavfmt_to_aufmt(fmt.format, fmt.bps); if (aufmt < 0) { err = ENOSYS; goto out; } if (prm) { prm->srate = fmt.srate; prm->channels = (uint8_t)fmt.channels; prm->fmt = aufmt; } break; case AUFILE_WRITE: af->prm = *prm; err = wav_header_encode(af->f, aufmt_to_wavfmt(prm->fmt), prm->channels, prm->srate, aufmt_to_bps(prm->fmt), 0); break; default: err = ENOSYS; break; } out: if (err) mem_deref(af); else *afp = af; return err; } /** * Read PCM-samples from a WAV file * * @param af Audio-file * @param p Read buffer * @param sz Size of buffer, on return contains actual read * * @return 0 if success, otherwise errorcode */ int aufile_read(struct aufile *af, uint8_t *p, size_t *sz) { size_t n; if (!af || !p || !sz || af->mode != AUFILE_READ) return EINVAL; if (af->nread >= af->datasize) { *sz = 0; return 0; } n = min(*sz, af->datasize - af->nread); n = fread(p, 1, n, af->f); if (ferror(af->f)) return errno; *sz = n; af->nread += n; return 0; } /** * Write PCM-samples to a WAV file * * @param af Audio-file * @param p Write buffer * @param sz Size of buffer * * @return 0 if success, otherwise errorcode */ int aufile_write(struct aufile *af, const uint8_t *p, size_t sz) { if (!af || !p || !sz || af->mode != AUFILE_WRITE) return EINVAL; if (1 != fwrite(p, sz, 1, af->f)) return ferror(af->f); af->nwritten += sz; return 0; } /** * Get size of a WAV file in bytes * * @param af Audio-file * * @return size in bytes if success, otherwise 0. */ size_t aufile_get_size(struct aufile *af) { if (!af) return 0; return af->datasize; } /** * Get length of a WAV file in ms * * @param af Audio-file * @param prm Audio file parameters from aufile_open * * @return length in ms if success, otherwise 0. */ size_t aufile_get_length(struct aufile *af, const struct aufile_prm *prm) { if (!af || !prm) return 0; size_t sample_size = aufmt_sample_size(prm->fmt); if (sample_size == 0) return 0; return af->datasize * 1000 / (sample_size * prm->channels * prm->srate); } /** * Set initial playing position of a WAV file in ms * * @param af Audio-file * @param prm Audio file parameters from aufile_open * @param pos_ms Playing position in milliseconds * * @return 0 if success, otherwise errorcode */ int aufile_set_position(struct aufile *af, const struct aufile_prm *prm, size_t pos_ms) { if (!af || !prm) return EINVAL; if (fseek(af->f, 0, SEEK_SET) < 0) return errno; /* this is only used for the side effect of moving the file ptr to the first data block. */ struct wav_fmt fmt; size_t datasize; int err = wav_header_decode(&fmt, &datasize, af->f); if (err) return err; off_t pos = (off_t)(prm->srate * aufmt_sample_size(prm->fmt) * prm->channels * pos_ms / 1000); pos = min((off_t)datasize, pos); if (fseek(af->f, pos, SEEK_CUR) < 0) return errno; af->nread = pos; return 0; } ================================================ FILE: rem/aufile/aufile.h ================================================ /** * @file aufile.h Audio File -- internal API * * Copyright (C) 2010 Creytiv.com */ enum wavfmt { WAVE_FMT_PCM = 0x0001, WAVE_FMT_ALAW = 0x0006, WAVE_FMT_ULAW = 0x0007, }; /** WAVE format sub-chunk */ struct wav_fmt { uint16_t format; uint16_t channels; uint32_t srate; uint32_t byterate; uint16_t block_align; uint16_t bps; uint16_t extra; }; int wav_header_encode(FILE *f, uint16_t format, uint16_t channels, uint32_t srate, uint16_t bps, size_t bytes); int wav_header_decode(struct wav_fmt *fmt, size_t *datasize, FILE *f); ================================================ FILE: rem/aufile/wave.c ================================================ /** * @file wave.c WAVE format encoding and decoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include "aufile.h" enum { WAVE_FMT_SIZE = 16 }; /** WAV-file chunk */ struct wav_chunk { uint8_t id[4]; uint32_t size; }; static int write_u16(FILE *f, uint16_t v) { v = sys_htols(v); if (1 != fwrite(&v, sizeof(v), 1, f)) return ferror(f); return 0; } static int write_u32(FILE *f, uint32_t v) { v = sys_htoll(v); if (1 != fwrite(&v, sizeof(v), 1, f)) return ferror(f); return 0; } static int read_u16(FILE *f, uint16_t *v) { uint16_t vle; if (1 != fread(&vle, sizeof(vle), 1, f)) return ferror(f); *v = sys_ltohs(vle); return 0; } static int read_u32(FILE *f, uint32_t *v) { uint32_t vle; if (1 != fread(&vle, sizeof(vle), 1, f)) return ferror(f); *v = sys_ltohl(vle); return 0; } static int chunk_encode(FILE *f, const char *id, size_t sz) { if (1 != fwrite(id, 4, 1, f)) return ferror(f); return write_u32(f, (uint32_t)sz); } static int chunk_decode(struct wav_chunk *chunk, FILE *f) { if (1 != fread(chunk->id, sizeof(chunk->id), 1, f)) return ferror(f); return read_u32(f, &chunk->size); } int wav_header_encode(FILE *f, uint16_t format, uint16_t channels, uint32_t srate, uint16_t bps, size_t bytes) { int err; err = chunk_encode(f, "RIFF", 36 + bytes); if (err) return err; if (1 != fwrite("WAVE", 4, 1, f)) return ferror(f); err = chunk_encode(f, "fmt ", WAVE_FMT_SIZE); if (err) return err; err = write_u16(f, format); err |= write_u16(f, channels); err |= write_u32(f, srate); err |= write_u32(f, srate * channels * bps / 8); err |= write_u16(f, channels * bps / 8); err |= write_u16(f, bps); if (err) return err; return chunk_encode(f, "data", bytes); } int wav_header_decode(struct wav_fmt *fmt, size_t *datasize, FILE *f) { struct wav_chunk header, format, chunk; uint8_t rifftype[4]; /* "WAVE" */ int err = 0; err = chunk_decode(&header, f); if (err) return err; if (memcmp(header.id, "RIFF", 4)) { (void)re_fprintf(stderr, "aufile: expected RIFF (%b)\n", header.id, sizeof(header.id)); return EBADMSG; } if (1 != fread(rifftype, sizeof(rifftype), 1, f)) return ferror(f); if (memcmp(rifftype, "WAVE", 4)) { (void)re_fprintf(stderr, "aufile: expected WAVE (%b)\n", rifftype, sizeof(rifftype)); return EBADMSG; } err = chunk_decode(&format, f); if (err) return err; if (memcmp(format.id, "fmt ", 4)) { (void)re_fprintf(stderr, "aufile: expected fmt (%b)\n", format.id, sizeof(format.id)); return EBADMSG; } if (format.size < WAVE_FMT_SIZE) return EBADMSG; err = read_u16(f, &fmt->format); err |= read_u16(f, &fmt->channels); err |= read_u32(f, &fmt->srate); err |= read_u32(f, &fmt->byterate); err |= read_u16(f, &fmt->block_align); err |= read_u16(f, &fmt->bps); if (err) return err; /* skip any extra bytes */ if (format.size >= (WAVE_FMT_SIZE + 2)) { err = read_u16(f, &fmt->extra); if (err) return err; if (fmt->extra > 0) { if (fseek(f, fmt->extra, SEEK_CUR)) return errno; } } /* fast forward to "data" chunk */ for (;;) { err = chunk_decode(&chunk, f); if (err) return err; if (chunk.size > header.size) { (void)re_fprintf(stderr, "chunk size too large" " (%u > %u)\n", chunk.size, header.size); return EBADMSG; } if (0 == memcmp(chunk.id, "data", 4)) { *datasize = chunk.size; break; } if (fseek(f, chunk.size, SEEK_CUR) < 0) return errno; } return 0; } ================================================ FILE: rem/auframe/auframe.c ================================================ /** * @file auframe.c Audio frame * * Copyright (C) 2010 - 2020 Alfred E. Heggestad */ #include #include #include #include #include /** * Initialize an audio frame * * @param af Audio frame * @param fmt Sample format (enum aufmt) * @param sampv Audio samples * @param sampc Total number of audio samples * @param srate Samplerate * @param ch Channels */ void auframe_init(struct auframe *af, enum aufmt fmt, void *sampv, size_t sampc, uint32_t srate, uint8_t ch) { if (!af) return; if (0 == aufmt_sample_size(fmt)) { re_printf("auframe: init: unsupported sample format %d (%s)\n", fmt, aufmt_name(fmt)); } memset(af, 0, sizeof(*af)); af->fmt = fmt; af->sampv = sampv; af->sampc = sampc; af->srate = srate; af->level = AULEVEL_UNDEF; af->ch = ch; af->id = 0; } /** * Get the size of an audio frame * * @param af Audio frame * * @return Number of bytes */ size_t auframe_size(const struct auframe *af) { size_t sz; if (!af) return 0; sz = aufmt_sample_size(af->fmt); if (sz == 0) { re_printf("auframe: size: illegal format %d (%s)\n", af->fmt, aufmt_name(af->fmt)); sz = 1; } return af->sampc * sz; } /** * Silence all samples in an audio frame * * @param af Audio frame */ void auframe_mute(struct auframe *af) { if (!af) return; memset(af->sampv, 0, auframe_size(af)); } /** * Get audio level (only calculated once) * * @note Set af->level = AULEVEL_UNDEF to force re-calculation * * @param af Audio frame * * @return Audio level expressed in dBov on success and AULEVEL_UNDEF on error */ double auframe_level(struct auframe *af) { if (!af) return AULEVEL_UNDEF; if (af->fmt == AUFMT_RAW) return AULEVEL_UNDEF; if (af->level == AULEVEL_UNDEF) af->level = aulevel_calc_dbov(af->fmt, af->sampv, af->sampc); return af->level; } uint64_t auframe_bytes_to_timestamp(const struct auframe *af, size_t n) { size_t sample_size = aufmt_sample_size(af->fmt); return ((uint64_t) n) * AUDIO_TIMEBASE / (af->srate * af->ch * sample_size); } uint64_t auframe_bytes_to_ms(const struct auframe *af, size_t n) { size_t sample_size = aufmt_sample_size(af->fmt); if (!af->srate || !af->ch || !sample_size) return 0; return ((uint64_t)n * 1000) / (af->srate * af->ch * sample_size); } size_t auframe_ms_to_bytes(const struct auframe *af, uint16_t ms) { return aufmt_sample_size(af->fmt) * au_calc_nsamp(af->srate, af->ch, ms); } ================================================ FILE: rem/aulevel/aulevel.c ================================================ /** * @file aulevel/aulevel.c Audio level * * Copyright (C) 2017 Creytiv.com */ #include #include #include /** * Generic routine to calculate RMS (Root-Mean-Square) from * a set of signed 16-bit values * * \verbatim .--------------- | N-1 | ----. | \ | \ 2 | | s[n] | / | / _ | ----' \ | n=0 \ | ------------ \| N \endverbatim * * @param data Array of signed 16-bit values * @param len Number of values * * @return RMS value from 0 to 32768 */ static double calc_rms_s16(const int16_t *data, size_t len) { int64_t sum = 0; size_t i; if (!data || !len) return .0; for (i = 0; i < len; i++) { sum += data[i] * data[i]; } return sqrt(sum / (double)len); } static double calc_rms_s32(const int32_t *data, size_t len) { double sum = 0; size_t i; if (!data || !len) return .0; for (i = 0; i < len; i++) { const double sample = data[i]; sum += sample * sample; } return sqrt(sum / (double)len); } static double calc_rms_float(const float *data, size_t len) { double sum = 0; size_t i; if (!data || !len) return .0; for (i = 0; i < len; i++) { const double sample = data[i]; sum += sample * sample; } return sqrt(sum / (double)len); } /** * Calculate the audio level in dBov from a set of audio samples. * dBov is the level, in decibels, relative to the overload point * of the system * * @param fmt Sample format (enum aufmt) * @param sampv Audio samples * @param sampc Number of audio samples * * @return Audio level expressed in dBov on success and AULEVEL_UNDEF on error */ double aulevel_calc_dbov(int fmt, const void *sampv, size_t sampc) { static const double peak_s16 = 32767.0; static const double peak_s32 = 2147483647.0; double rms, dbov; if (!sampv || !sampc) return AULEVEL_UNDEF; switch (fmt) { case AUFMT_S16LE: rms = calc_rms_s16(sampv, sampc) / peak_s16; break; case AUFMT_S32LE: rms = calc_rms_s32(sampv, sampc) / peak_s32; break; case AUFMT_FLOAT: rms = calc_rms_float(sampv, sampc) / 1.0; break; default: re_printf("aulevel: sample format not supported (%s)\n", aufmt_name(fmt)); return AULEVEL_UNDEF; } dbov = 20 * log10(rms); if (dbov < AULEVEL_MIN) dbov = AULEVEL_MIN; else if (dbov > AULEVEL_MAX) dbov = AULEVEL_MAX; return dbov; } ================================================ FILE: rem/aumix/aumix.c ================================================ /** * @file aumix.c Audio Mixer * * Copyright (C) 2010 Creytiv.com */ #define _BSD_SOURCE 1 #define _DEFAULT_SOURCE 1 #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include /** Defines an Audio mixer */ struct aumix { mtx_t *mutex; cnd_t cond; struct list srcl; thrd_t thread; struct aufile *af; uint32_t ptime; uint32_t frame_size; uint32_t srate; uint8_t ch; struct { uint16_t min; uint16_t max; } latency; aumix_record_h *recordh; aumix_record_h *record_sumh; struct auframe rec_sum; bool run; }; /** Defines an Audio mixer source */ struct aumix_source { struct le le; struct pl *id; struct auframe af; int16_t *frame; struct aubuf *aubuf; struct aumix *mix; aumix_frame_h *fh; aumix_read_h *readh; void *arg; bool muted; }; static void dummy_frame_handler(const int16_t *sampv, size_t sampc, void *arg) { (void)sampv; (void)sampc; (void)arg; } static void destructor(void *arg) { struct aumix *mix = arg; mtx_lock(mix->mutex); bool run = mix->run; mtx_unlock(mix->mutex); if (run) { mtx_lock(mix->mutex); mix->run = false; cnd_signal(&mix->cond); mtx_unlock(mix->mutex); thrd_join(mix->thread, NULL); } mem_deref(mix->af); mem_deref(mix->mutex); } static void source_destructor(void *arg) { struct aumix_source *src = arg; if (src->le.list) { mtx_lock(src->mix->mutex); list_unlink(&src->le); mtx_unlock(src->mix->mutex); } mem_deref(src->aubuf); mem_deref(src->frame); mem_deref(src->mix); mem_deref(src->id); } static int aumix_thread(void *arg) { uint8_t *silence, *frame, *base_frame; struct aumix *mix = arg; int16_t *mix_frame; uint64_t ts = 0; silence = mem_zalloc(mix->frame_size*2, NULL); frame = mem_alloc(mix->frame_size*2, NULL); mix_frame = mem_alloc(mix->frame_size*2, NULL); if (!silence || !frame || !mix_frame) goto out; mtx_lock(mix->mutex); while (mix->run) { struct le *le; uint64_t now; if (!mix->srcl.head) { mix->af = mem_deref(mix->af); cnd_wait(&mix->cond, mix->mutex); ts = 0; } else { mtx_unlock(mix->mutex); sys_usleep(4000); mtx_lock(mix->mutex); } now = tmr_jiffies(); if (!ts) ts = now; if (ts > now) continue; if (mix->af) { size_t n = mix->frame_size*2; if (aufile_read(mix->af, frame, &n) || n == 0) { mix->af = mem_deref(mix->af); base_frame = silence; } else if (n < mix->frame_size*2) { memset(frame + n, 0, mix->frame_size*2 - n); mix->af = mem_deref(mix->af); base_frame = frame; } else { base_frame = frame; } } else { base_frame = silence; } for (le = mix->srcl.head; le; le = le->next) { struct aumix_source *src = le->data; if (src->muted) continue; if (src->readh) src->readh(&src->af, src->arg); else aubuf_read_auframe(src->aubuf, &src->af); if (mix->recordh) mix->recordh(&src->af); } for (le = mix->srcl.head; le; le = le->next) { struct aumix_source *src = le->data; struct le *cle; memcpy(mix_frame, base_frame, mix->frame_size * 2); LIST_FOREACH(&mix->srcl, cle) { struct aumix_source *csrc = cle->data; int32_t sample; /* skip self */ if (csrc == src) continue; if (csrc->muted) continue; for (size_t i = 0; i < mix->frame_size; i++) { sample = mix_frame[i] + csrc->frame[i]; /* hard clipping */ if (sample >= 32767) sample = 32767; if (sample <= -32767) sample = -32767; mix_frame[i] = (int16_t)sample; } } src->fh(mix_frame, mix->frame_size, src->arg); } if (mix->record_sumh) { struct le *cle; memcpy(mix_frame, base_frame, mix->frame_size * 2); LIST_FOREACH(&mix->srcl, cle) { struct aumix_source *csrc = cle->data; int32_t sample; if (csrc->muted) continue; for (size_t i = 0; i < mix->frame_size; i++) { sample = mix_frame[i] + csrc->frame[i]; /* hard clipping */ if (sample >= 32767) sample = 32767; if (sample <= -32767) sample = -32767; mix_frame[i] = (int16_t)sample; } } mix->rec_sum.timestamp = now; mix->rec_sum.sampv = mix_frame; mix->record_sumh(&mix->rec_sum); } ts += mix->ptime; } mtx_unlock(mix->mutex); out: mem_deref(mix_frame); mem_deref(silence); mem_deref(frame); return 0; } /** * Allocate a new Audio mixer * * @param mixp Pointer to allocated audio mixer * @param srate Sample rate in [Hz] * @param ch Number of channels * @param ptime Packet time in [ms] * * @return 0 for success, otherwise error code */ int aumix_alloc(struct aumix **mixp, uint32_t srate, uint8_t ch, uint32_t ptime) { struct aumix *mix; int err; if (!mixp || !srate || !ch || !ptime) return EINVAL; mix = mem_zalloc(sizeof(*mix), destructor); if (!mix) return ENOMEM; mix->ptime = ptime; mix->frame_size = srate * ch * ptime / 1000; mix->srate = srate; mix->ch = ch; mix->recordh = NULL; mix->latency.min = 60; /* ms */ mix->latency.max = 200; /* ms */ mix->rec_sum.ch = ch; mix->rec_sum.srate = srate; mix->rec_sum.sampc = mix->frame_size; err = mutex_alloc(&mix->mutex); if (err) { goto out; } err = cnd_init(&mix->cond) != thrd_success; if (err) { err = ENOMEM; goto out; } mix->run = true; err = thread_create_name(&mix->thread, "aumix", aumix_thread, mix); if (err) { mix->run = false; goto out; } out: if (err) mem_deref(mix); else *mixp = mix; return err; } /** * Set aumix aubuf default latency * * @param mix Audio mixer * @param min Minimum value in [ms] * @param max Maximum value in [ms] */ void aumix_latency(struct aumix *mix, uint16_t min, uint16_t max) { if (!mix) return; mtx_lock(mix->mutex); mix->latency.min = min; mix->latency.max = max; mtx_unlock(mix->mutex); } /** * Add multitrack record handler (each source can be identified by auframe->id) * * @param mix Audio mixer * @param recordh Record Handler */ void aumix_recordh(struct aumix *mix, aumix_record_h *recordh) { if (!mix) return; mtx_lock(mix->mutex); mix->recordh = recordh; mtx_unlock(mix->mutex); } /** * Add sum record handler * * @param mix Audio mixer * @param recordh Record Handler */ void aumix_record_sumh(struct aumix *mix, aumix_record_h *recordh) { if (!mix) return; mtx_lock(mix->mutex); mix->record_sumh = recordh; mtx_unlock(mix->mutex); } /** * Load audio file for mixer announcements * * @param mix Audio mixer * @param filepath Filename of audio file with complete path * * @return 0 for success, otherwise error code */ int aumix_playfile(struct aumix *mix, const char *filepath) { struct aufile_prm prm; struct aufile *af; int err; if (!mix || !filepath) return EINVAL; err = aufile_open(&af, &prm, filepath, AUFILE_READ); if (err) return err; if (prm.fmt != AUFMT_S16LE || prm.srate != mix->srate || prm.channels != mix->ch) { mem_deref(af); return EINVAL; } mtx_lock(mix->mutex); mem_deref(mix->af); mix->af = af; mtx_unlock(mix->mutex); return 0; } /** * Count number of audio sources in the audio mixer * * @param mix Audio mixer * * @return Number of audio sources */ uint32_t aumix_source_count(const struct aumix *mix) { if (!mix) return 0; mtx_lock(mix->mutex); uint32_t count = list_count(&mix->srcl); mtx_unlock(mix->mutex); return count; } /** * Allocate an audio mixer source * * @param srcp Pointer to allocated audio source * @param mix Audio mixer * @param fh Mixer frame handler * @param arg Handler argument * * @return 0 for success, otherwise error code */ int aumix_source_alloc(struct aumix_source **srcp, struct aumix *mix, aumix_frame_h *fh, void *arg) { struct aumix_source *src; size_t sz; int err; if (!srcp || !mix) return EINVAL; src = mem_zalloc(sizeof(*src), source_destructor); if (!src) return ENOMEM; src->mix = mem_ref(mix); src->fh = fh ? fh : dummy_frame_handler; src->arg = arg; src->muted = false; sz = mix->frame_size*2; src->frame = mem_alloc(sz, NULL); if (!src->frame) { err = ENOMEM; goto out; } auframe_init(&src->af, AUFMT_S16LE, src->frame, mix->frame_size, mix->srate, mix->ch); err = aubuf_alloc(&src->aubuf, auframe_ms_to_bytes(&src->af, mix->latency.min), auframe_ms_to_bytes(&src->af, mix->latency.max)); if (err) goto out; out: if (err) mem_deref(src); else *srcp = src; return err; } /** * Set source id * * @param src Audio mixer source * @param id Source identifier */ void aumix_source_set_id(struct aumix_source *src, struct pl *id) { if (!src || !id) return; mtx_lock(src->mix->mutex); src->id = mem_ref(id); aubuf_set_id(src->aubuf, id); mtx_unlock(src->mix->mutex); } /** * Add source read handler (alternative to aumix_source_put) * * @param src Audio mixer source * @param readh Read Handler */ void aumix_source_readh(struct aumix_source *src, aumix_read_h *readh) { if (!src || !src->mix) return; mtx_lock(src->mix->mutex); src->readh = readh; mtx_unlock(src->mix->mutex); } /** * Mute/unmute aumix source * * @param src Audio mixer source * @param mute True to mute, false to unmute */ void aumix_source_mute(struct aumix_source *src, bool mute) { if (!src) return; src->muted = mute; } /** * Enable/disable aumix source * * @param src Audio mixer source * @param enable True to enable, false to disable */ void aumix_source_enable(struct aumix_source *src, bool enable) { struct aumix *mix; if (!src) return; if (src->le.list && enable) return; if (!src->le.list && !enable) return; mix = src->mix; mtx_lock(mix->mutex); if (enable) { list_append(&mix->srcl, &src->le, src); cnd_signal(&mix->cond); } else { list_unlink(&src->le); } mtx_unlock(mix->mutex); } /** * Write PCM samples for a given source to the audio mixer * * @deprecated use aumix_source_readh or aumix_source_put_auframe * * @param src Audio mixer source * @param sampv PCM samples * @param sampc Number of samples * * @return 0 for success, otherwise error code */ int aumix_source_put(struct aumix_source *src, const int16_t *sampv, size_t sampc) { if (!src || !sampv) return EINVAL; return aubuf_write_samp(src->aubuf, sampv, sampc); } /** * Put a audio frame for a given source to the audio mixer * * @param src Audio mixer source * @param af Audio frame * * @return 0 for success, otherwise error code */ int aumix_source_put_auframe(struct aumix_source *src, struct auframe *af) { if (!src) return EINVAL; return aubuf_write_auframe(src->aubuf, af); } /** * Flush the audio buffer of a given audio mixer source * * @param src Audio mixer source */ void aumix_source_flush(struct aumix_source *src) { if (!src) return; aubuf_flush(src->aubuf); } /** * Audio mixer debug handler * * @param pf Print function * @param mix Audio mixer * * @return 0 if success, otherwise errorcode */ int aumix_debug(struct re_printf *pf, const struct aumix *mix) { struct le *le; int err = 0; if (!pf || !mix) return EINVAL; re_hprintf(pf, "aumix debug:\n"); mtx_lock(mix->mutex); LIST_FOREACH(&mix->srcl, le) { struct aumix_source *src = le->data; re_hprintf(pf, "\tsource: %p muted=%d ", src, src->muted); err = aubuf_debug(pf, src->aubuf); if (err) goto out; re_hprintf(pf, "\n"); } out: mtx_unlock(mix->mutex); return err; } ================================================ FILE: rem/auresamp/resamp.c ================================================ /** * @file resamp.c Audio Resampler * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include /* 48kHz sample-rate, 4kHz cutoff (pass 0-3kHz, stop 5-24kHz) */ static const int16_t fir_48_4[] = { 62, -176, -329, -556, -802, -1005, -1090, -985, -636, -23, 826, 1837, 2894, 3859, 4595, 4994, 4994, 4595, 3859, 2894, 1837, 826, -23, -636, -985, -1090, -1005, -802, -556, -329, -176, 62 }; /* 48kHz sample-rate, 8kHz cutoff (pass 0-7kHz, stop 9-24kHz) */ static const int16_t fir_48_8[] = { 238, 198, -123, -738, -1268, -1204, -380, 714, 1164, 376, -1220, -2206, -1105, 2395, 6909, 10069, 10069, 6909, 2395, -1105, -2206, -1220, 376, 1164, 714, -380, -1204, -1268, -738, -123, 198, 238 }; /* 16kHz sample-rate, 4kHz cutoff and 32kHz sample-rate, 8 kHz cutoff */ static const int16_t fir_16_4[] = { 22, 60, -41, -157, -9, 322, 195, -490, -613, 539, 1362, -229, -2657, -1101, 6031, 13167, 13167, 6031, -1101, -2657, -229, 1362, 539, -613, -490, 195, 322, -9, -157, -41, 60, 22 }; static void upsample_mono2mono(int16_t *outv, const int16_t *inv, size_t inc, unsigned ratio) { unsigned i; while (inc >= 1) { for (i=0; i= 1) { for (i=0; i= 2) { const int16_t s = inv[0]/2 + inv[1]/2; for (i=0; i= 2) { for (i=0; i= ratio) { *outv++ = *inv; inv += ratio; inc -= ratio; } } static void downsample_mono2stereo(int16_t *outv, const int16_t *inv, size_t inc, unsigned ratio) { while (inc >= ratio) { *outv++ = *inv; *outv++ = *inv; inv += ratio; inc -= ratio; } } static void downsample_stereo2mono(int16_t *outv, const int16_t *inv, size_t inc, unsigned ratio) { ratio *= 2; while (inc >= ratio) { *outv++ = inv[0]/2 + inv[1]/2; inv += ratio; inc -= ratio; } } static void downsample_stereo2stereo(int16_t *outv, const int16_t *inv, size_t inc, unsigned ratio) { ratio *= 2; while (inc >= ratio) { *outv++ = inv[0]; *outv++ = inv[1]; inv += ratio; inc -= ratio; } } /** * Initialize a resampler object * * @param rs Resampler to initialize */ void auresamp_init(struct auresamp *rs) { if (!rs) return; memset(rs, 0, sizeof(*rs)); fir_reset(&rs->fir); } /** * Configure a resampler object * * @note The sample rate ratio must be an integer * * @param rs Resampler * @param irate Input sample rate * @param ich Input channel count * @param orate Output sample rate * @param och Output channel count * * @return 0 if success, otherwise error code */ int auresamp_setup(struct auresamp *rs, uint32_t irate, unsigned ich, uint32_t orate, unsigned och) { if (!rs || !irate || !ich || !orate || !och) return EINVAL; if (orate == irate && och == ich) { auresamp_init(rs); return 0; } if (orate >= irate) { if (orate % irate) return ENOTSUP; if (ich == 1 && och == 1) rs->resample = upsample_mono2mono; else if (ich == 1 && och == 2) rs->resample = upsample_mono2stereo; else if (ich == 2 && och == 1) rs->resample = upsample_stereo2mono; else if (ich == 2 && och == 2) rs->resample = upsample_stereo2stereo; else return ENOTSUP; if (!rs->up || orate != rs->orate || och != rs->och) fir_reset(&rs->fir); rs->ratio = orate / irate; rs->up = true; if (orate == irate) { rs->tapv = NULL; rs->tapc = 0; } else if (orate == 48000 && irate == 16000) { rs->tapv = fir_48_8; rs->tapc = RE_ARRAY_SIZE(fir_48_8); } else if ((orate == 16000 && irate == 8000) || (orate == 32000 && irate == 16000)) { rs->tapv = fir_16_4; rs->tapc = RE_ARRAY_SIZE(fir_16_4); } else { rs->tapv = fir_48_4; rs->tapc = RE_ARRAY_SIZE(fir_48_4); } } else { if (irate % orate) return ENOTSUP; if (ich == 1 && och == 1) rs->resample = downsample_mono2mono; else if (ich == 1 && och == 2) rs->resample = downsample_mono2stereo; else if (ich == 2 && och == 1) rs->resample = downsample_stereo2mono; else if (ich == 2 && och == 2) rs->resample = downsample_stereo2stereo; else return ENOTSUP; if (rs->up || irate != rs->irate || ich != rs->ich) fir_reset(&rs->fir); rs->ratio = irate / orate; rs->up = false; if (irate == 48000 && orate == 16000) { rs->tapv = fir_48_8; rs->tapc = RE_ARRAY_SIZE(fir_48_8); } else if ((irate == 16000 && orate == 8000) || (irate == 32000 && orate == 16000)) { rs->tapv = fir_16_4; rs->tapc = RE_ARRAY_SIZE(fir_16_4); } else { rs->tapv = fir_48_4; rs->tapc = RE_ARRAY_SIZE(fir_48_4); } } rs->orate = orate; rs->och = och; rs->irate = irate; rs->ich = ich; return 0; } /** * Resample * * @note When downsampling, the input count must be divisible by rate ratio * * @param rs Resampler * @param outv Output samples * @param outc Output sample count (in/out) * @param inv Input samples * @param inc Input sample count * * @return 0 if success, otherwise error code */ int auresamp(struct auresamp *rs, int16_t *outv, size_t *outc, const int16_t *inv, size_t inc) { size_t incc, outcc; if (!rs || !rs->resample || !outv || !outc || !inv) return EINVAL; incc = inc / rs->ich; if (rs->up) { outcc = incc * rs->ratio; if (*outc < outcc * rs->och) return ENOMEM; rs->resample(outv, inv, inc, rs->ratio); *outc = outcc * rs->och; if (rs->tapv) fir_filter(&rs->fir, outv, outv, *outc, rs->och, rs->tapv, rs->tapc); } else { outcc = incc / rs->ratio; if (*outc < outcc * rs->och || *outc < inc) return ENOMEM; fir_filter(&rs->fir, outv, inv, inc, rs->ich, rs->tapv, rs->tapc); rs->resample(outv, outv, inc, rs->ratio); *outc = outcc * rs->och; } return 0; } ================================================ FILE: rem/autone/tone.c ================================================ /** * @file tone.c Audio Tones * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #define SCALE (32767) #define DTMF_AMP (5) #if !defined (M_PI) #define M_PI 3.14159265358979323846264338327 #endif static inline uint32_t digit2lo(int digit) { switch (digit) { case '1': case '2': case '3': case 'A': return 697; case '4': case '5': case '6': case 'B': return 770; case '7': case '8': case '9': case 'C': return 852; case '*': case '0': case '#': case 'D': return 941; default: return 0; } } static inline uint32_t digit2hi(int digit) { switch (digit) { case '1': case '4': case '7': case '*': return 1209; case '2': case '5': case '8': case '0': return 1336; case '3': case '6': case '9': case '#': return 1477; case 'A': case 'B': case 'C': case 'D': return 1633; default: return 0; } } /** * Generate a dual-tone sine wave into a PCM buffer * * @param mb Buffer for PCM samples * @param srate Sample rate in [Hz] * @param f1 Frequency number one * @param l1 Level of f1 from 0-100 * @param f2 Frequency number two * @param l2 Level of f2 from 0-100 * * @return 0 for success, otherwise error code */ int autone_sine(struct mbuf *mb, uint32_t srate, uint32_t f1, int l1, uint32_t f2, int l2) { double d1, d2; uint32_t i; int err = 0; if (!mb || !srate) return EINVAL; d1 = 1.0f * f1 / srate; d2 = 1.0f * f2 / srate; for (i=0; i #include #include #include #include #include #include #define AVC_CONFIG_VERSION 1 #define SPS_MASK 0xe0 int avc_config_encode(struct mbuf *mb, uint8_t profile_ind, uint8_t profile_compat, uint8_t level_ind, uint16_t sps_length, const uint8_t *sps, uint16_t pps_length, const uint8_t *pps) { int err; if (!mb || !sps || !pps) return EINVAL; err = mbuf_write_u8(mb, AVC_CONFIG_VERSION); err |= mbuf_write_u8(mb, profile_ind); err |= mbuf_write_u8(mb, profile_compat); err |= mbuf_write_u8(mb, level_ind); err |= mbuf_write_u8(mb, 0xfc | (4-1)); /* SPS */ err |= mbuf_write_u8(mb, SPS_MASK | 1); err |= mbuf_write_u16(mb, htons(sps_length)); err |= mbuf_write_mem(mb, sps, sps_length); /* PPS */ err |= mbuf_write_u8(mb, 1); err |= mbuf_write_u16(mb, htons(pps_length)); err |= mbuf_write_mem(mb, pps, pps_length); return err; } int avc_config_decode(struct avc_config *conf, struct mbuf *mb) { uint8_t version, length_size, count; if (!conf || !mb) return EINVAL; if (mbuf_get_left(mb) < 5) return EBADMSG; version = mbuf_read_u8(mb); conf->profile_ind = mbuf_read_u8(mb); conf->profile_compat = mbuf_read_u8(mb); conf->level_ind = mbuf_read_u8(mb); length_size = mbuf_read_u8(mb) & 0x03; if (version != AVC_CONFIG_VERSION || length_size != 3) return EPROTO; /* SPS */ if (mbuf_get_left(mb) < 3) return EBADMSG; count = mbuf_read_u8(mb) & 0x1f; conf->sps_len = ntohs(mbuf_read_u16(mb)); if (count != 1 || conf->sps_len > sizeof(conf->sps)) return EOVERFLOW; if (mbuf_get_left(mb) < conf->sps_len) return EBADMSG; int err = mbuf_read_mem(mb, conf->sps, conf->sps_len); if (err) return err; /* PPS */ if (mbuf_get_left(mb) < 3) return EBADMSG; count = mbuf_read_u8(mb); conf->pps_len = ntohs(mbuf_read_u16(mb)); if (count != 1 || conf->pps_len > sizeof(conf->pps)) return EOVERFLOW; if (mbuf_get_left(mb) < conf->pps_len) return EBADMSG; err = mbuf_read_mem(mb, conf->pps, conf->pps_len); if (err) return err; return 0; } ================================================ FILE: rem/dtmf/dec.c ================================================ /** * @file dtmf/dec.c DTMF Decoder * * Copyright (C) 2010 Creytiv.com */ #include #include #include #define BLOCK_SIZE 102 /* At 8kHz sample rate */ #define THRESHOLD 16439.10631 /* -42dBm0 / bsize^2 */ #define NORMAL_TWIST 6.309573 /* 8dB */ #define REVERSE_TWIST 2.511886 /* 4dB */ #define RELATIVE_KEY 6.309573 /* 8dB */ #define RELATIVE_SUM 0.822243 /* -0.85dB */ static const double fx[4] = { 1209.0, 1336.0, 1477.0, 1633.0 }; static const double fy[4] = { 697.0, 770.0, 852.0, 941.0 }; static const char keyv[4][4] = {{'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'}}; struct dtmf_dec { struct goertzel gx[4], gy[4]; dtmf_dec_h *dech; void *arg; double threshold; double energy; double efac; unsigned bsize; unsigned bidx; char digit, digit1; }; static char decode_digit(struct dtmf_dec *dec) { unsigned i, x = 0, y = 0; double ex[4], ey[4]; for (i=0; i<4; i++) { ex[i] = goertzel_result(&dec->gx[i]); ey[i] = goertzel_result(&dec->gy[i]); if (ex[i] > ex[x]) x = i; if (ey[i] > ey[y]) y = i; } if (ex[x] < dec->threshold || ey[y] < dec->threshold) return 0; if (ex[x] > ey[y] * NORMAL_TWIST || ey[y] > ex[x] * REVERSE_TWIST) return 0; for (i=0; i<4; i++) { if ((i != x && ex[i] * RELATIVE_KEY > ex[x]) || (i != y && ey[i] * RELATIVE_KEY > ey[y])) return 0; } if ((ex[x] + ey[y]) < dec->efac * dec->energy) return 0; return keyv[y][x]; } /** * Allocate a DTMF decoder instance * * @param decp Pointer to allocated decoder * @param srate Sample rate * @param ch Number of channels * @param dech Decode handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dtmf_dec_alloc(struct dtmf_dec **decp, unsigned srate, unsigned ch, dtmf_dec_h *dech, void *arg) { struct dtmf_dec *dec; if (!decp || !dech || !srate || !ch) return EINVAL; dec = mem_zalloc(sizeof(*dec), NULL); if (!dec) return ENOMEM; dtmf_dec_reset(dec, srate, ch); dec->dech = dech; dec->arg = arg; *decp = dec; return 0; } /** * Reset and configure DTMF decoder state * * @param dec DTMF decoder * @param srate Sample rate * @param ch Number of channels */ void dtmf_dec_reset(struct dtmf_dec *dec, unsigned srate, unsigned ch) { unsigned i; if (!dec || !srate || !ch) return; srate *= ch; for (i=0; i<4; i++) { goertzel_init(&dec->gx[i], fx[i], srate); goertzel_init(&dec->gy[i], fy[i], srate); } dec->bsize = (BLOCK_SIZE * srate) / 8000; dec->threshold = THRESHOLD * dec->bsize * dec->bsize; dec->efac = RELATIVE_SUM * dec->bsize; dec->energy = 0.0; dec->bidx = 0; dec->digit = 0; dec->digit1 = 0; } /** * Decode DTMF from input audio samples * * @param dec DTMF decoder * @param sampv Buffer with audio samples * @param sampc Number of samples */ void dtmf_dec_probe(struct dtmf_dec *dec, const int16_t *sampv, size_t sampc) { size_t i; if (!dec || !sampv) return; for (i=0; igx[j], sampv[i]); goertzel_update(&dec->gy[j], sampv[i]); } dec->energy += sampv[i] * sampv[i]; if (++dec->bidx < dec->bsize) continue; digit0 = decode_digit(dec); if (digit0 != dec->digit && dec->digit1 != dec->digit) { dec->digit = digit0; if (digit0 != dec->digit1) dec->digit = 0; if (dec->digit) dec->dech(dec->digit, dec->arg); } dec->digit1 = digit0; dec->energy = 0.0; dec->bidx = 0; } } ================================================ FILE: rem/fir/fir.c ================================================ /** * @file fir.c FIR -- Finite Impulse Response * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Reset the FIR-filter * * @param fir FIR-filter state */ void fir_reset(struct fir *fir) { if (!fir) return; memset(fir, 0, sizeof(*fir)); } /** * Process samples with the FIR filter * * @note product of channel and tap-count must be power of two * * @param fir FIR filter * @param outv Output samples * @param inv Input samples * @param inc Number of samples * @param ch Number of channels * @param tapv Filter taps * @param tapc Number of taps */ void fir_filter(struct fir *fir, int16_t *outv, const int16_t *inv, size_t inc, unsigned ch, const int16_t *tapv, size_t tapc) { const unsigned hmask = (ch * (unsigned)tapc) - 1; if (!fir || !outv || !inv || !ch || !tapv || !tapc) return; if (hmask >= RE_ARRAY_SIZE(fir->history) || hmask & (hmask+1)) return; while (inc--) { int64_t acc = 0; unsigned i, j; fir->history[fir->index & hmask] = *inv++; for (i=0, j=fir->index++; ihistory[j & hmask] * tapv[i]; if (acc > 0x3fffffff) acc = 0x3fffffff; else if (acc < -0x40000000) acc = -0x40000000; *outv++ = (int16_t)(acc>>15); } } ================================================ FILE: rem/g711/g711.c ================================================ /** * @file g711.c G.711 codec * * Copyright (C) 2010 Creytiv.com */ #include #include const uint8_t g711_l2u[4096] = { 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0xef, 0xee, 0xee, 0xed, 0xed, 0xec, 0xec, 0xeb, 0xeb, 0xea, 0xea, 0xe9, 0xe9, 0xe8, 0xe8, 0xe7, 0xe7, 0xe6, 0xe6, 0xe5, 0xe5, 0xe4, 0xe4, 0xe3, 0xe3, 0xe2, 0xe2, 0xe1, 0xe1, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }; const uint8_t g711_l2A[2048] = { 0xd5, 0xd4, 0xd7, 0xd6, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde, 0xd9, 0xd8, 0xdb, 0xda, 0xc5, 0xc4, 0xc7, 0xc6, 0xc1, 0xc0, 0xc3, 0xc2, 0xcd, 0xcc, 0xcf, 0xce, 0xc9, 0xc8, 0xcb, 0xca, 0xf5, 0xf5, 0xf4, 0xf4, 0xf7, 0xf7, 0xf6, 0xf6, 0xf1, 0xf1, 0xf0, 0xf0, 0xf3, 0xf3, 0xf2, 0xf2, 0xfd, 0xfd, 0xfc, 0xfc, 0xff, 0xff, 0xfe, 0xfe, 0xf9, 0xf9, 0xf8, 0xf8, 0xfb, 0xfb, 0xfa, 0xfa, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, }; const int16_t g711_u2l[256] = { -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876, -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372, -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120, -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, -2, 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876, 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372, 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 2, }; const int16_t g711_A2l[256] = { -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848, }; ================================================ FILE: rem/goertzel/goertzel.c ================================================ /** * @file goertzel.c Goertzel algorithm * * Copyright (C) 2010 Creytiv.com */ #include #include #include #define PI 3.14159265358979323846264338327 /** * Initialize goertzel state * * @param g Goertzel state * @param freq Target frequency * @param srate Sample rate */ void goertzel_init(struct goertzel *g, double freq, unsigned srate) { g->q1 = 0.0; g->q2 = 0.0; g->coef = 2.0 * cos(2.0 * PI * (freq/(double)srate)); } /** * Reset goertzel state * * @param g Goertzel state */ void goertzel_reset(struct goertzel *g) { g->q1 = 0.0; g->q2 = 0.0; } /** * Calculate result and reset state * * @param g Goertzel state * * @return Result value */ double goertzel_result(struct goertzel *g) { double res; goertzel_update(g, 0); res = g->q1*g->q1 + g->q2*g->q2 - g->q1*g->q2*g->coef; goertzel_reset(g); return res * 2.0; } ================================================ FILE: rem/vid/draw.c ================================================ /** * @file draw.c Video Frame primitive drawing routines * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Draw a pixel to a video frame * * @param f Video frame * @param x Pixel X-position * @param y Pixel Y-position * @param r Red color component * @param g Green color component * @param b Blue color component */ void vidframe_draw_point(struct vidframe *f, unsigned x, unsigned y, uint8_t r, uint8_t g, uint8_t b) { uint8_t *yp, *up, *vp; uint32_t *p; size_t uv_offset; if (!f) return; if (x >= f->size.w || y >= f->size.h) return; switch (f->fmt) { case VID_FMT_YUV420P: yp = f->data[0] + f->linesize[0] * y + x; up = f->data[1] + f->linesize[1] * (y/2) + x/2; vp = f->data[2] + f->linesize[2] * (y/2) + x/2; yp[0] = rgb2y(r, g, b); up[0] = rgb2u(r, g, b); vp[0] = rgb2v(r, g, b); break; case VID_FMT_YUYV422: uv_offset = (f->linesize[0] * y + x * 2) & ~3; yp = f->data[0] + uv_offset; yp[0] = rgb2y(r, g, b); yp[1] = rgb2u(r, g, b); yp[2] = rgb2y(r, g, b); yp[3] = rgb2v(r, g, b); break; case VID_FMT_YUV444P: yp = f->data[0] + f->linesize[0] * y + x; up = f->data[1] + f->linesize[1] * y + x; vp = f->data[2] + f->linesize[2] * y + x; yp[0] = rgb2y(r, g, b); up[0] = rgb2u(r, g, b); vp[0] = rgb2v(r, g, b); break; case VID_FMT_RGB32: p = (void *)(f->data[0] + f->linesize[0] * y + x*4); *p = (uint32_t)r << 16 | (uint32_t)g << 8 | b; break; case VID_FMT_NV12: uv_offset = (f->linesize[1] * (y/2) + x) & ~1; yp = f->data[0] + f->linesize[0] * y + x; up = f->data[1] + uv_offset; vp = f->data[1] + uv_offset + 1; yp[0] = rgb2y(r, g, b); up[0] = rgb2u(r, g, b); vp[0] = rgb2v(r, g, b); break; case VID_FMT_NV21: uv_offset = (f->linesize[1] * (y/2) + x) & ~1; yp = f->data[0] + f->linesize[0] * y + x; up = f->data[1] + uv_offset + 1; vp = f->data[1] + uv_offset; yp[0] = rgb2y(r, g, b); up[0] = rgb2u(r, g, b); vp[0] = rgb2v(r, g, b); break; case VID_FMT_YUV422P: yp = f->data[0] + f->linesize[0] * y + x; up = f->data[1] + f->linesize[1] * y + x/2; vp = f->data[2] + f->linesize[2] * y + x/2; yp[0] = rgb2y(r, g, b); up[0] = rgb2u(r, g, b); vp[0] = rgb2v(r, g, b); break; default: (void)re_fprintf(stderr, "vidframe_draw_point:" " unsupported format %s\n", vidfmt_name(f->fmt)); break; } } /** * Draw a horizontal line * * @param f Video frame * @param x0 Origin X-position * @param y0 Origin Y-position * @param w Line width * @param r Red color component * @param g Green color component * @param b Blue color component */ void vidframe_draw_hline(struct vidframe *f, unsigned x0, unsigned y0, unsigned w, uint8_t r, uint8_t g, uint8_t b) { uint8_t y, u, v; uint8_t *p; size_t offset; if (!f) return; if (x0 >= f->size.w || y0 >= f->size.h) return; w = min(w, f->size.w-x0); y = rgb2y(r, g, b); u = rgb2u(r, g, b); v = rgb2v(r, g, b); switch (f->fmt) { case VID_FMT_YUV420P: memset(f->data[0] + y0 *f->linesize[0] + x0, y, w); memset(f->data[1] + (y0/2)*f->linesize[1] + x0/2, u, w/2); memset(f->data[2] + (y0/2)*f->linesize[2] + x0/2, v, w/2); break; case VID_FMT_YUV444P: memset(f->data[0] + y0*f->linesize[0] + x0, y, w); memset(f->data[1] + y0*f->linesize[1] + x0, u, w); memset(f->data[2] + y0*f->linesize[2] + x0, v, w); break; case VID_FMT_YUYV422: offset = (y0*f->linesize[0] + x0) & ~3; p = f->data[0] + offset; for (unsigned x=0; xlinesize[1] * (y0/2) + x0) & ~1; p = f->data[1] + offset; memset(f->data[0] + y0 *f->linesize[0] + x0, y, w); for (unsigned x=0; xdata[0] + y0*f->linesize[0] + x0, y, w); memset(f->data[1] + y0*f->linesize[1] + x0, u, w); memset(f->data[2] + y0*f->linesize[2] + x0, v, w); break; default: (void)re_fprintf(stderr, "vidframe_draw_hline:" " unsupported format %s\n", vidfmt_name(f->fmt)); break; } } /** * Draw a vertical line * * @param f Video frame * @param x0 Origin X-position * @param y0 Origin Y-position * @param h Line height * @param r Red color component * @param g Green color component * @param b Blue color component */ void vidframe_draw_vline(struct vidframe *f, unsigned x0, unsigned y0, unsigned h, uint8_t r, uint8_t g, uint8_t b) { if (!f) return; while (h--) { vidframe_draw_point(f, x0, y0++, r, g, b); } } /** * Draw a rectangle * * @param f Video frame * @param x0 Origin X-position * @param y0 Origin Y-position * @param w Rectangle width * @param h Rectangle height * @param r Red color component * @param g Green color component * @param b Blue color component */ void vidframe_draw_rect(struct vidframe *f, unsigned x0, unsigned y0, unsigned w, unsigned h, uint8_t r, uint8_t g, uint8_t b) { if (!f) return; vidframe_draw_hline(f, x0, y0, w, r, g, b); vidframe_draw_hline(f, x0, y0+h-1, w, r, g, b); vidframe_draw_vline(f, x0, y0, h, r, g, b); vidframe_draw_vline(f, x0+w-1, y0, h, r, g, b); } ================================================ FILE: rem/vid/fmt.c ================================================ /** * @file vid/fmt.c Video Formats * * Copyright (C) 2010 Creytiv.com */ #include #include /** Video format description table */ const struct vidfmt_desc vidfmt_descv[VID_FMT_N] = { {"yuv420p", 3, 3, { {0, 1}, {1, 1}, {2, 1}, {0, 0} } }, {"yuyv422", 1, 3, { {0, 2}, {0, 4}, {0, 4}, {0, 0} } }, {"uyvy422", 1, 3, { {0, 2}, {0, 4}, {0, 4}, {0, 0} } }, {"rgb32", 1, 4, { {0, 4}, {0, 4}, {0, 4}, {0, 4} } }, {"argb", 1, 4, { {0, 4}, {0, 4}, {0, 4}, {0, 4} } }, {"rgb565", 1, 3, { {0, 2}, {0, 2}, {0, 2}, {0, 0} } }, {"nv12", 3, 2, { {0, 1}, {1, 2}, {1, 2}, {0, 0} } }, {"nv21", 3, 2, { {0, 1}, {1, 2}, {1, 2}, {0, 0} } }, {"yuv444p", 3, 3, { {0, 1}, {1, 1}, {2, 1}, {0, 0} } }, {"yuv422p", 3, 3, { {0, 1}, {1, 1}, {2, 1}, {0, 0} } }, }; /** * Get the name of a video format * * @param fmt Video format * * @return Name of the video format */ const char *vidfmt_name(enum vidfmt fmt) { if (fmt >= VID_FMT_N) return "???"; return vidfmt_descv[fmt].name; } ================================================ FILE: rem/vid/frame.c ================================================ /** * @file frame.c Video Frame * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Get video frame buffer size * * @param fmt Video pixel format * @param sz Size of video frame * * @return Number of bytes */ size_t vidframe_size(enum vidfmt fmt, const struct vidsz *sz) { if (!sz) return 0; switch (fmt) { case VID_FMT_YUV420P: return (size_t)sz->w * sz->h * 3 / 2; case VID_FMT_YUYV422: return (size_t)sz->w * sz->h * 2; case VID_FMT_UYVY422: return (size_t)sz->w * sz->h * 2; case VID_FMT_RGB32: return (size_t)sz->w * sz->h * 4; case VID_FMT_ARGB: return (size_t)sz->w * sz->h * 4; case VID_FMT_RGB565: return (size_t)sz->w * sz->h * 2; case VID_FMT_NV12: return (size_t)sz->w * sz->h * 3 / 2; case VID_FMT_NV21: return (size_t)sz->w * sz->h * 3 / 2; case VID_FMT_YUV444P: return (size_t)sz->w * sz->h * 3; case VID_FMT_YUV422P: return (size_t)sz->w * sz->h * 2; default: return 0; } } /** * Initialize a video frame * * @param vf Video frame * @param fmt Video pixel format * @param sz Size of video frame * @param data Pointer to video planes * @param linesize Pointer to linesizes */ void vidframe_init(struct vidframe *vf, enum vidfmt fmt, const struct vidsz *sz, void *data[4], unsigned linesize[4]) { int i; if (!vf || !sz || !data || !linesize) return; for (i=0; i<4; i++) { vf->data[i] = data[i]; vf->linesize[i] = linesize[i]; } vf->size = *sz; vf->fmt = fmt; vf->xoffs = 0; vf->yoffs = 0; } /** * Initialize a video frame from a buffer * * @param vf Video frame * @param fmt Video pixel format * @param sz Size of video frame * @param buf Frame buffer */ void vidframe_init_buf(struct vidframe *vf, enum vidfmt fmt, const struct vidsz *sz, uint8_t *buf) { unsigned w, h; if (!vf || !sz || !buf) return; w = (sz->w + 1) >> 1; h = (sz->h + 1) >> 1; unsigned w2 = (sz->w + 1) >> 1; memset(vf->linesize, 0, sizeof(vf->linesize)); memset(vf->data, 0, sizeof(vf->data)); switch (fmt) { case VID_FMT_YUV420P: vf->linesize[0] = sz->w; vf->linesize[1] = w; vf->linesize[2] = w; vf->data[0] = buf; vf->data[1] = vf->data[0] + vf->linesize[0] * sz->h; vf->data[2] = vf->data[1] + vf->linesize[1] * h; break; case VID_FMT_YUYV422: case VID_FMT_UYVY422: vf->linesize[0] = sz->w * 2; vf->data[0] = buf; break; case VID_FMT_RGB32: case VID_FMT_ARGB: vf->linesize[0] = sz->w * 4; vf->data[0] = buf; break; case VID_FMT_RGB565: vf->linesize[0] = sz->w * 2; vf->data[0] = buf; break; case VID_FMT_NV12: case VID_FMT_NV21: vf->linesize[0] = sz->w; vf->linesize[1] = w*2; vf->data[0] = buf; vf->data[1] = vf->data[0] + vf->linesize[0] * sz->h; break; case VID_FMT_YUV444P: vf->linesize[0] = sz->w; vf->linesize[1] = sz->w; vf->linesize[2] = sz->w; vf->data[0] = buf; vf->data[1] = vf->data[0] + vf->linesize[0] * sz->h; vf->data[2] = vf->data[1] + vf->linesize[1] * sz->h; break; case VID_FMT_YUV422P: vf->linesize[0] = sz->w; vf->linesize[1] = w2; vf->linesize[2] = w2; vf->data[0] = buf; vf->data[1] = vf->data[0] + vf->linesize[0] * sz->h; vf->data[2] = vf->data[1] + vf->linesize[1] * sz->h; break; default: (void)re_printf("vidframe: no fmt %s\n", vidfmt_name(fmt)); return; } vf->size = *sz; vf->fmt = fmt; vf->xoffs = 0; vf->yoffs = 0; } /** * Allocate an empty video frame * * @param vfp Pointer to allocated video frame * @param fmt Video pixel format * @param sz Size of video frame * * @return 0 for success, otherwise error code */ int vidframe_alloc(struct vidframe **vfp, enum vidfmt fmt, const struct vidsz *sz) { struct vidframe *vf; if (!sz || !sz->w || !sz->h) return EINVAL; vf = mem_zalloc(sizeof(*vf) + vidframe_size(fmt, sz), NULL); if (!vf) return ENOMEM; vidframe_init_buf(vf, fmt, sz, (uint8_t *)(vf + 1)); *vfp = vf; return 0; } /** * Fill a video frame with a nice color * * @param vf Video frame * @param r Red color component * @param g Green color component * @param b Blue color component */ void vidframe_fill(struct vidframe *vf, uint32_t r, uint32_t g, uint32_t b) { uint8_t *p; size_t h; unsigned i, x; int u, v; if (!vf) return; switch (vf->fmt) { case VID_FMT_YUV420P: h = vf->size.h; memset(vf->data[0], rgb2y(r, g, b), h * vf->linesize[0]); memset(vf->data[1], rgb2u(r, g, b), h/2 * vf->linesize[1]); memset(vf->data[2], rgb2v(r, g, b), h/2 * vf->linesize[2]); break; case VID_FMT_YUV444P: h = vf->size.h; memset(vf->data[0], rgb2y(r, g, b), h * vf->linesize[0]); memset(vf->data[1], rgb2u(r, g, b), h * vf->linesize[1]); memset(vf->data[2], rgb2v(r, g, b), h * vf->linesize[2]); break; case VID_FMT_RGB32: p = vf->data[0]; for (i=0; ilinesize[0] * vf->size.h; i+=4) { *p++ = b; *p++ = g; *p++ = r; *p++ = 0; } break; case VID_FMT_NV12: case VID_FMT_NV21: h = vf->size.h; if (vf->fmt == VID_FMT_NV12) { u = rgb2u(r, g, b); v = rgb2v(r, g, b); } else { v = rgb2u(r, g, b); u = rgb2v(r, g, b); } memset(vf->data[0], rgb2y(r, g, b), h * vf->linesize[0]); p = vf->data[1]; for (h=0; hsize.h; h+=2) { for (x=0; xsize.w; x+=2) { p[x ] = u; p[x+1] = v; } p += vf->linesize[1]; } break; case VID_FMT_YUV422P: h = vf->size.h; memset(vf->data[0], rgb2y(r, g, b), h * vf->linesize[0]); memset(vf->data[1], rgb2u(r, g, b), h * vf->linesize[1]); memset(vf->data[2], rgb2v(r, g, b), h * vf->linesize[2]); break; default: (void)re_printf("vidfill: no fmt %s\n", vidfmt_name(vf->fmt)); break; } } /** * Copy content between to equally sized video frames of same pixel format * * @param dst Destination frame * @param src Source frame */ void vidframe_copy(struct vidframe *dst, const struct vidframe *src) { const uint8_t *ds0, *ds1, *ds2; unsigned lsd, lss, w, h, y; unsigned lsd1, lss1; unsigned lsd2, lss2; uint8_t *dd0, *dd1, *dd2; if (!dst || !src) return; if (!vidsz_cmp(&dst->size, &src->size)) return; if (dst->fmt != src->fmt) return; switch (dst->fmt) { case VID_FMT_YUV420P: lsd = dst->linesize[0]; lss = src->linesize[0]; dd0 = dst->data[0]; dd1 = dst->data[1]; dd2 = dst->data[2]; ds0 = src->data[0]; ds1 = src->data[1]; ds2 = src->data[2]; w = dst->size.w & ~1; h = dst->size.h & ~1; for (y=0; ylinesize[0]; lss = src->linesize[0]; dd0 = dst->data[0]; dd1 = dst->data[1]; dd2 = dst->data[2]; ds0 = src->data[0]; ds1 = src->data[1]; ds2 = src->data[2]; w = dst->size.w; h = dst->size.h; for (y=0; ylinesize[0]; lss = src->linesize[0]; dd0 = dst->data[0]; dd1 = dst->data[1]; ds0 = src->data[0]; ds1 = src->data[1]; w = dst->size.w & ~1; h = dst->size.h & ~1; for (y=0; ylinesize[0]; lss = src->linesize[0]; lsd1 = dst->linesize[1]; lss1 = src->linesize[1]; lsd2 = dst->linesize[2]; lss2 = src->linesize[2]; dd0 = dst->data[0]; dd1 = dst->data[1]; dd2 = dst->data[2]; ds0 = src->data[0]; ds1 = src->data[1]; ds2 = src->data[2]; w = dst->size.w & ~1; h = dst->size.h & ~1; for (y=0; ylinesize[0]; lss = src->linesize[0]; dd0 = dst->data[0]; ds0 = src->data[0]; w = dst->size.w & ~1; h = dst->size.h & ~1; for (y=0; yfmt)); break; } } ================================================ FILE: rem/vidconv/vconv.c ================================================ /** * @file vconv.c Video Conversion * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #if 0 /* * The lookup tables are generated with the following code: */ #define P 14 #define COEF_RV ((int32_t) (1.370705f * (float)(1 << P))) #define COEF_GU ((int32_t) (-0.337633f * (float)(1 << P))) #define COEF_GV ((int32_t) (-0.698001f * (float)(1 << P))) #define COEF_BU ((int32_t) (1.732446f * (float)(1 << P))) #define ERV(a) (COEF_RV * ((a) - 128)) #define EGU(a) (COEF_GU * ((a) - 128)) #define EGV(a) (COEF_GV * ((a) - 128)) #define EBU(a) (COEF_BU * ((a) - 128)) int16_t CRV[256]; int16_t CGU[256]; int16_t CGV[256]; int16_t CBU[256]; static void init_table(void) { int i; for (i = 0; i < 256; ++i) { CRV[i] = ERV(i) >> P; CGU[i] = EGU(i) >> P; CGV[i] = EGV(i) >> P; CBU[i] = EBU(i) >> P; } } #endif static const int16_t CRV[256] = { -176,-175,-173,-172,-170,-169,-168,-166,-165,-164,-162,-161, -159,-158,-157,-155,-154,-153,-151,-150,-149,-147,-146,-144, -143,-142,-140,-139,-138,-136,-135,-133,-132,-131,-129,-128, -127,-125,-124,-122,-121,-120,-118,-117,-116,-114,-113,-112, -110,-109,-107,-106,-105,-103,-102,-101, -99, -98, -96, -95, -94, -92, -91, -90, -88, -87, -85, -84, -83, -81, -80, -79, -77, -76, -75, -73, -72, -70, -69, -68, -66, -65, -64, -62, -61, -59, -58, -57, -55, -54, -53, -51, -50, -48, -47, -46, -44, -43, -42, -40, -39, -38, -36, -35, -33, -32, -31, -29, -28, -27, -25, -24, -22, -21, -20, -18, -17, -16, -14, -13, -11, -10, -9, -7, -6, -5, -3, -2, 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 15, 16, 17, 19, 20, 21, 23, 24, 26, 27, 28, 30, 31, 32, 34, 35, 37, 38, 39, 41, 42, 43, 45, 46, 47, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 63, 64, 65, 67, 68, 69, 71, 72, 74, 75, 76, 78, 79, 80, 82, 83, 84, 86, 87, 89, 90, 91, 93, 94, 95, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 111, 112, 113, 115, 116, 117, 119, 120, 121, 123, 124, 126, 127, 128, 130, 131, 132, 134, 135, 137, 138, 139, 141, 142, 143, 145, 146, 148, 149, 150, 152, 153, 154, 156, 157, 158, 160, 161, 163, 164, 165, 167, 168, 169, 171, 172, 174}; static const int16_t CGU[256] = { 43, 42, 42, 42, 41, 41, 41, 40, 40, 40, 39, 39, 39, 38, 38, 38, 37, 37, 37, 36, 36, 36, 35, 35, 35, 34, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27, 27, 26, 26, 25, 25, 25, 24, 24, 24, 23, 23, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, -1, -1, -2, -2, -2, -3, -3, -3, -4, -4, -4, -5, -5, -5, -6, -6, -6, -7, -7, -7, -8, -8, -8, -9, -9, -9, -10, -10, -10, -11, -11, -11, -12, -12, -12, -13, -13, -13, -14, -14, -14, -15, -15, -15, -16, -16, -16, -17, -17, -17, -18, -18, -18, -19, -19, -19, -20, -20, -20, -21, -21, -21, -22, -22, -22, -23, -23, -23, -24, -24, -24, -25, -25, -25, -26, -26, -26, -27, -27, -28, -28, -28, -29, -29, -29, -30, -30, -30, -31, -31, -31, -32, -32, -32, -33, -33, -33, -34, -34, -34, -35, -35, -35, -36, -36, -36, -37, -37, -37, -38, -38, -38, -39, -39, -39, -40, -40, -40, -41, -41, -41, -42, -42, -42, -43, -43, -43}; static const int16_t CGV[256] = { 89, 88, 87, 87, 86, 85, 85, 84, 83, 83, 82, 81, 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 73, 73, 72, 71, 71, 70, 69, 69, 68, 67, 67, 66, 65, 64, 64, 63, 62, 62, 61, 60, 60, 59, 58, 57, 57, 56, 55, 55, 54, 53, 53, 52, 51, 50, 50, 49, 48, 48, 47, 46, 46, 45, 44, 43, 43, 42, 41, 41, 40, 39, 39, 38, 37, 36, 36, 35, 34, 34, 33, 32, 32, 31, 30, 30, 29, 28, 27, 27, 26, 25, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 18, 17, 16, 16, 15, 14, 13, 13, 12, 11, 11, 10, 9, 9, 8, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 0, 0, -1, -2, -3, -3, -4, -5, -5, -6, -7, -7, -8, -9, -10, -10, -11, -12, -12, -13, -14, -14, -15, -16, -17, -17, -18, -19, -19, -20, -21, -21, -22, -23, -24, -24, -25, -26, -26, -27, -28, -28, -29, -30, -31, -31, -32, -33, -33, -34, -35, -35, -36, -37, -37, -38, -39, -40, -40, -41, -42, -42, -43, -44, -44, -45, -46, -47, -47, -48, -49, -49, -50, -51, -51, -52, -53, -54, -54, -55, -56, -56, -57, -58, -58, -59, -60, -61, -61, -62, -63, -63, -64, -65, -65, -66, -67, -68, -68, -69, -70, -70, -71, -72, -72, -73, -74, -74, -75, -76, -77, -77, -78, -79, -79, -80, -81, -81, -82, -83, -84, -84, -85, -86, -86, -87, -88, -88, -89}; static const int16_t CBU[256] = { -222,-221,-219,-217,-215,-214,-212,-210,-208,-207,-205,-203, -201,-200,-198,-196,-195,-193,-191,-189,-188,-186,-184,-182, -181,-179,-177,-175,-174,-172,-170,-169,-167,-165,-163,-162, -160,-158,-156,-155,-153,-151,-149,-148,-146,-144,-143,-141, -139,-137,-136,-134,-132,-130,-129,-127,-125,-124,-122,-120, -118,-117,-115,-113,-111,-110,-108,-106,-104,-103,-101, -99, -98, -96, -94, -92, -91, -89, -87, -85, -84, -82, -80, -78, -77, -75, -73, -72, -70, -68, -66, -65, -63, -61, -59, -58, -56, -54, -52, -51, -49, -47, -46, -44, -42, -40, -39, -37, -35, -33, -32, -30, -28, -26, -25, -23, -21, -20, -18, -16, -14, -13, -11, -9, -7, -6, -4, -2, 0, 1, 3, 5, 6, 8, 10, 12, 13, 15, 17, 19, 20, 22, 24, 25, 27, 29, 31, 32, 34, 36, 38, 39, 41, 43, 45, 46, 48, 50, 51, 53, 55, 57, 58, 60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77, 79, 81, 83, 84, 86, 88, 90, 91, 93, 95, 97, 98, 100, 102, 103, 105, 107, 109, 110, 112, 114, 116, 117, 119, 121, 123, 124, 126, 128, 129, 131, 133, 135, 136, 138, 140, 142, 143, 145, 147, 148, 150, 152, 154, 155, 157, 159, 161, 162, 164, 166, 168, 169, 171, 173, 174, 176, 178, 180, 181, 183, 185, 187, 188, 190, 192, 194, 195, 197, 199, 200, 202, 204, 206, 207, 209, 211, 213, 214, 216, 218, 220}; static inline void yuv2rgb(uint8_t *rgb, uint8_t y, int ruv, int guv, int buv) { *rgb++ = saturate_u8(y + buv); *rgb++ = saturate_u8(y + guv); *rgb++ = saturate_u8(y + ruv); *rgb = 0; } static inline void yuv2rgb565(uint8_t *rgb, uint8_t y, int ruv, int guv, int buv) { int r = saturate_u8(y + ruv) >> 3; int g = saturate_u8(y + guv) >> 2; int b = saturate_u8(y + buv) >> 3; rgb[1] = r << 3 | g >> 3; rgb[0] = g << 5 | b; } static inline void _yuv2rgb(uint8_t *rgb, uint8_t y, uint8_t u, uint8_t v) { int ruv, guv, buv; ruv = CRV[v]; guv = CGV[v] + CGU[u]; buv = CBU[u]; yuv2rgb(rgb, y, ruv, guv, buv); } typedef void(line_h)(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *sd0, const uint8_t *sd1, const uint8_t *sd2, unsigned lss); static void yuv420p_to_yuv420p(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs, xs2; unsigned id, is; for (x=0; x>1) + (ys>>1)*lss/2; dd1[id] = ds1[is]; dd2[id] = ds2[is]; } } static void yuyv422_to_yuv420p(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *sd0, const uint8_t *sd1, const uint8_t *sd2, unsigned lss) { unsigned x, xd, xs; unsigned id, is, is2; (void)sd1; (void)sd2; for (x=0; x> 16, x0 >> 8, x0); dd0[id+1] = rgb2y(x1 >> 16, x1 >> 8, x1); dd0[id + lsd] = rgb2y(x2 >> 16, x2 >> 8, x2); dd0[id+1 + lsd] = rgb2y(x3 >> 16, x3 >> 8, x3); id = xd/2 + yd*lsd/4; dd1[id] = rgb2u(x0 >> 16, x0 >> 8, x0); dd2[id] = rgb2v(x0 >> 16, x0 >> 8, x0); } } static void rgb32_to_yuv444p(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs; unsigned id; (void)ds1; (void)ds2; for (x=0; x> 16, x0 >> 8, x0); dd0[id + lsd] = rgb2y(x1 >> 16, x1 >> 8, x1); dd1[id] = rgb2u(x0 >> 16, x0 >> 8, x0); dd1[id + lsd] = rgb2u(x1 >> 16, x1 >> 8, x1); dd2[id] = rgb2v(x0 >> 16, x0 >> 8, x0); dd2[id + lsd] = rgb2v(x1 >> 16, x1 >> 8, x1); } } static void yuv420p_to_rgb32(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs, xs2; unsigned id, is; (void)dd1; (void)dd2; for (x=0; x>1) + (ys>>1)*lss/2; u = ds1[is]; v = ds2[is]; ruv = CRV[v]; guv = CGV[v] + CGU[u]; buv = CBU[u]; yuv2rgb(&dd0[id], ds0[xs + ys*lss], ruv, guv, buv); yuv2rgb(&dd0[id+4], ds0[xs2 + ys*lss], ruv, guv, buv); yuv2rgb(&dd0[id + lsd], ds0[xs + ys2*lss], ruv, guv, buv); yuv2rgb(&dd0[id+4 + lsd], ds0[xs2 + ys2*lss], ruv, guv, buv); } } static void yuv420p_to_rgb565(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs, xs2; unsigned id, is; (void)dd1; (void)dd2; for (x=0; x>1) + (ys>>1)*lss/2; u = ds1[is]; v = ds2[is]; ruv = CRV[v]; guv = CGV[v] + CGU[u]; buv = CBU[u]; yuv2rgb565(&dd0[id], ds0[xs + ys*lss], ruv, guv,buv); yuv2rgb565(&dd0[id+2], ds0[xs2 + ys*lss], ruv, guv,buv); yuv2rgb565(&dd0[id + lsd], ds0[xs + ys2*lss], ruv, guv,buv); yuv2rgb565(&dd0[id+2 + lsd], ds0[xs2 + ys2*lss], ruv, guv,buv); } } static void nv12_to_yuv420p(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs, xs2; unsigned id, is; (void)ds2; for (x=0; x>1) + (yd>>1)*lsd/2; is = xs/2 + ys*lss/4; dd1[id] = ds1[2*is]; dd2[id] = ds1[2*is+1]; } } static void yuv420p_to_nv12(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs, xs2; unsigned id, is; (void)dd2; for (x=0; x>1) + (ys>>1)*lss/2; dd1[2*id] = ds1[is]; dd1[2*id+1] = ds2[is]; } } static void nv21_to_yuv420p(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs, xs2; unsigned id, is; (void)ds2; for (x=0; x>1) + (ys>>1)*lss/2) & ~1; dd2[id] = ds1[2*is]; dd1[id] = ds1[2*is+1]; } } static void yuv444p_to_rgb32(unsigned xsoffs, unsigned xdoffs, unsigned width, double rw, unsigned yd, unsigned ys, unsigned ys2, uint8_t *dd0, uint8_t *dd1, uint8_t *dd2, unsigned lsd, const uint8_t *ds0, const uint8_t *ds1, const uint8_t *ds2, unsigned lss) { unsigned x, xd, xs; unsigned id; unsigned is1, is2; (void)dd1; (void)dd2; for (x=0; xfmt < MAX_SRC && dst->fmt < MAX_DST) { /* Lookup conversion function */ lineh = conv_table[src->fmt][dst->fmt]; } if (!lineh) { (void)re_printf("vidconv: no pixel converter found for" " %s -> %s\n", vidfmt_name(src->fmt), vidfmt_name(dst->fmt)); return; } if (r) { r->x &= ~1; r->y &= ~1; r->w &= ~1; r->h &= ~1; if ((r->x + r->w) > dst->size.w || (r->y + r->h) > dst->size.h) { (void)re_printf("vidconv: out of bounds (%u x %u)\n", dst->size.w, dst->size.h); return; } } else { rdst.x = rdst.y = 0; rdst.w = dst->size.w & ~1; rdst.h = dst->size.h & ~1; r = &rdst; } rw = (double)src->size.w / (double)r->w; rh = (double)src->size.h / (double)r->h; lsd = dst->linesize[0]; lss = src->linesize[0]; dd0 = dst->data[0]; dd1 = dst->data[1]; dd2 = dst->data[2]; ds0 = src->data[0]; ds1 = src->data[1]; ds2 = src->data[2]; for (y=0; yh; y+=2) { yd = y + r->y; ys = (unsigned)((y + src->yoffs) * rh); ys2 = (unsigned)((y + src->yoffs + 1) * rh); lineh(src->xoffs, r->x, r->w, rw, yd, ys, ys2, dd0, dd1, dd2, lsd, ds0, ds1, ds2, lss); } } /** * Same as vidconv(), but maintain source aspect ratio within bounds of r * * @param dst Destination video frame * @param src Source video frame * @param r Drawing area in destination frame */ void vidconv_aspect(struct vidframe *dst, const struct vidframe *src, struct vidrect *r) { struct vidsz asz; double ar; ar = (double)src->size.w / (double)src->size.h; asz.w = r->w; asz.h = r->h; r->w = (unsigned)min((double)asz.w, (double)asz.h * ar); r->h = (unsigned)min((double)asz.h, (double)asz.w / ar); r->x = r->x + (asz.w - r->w) / 2; r->y = r->y + (asz.h - r->h) / 2; vidconv(dst, src, r); } /** * Same as vidconv(), but maintain source min. center within bounds of r * * @param dst Destination video frame * @param src Source video frame * @param r Drawing area in destination frame */ void vidconv_center(struct vidframe *dst, const struct vidframe *src, struct vidrect *r) { struct vidframe sc = *src; if (src->size.w >= src->size.h) { double rh = (double)src->size.h / r->h; sc.size.w = (unsigned)min((double)src->size.w, (double)r->w * rh); sc.xoffs = ((unsigned)(src->size.w / rh) - r->w) / 2; if (sc.xoffs >= src->size.w) sc.xoffs = 0; } else { double rw = (double)src->size.w / r->w; sc.size.h = (unsigned)min((double)src->size.h, (double)r->h * rw); sc.yoffs = ((unsigned)(src->size.h / rw) - r->h) / 2; if (sc.yoffs >= src->size.h) sc.yoffs = 0; } vidconv(dst, &sc, r); } ================================================ FILE: rem/vidmix/vidmix.c ================================================ /** * @file vidmix.c Video Mixer * * Copyright (C) 2010 Creytiv.com */ #define _BSD_SOURCE 1 #define _DEFAULT_SOURCE 1 #ifdef HAVE_UNISTD_H #include #endif #define __USE_UNIX98 1 #include #include #include #include #include /* * Clock-rate for video timestamp */ #define VIDEO_TIMEBASE 1000000U struct vidmix { mtx_t rwlock; struct list srcl; bool initialized; uint32_t next_pidx; enum vidfmt fmt; }; struct vidmix_source { struct le le; uint32_t pidx; thrd_t thread; mtx_t *mutex; struct vidframe *frame_tx; struct vidframe *frame_rx; struct vidmix *mix; vidmix_frame_h *fh; void *arg; void *focus; bool content_hide; bool focus_full; unsigned fint; bool selfview; bool content; bool run; }; static inline void source_mix_full(struct vidframe *mframe, const struct vidframe *frame_src); static inline void clear_frame(struct vidframe *vf) { vidframe_fill(vf, 0, 0, 0); } static void destructor(void *arg) { struct vidmix *mix = arg; if (mix->initialized) mtx_destroy(&mix->rwlock); } static void source_destructor(void *arg) { struct vidmix_source *src = arg; vidmix_source_stop(src); if (src->le.list) { mtx_lock(&src->mix->rwlock); list_unlink(&src->le); mtx_unlock(&src->mix->rwlock); } mem_deref(src->frame_tx); mem_deref(src->frame_rx); mem_deref(src->mix); mem_deref(src->mutex); } static inline void source_mix(struct vidframe *mframe, const struct vidframe *frame_src, unsigned n, unsigned rows, unsigned idx, bool focus, bool focus_this, bool focus_full) { struct vidrect rect; if (!frame_src) return; if (focus) { const unsigned nmin = focus_full ? 12 : 6; n = max((n+1), nmin)/2; if (focus_this) { rect.w = mframe->size.w * (n-1) / n; rect.h = mframe->size.h * (n-1) / n; rect.x = 0; rect.y = 0; } else { rect.w = mframe->size.w / n; rect.h = mframe->size.h / n; if (idx < n) { rect.x = mframe->size.w - rect.w; rect.y = rect.h * idx; } else if (idx < (n*2 - 1)) { rect.x = rect.w * (n*2 - 2 - idx); rect.y = mframe->size.h - rect.h; } else { return; } } } else if (rows == 1) { source_mix_full(mframe, frame_src); return; } else if (n <= 3) { rect.w = mframe->size.w / n; rect.h = mframe->size.h; rect.x = (rect.w) * (idx % n); rect.y = 0; vidconv_center(mframe, frame_src, &rect); return; } else { rect.w = mframe->size.w / rows; rect.h = mframe->size.h / rows; rect.x = rect.w * (idx % rows); rect.y = rect.h * (idx / rows); } vidconv_aspect(mframe, frame_src, &rect); } static inline void source_mix_full(struct vidframe *mframe, const struct vidframe *frame_src) { if (!frame_src) return; if (vidsz_cmp(&mframe->size, &frame_src->size)) { vidframe_copy(mframe, frame_src); } else { struct vidrect rect; rect.w = mframe->size.w; rect.h = mframe->size.h; rect.x = 0; rect.y = 0; vidconv_aspect(mframe, frame_src, &rect); } } static inline unsigned calc_rows(unsigned n) { unsigned rows; for (rows=1;; rows++) if (n <= (rows * rows)) return rows; } static int vidmix_thread(void *arg) { struct vidmix_source *src = arg; struct vidmix *mix = src->mix; uint64_t ts = tmr_jiffies_usec(); mtx_lock(src->mutex); while (src->run) { unsigned n, rows, idx; struct le *le; uint64_t now; mtx_unlock(src->mutex); sys_usleep(4000); mtx_lock(src->mutex); now = tmr_jiffies_usec(); if (ts > now) continue; if (!src->frame_tx) { ts += src->fint; continue; } mtx_lock(&mix->rwlock); clear_frame(src->frame_tx); for (le=mix->srcl.head, n=0; le; le=le->next) { const struct vidmix_source *lsrc = le->data; if (lsrc == src && !src->selfview) continue; if (lsrc->content && src->content_hide) continue; if (lsrc == src->focus && src->focus_full) source_mix_full(src->frame_tx, lsrc->frame_rx); ++n; } rows = calc_rows(n); for (le=mix->srcl.head, idx=0; le; le=le->next) { const struct vidmix_source *lsrc = le->data; if (lsrc == src && !src->selfview) continue; if (lsrc->content && src->content_hide) continue; if (lsrc == src->focus && src->focus_full) continue; source_mix(src->frame_tx, lsrc->frame_rx, n, rows, idx, src->focus != NULL, src->focus == lsrc, src->focus_full); if (src->focus != lsrc) ++idx; } mtx_unlock(&mix->rwlock); src->fh(ts, src->frame_tx, src->arg); ts += src->fint; } mtx_unlock(src->mutex); return 0; } static int content_thread(void *arg) { struct vidmix_source *src = arg; struct vidmix *mix = src->mix; uint64_t ts = tmr_jiffies_usec(); mtx_lock(src->mutex); while (src->run) { struct le *le; uint64_t now; mtx_unlock(src->mutex); sys_usleep(4000); mtx_lock(src->mutex); now = tmr_jiffies_usec(); if (ts > now) continue; mtx_lock(&mix->rwlock); for (le=mix->srcl.head; le; le=le->next) { const struct vidmix_source *lsrc = le->data; if (!lsrc->content || !lsrc->frame_rx || lsrc == src) continue; src->fh(ts, lsrc->frame_rx, src->arg); break; } mtx_unlock(&mix->rwlock); ts += src->fint; } mtx_unlock(src->mutex); return 0; } /** * Allocate a new Video mixer * * @param mixp Pointer to allocated video mixer * * @return 0 for success, otherwise error code */ int vidmix_alloc(struct vidmix **mixp) { struct vidmix *mix; int err; if (!mixp) return EINVAL; mix = mem_zalloc(sizeof(*mix), destructor); if (!mix) return ENOMEM; err = mtx_init(&mix->rwlock, mtx_plain) != thrd_success; if (err) { err = ENOMEM; goto out; } mix->fmt = VID_FMT_YUV420P; mix->initialized = true; out: if (err) mem_deref(mix); else *mixp = mix; return err; } /** * Set video mixer pixel format * * @param mix Video mixer * @param fmt Pixel format */ void vidmix_set_fmt(struct vidmix *mix, enum vidfmt fmt) { if (!mix) return; mix->fmt = fmt; } /** * Allocate a video mixer source * * @param srcp Pointer to allocated video source * @param mix Video mixer * @param sz Size of output video frame (optional) * @param fps Output frame rate (frames per second) * @param content True if source is of type content * @param fh Mixer frame handler * @param arg Handler argument * * @return 0 for success, otherwise error code */ int vidmix_source_alloc(struct vidmix_source **srcp, struct vidmix *mix, const struct vidsz *sz, unsigned fps, bool content, vidmix_frame_h *fh, void *arg) { struct vidmix_source *src; int err; if (!srcp || !mix || !fps || !fh) return EINVAL; src = mem_zalloc(sizeof(*src), source_destructor); if (!src) return ENOMEM; src->mix = mem_ref(mix); src->fint = VIDEO_TIMEBASE/fps; src->content = content; src->fh = fh; src->arg = arg; src->pidx = ++mix->next_pidx; err = mutex_alloc(&src->mutex); if (err) goto out; if (sz) { err = vidframe_alloc(&src->frame_tx, mix->fmt, sz); if (err) goto out; clear_frame(src->frame_tx); } out: if (err) mem_deref(src); else *srcp = src; return err; } /** * Check if vidmix source is enabled * * @param src Video mixer source * * @return true if enabled, otherwise false */ bool vidmix_source_isenabled(const struct vidmix_source *src) { return src ? (src->le.list != NULL) : false; } /** * Check if vidmix source is running * * @param src Video mixer source * * @return true if running, otherwise false */ bool vidmix_source_isrunning(const struct vidmix_source *src) { if (!src) return false; mtx_lock(src->mutex); bool run = src->run; mtx_unlock(src->mutex); return run; } /** * Get vidmix source position index * * @param src Video mixer source * * @return position index */ uint32_t vidmix_source_get_pidx(const struct vidmix_source *src) { return src ? src->pidx : 0; } /** * Get focus source * * @param src Video mixer source * * @return pointer of focused source or NULL if focus is not set */ void *vidmix_source_get_focus(const struct vidmix_source *src) { return src ? src->focus : NULL; } static bool sort_src_handler(struct le *le1, struct le *le2, void *arg) { struct vidmix_source *src1 = le1->data; struct vidmix_source *src2 = le2->data; (void)arg; /* NOTE: important to use less than OR equal to, otherwise the list_sort function may be stuck in a loop */ return src1->pidx <= src2->pidx; } /** * Enable/disable vidmix source * * @param src Video mixer source * @param enable True to enable, false to disable */ void vidmix_source_enable(struct vidmix_source *src, bool enable) { if (!src) return; if (src->le.list && enable) return; if (!src->le.list && !enable) return; mtx_lock(&src->mix->rwlock); if (enable) { if (src->frame_rx) clear_frame(src->frame_rx); list_insert_sorted(&src->mix->srcl, sort_src_handler, NULL, &src->le, src); } else { list_unlink(&src->le); } mtx_unlock(&src->mix->rwlock); } /** * Start vidmix source thread * * @param src Video mixer source * * @return 0 for success, otherwise error code */ int vidmix_source_start(struct vidmix_source *src) { int err; if (!src) return EINVAL; mtx_lock(src->mutex); bool run = src->run; mtx_unlock(src->mutex); if (run) return EALREADY; mtx_lock(src->mutex); src->run = true; mtx_unlock(src->mutex); err = thread_create_name(&src->thread, "vidmix", src->content ? content_thread : vidmix_thread, src); if (err) { mtx_lock(src->mutex); src->run = false; mtx_unlock(src->mutex); } return err; } /** * Stop vidmix source thread * * @param src Video mixer source */ void vidmix_source_stop(struct vidmix_source *src) { if (!src) return; mtx_lock(src->mutex); bool run = src->run; mtx_unlock(src->mutex); if (run) { mtx_lock(src->mutex); src->run = false; mtx_unlock(src->mutex); thrd_join(src->thread, NULL); } } /** * Set video mixer output frame size * * @param src Video mixer source * @param sz Size of output video frame * * @return 0 for success, otherwise error code */ int vidmix_source_set_size(struct vidmix_source *src, const struct vidsz *sz) { struct vidframe *frame; int err; if (!src || !sz) return EINVAL; if (src->frame_tx && vidsz_cmp(&src->frame_tx->size, sz)) return 0; err = vidframe_alloc(&frame, src->mix->fmt, sz); if (err) return err; clear_frame(frame); mtx_lock(src->mutex); mem_deref(src->frame_tx); src->frame_tx = frame; mtx_unlock(src->mutex); return 0; } /** * Set video mixer output frame rate * * @param src Video mixer source * @param fps Output frame rate (frames per second) */ void vidmix_source_set_rate(struct vidmix_source *src, unsigned fps) { if (!src || !fps) return; mtx_lock(src->mutex); src->fint = VIDEO_TIMEBASE/fps; mtx_unlock(src->mutex); } /** * Set video mixer content hide * * @param src Video mixer source * @param hide True to hide content, false to show */ void vidmix_source_set_content_hide(struct vidmix_source *src, bool hide) { if (!src) return; mtx_lock(src->mutex); src->content_hide = hide; mtx_unlock(src->mutex); } /** * Toggle vidmix source selfview * * @param src Video mixer source */ void vidmix_source_toggle_selfview(struct vidmix_source *src) { if (!src) return; mtx_lock(src->mutex); src->selfview = !src->selfview; mtx_unlock(src->mutex); } /** * Set focus on selected participant source * * @param src Video mixer source * @param focus_src Video mixer source to focus, NULL to clear focus state * @param focus_full Full focus */ void vidmix_source_set_focus(struct vidmix_source *src, const struct vidmix_source *focus_src, bool focus_full) { if (!src) return; mtx_lock(src->mutex); src->focus_full = focus_full; src->focus = (void *)focus_src; mtx_unlock(src->mutex); } /** * Set focus on selected participant * * @param src Video mixer source * @param pidx Participant to focus, 0 to disable */ void vidmix_source_set_focus_idx(struct vidmix_source *src, uint32_t pidx) { bool focus_full = false; void *focus = NULL; if (!src) return; if (pidx > 0) { struct le *le; mtx_lock(&src->mix->rwlock); LIST_FOREACH(&src->mix->srcl, le) { const struct vidmix_source *lsrc = le->data; if (lsrc == src && !src->selfview) continue; if (lsrc->content && src->content_hide) continue; if (lsrc->pidx == pidx) { focus = (void *)lsrc; break; } } mtx_unlock(&src->mix->rwlock); } if (focus && focus == src->focus) focus_full = !src->focus_full; mtx_lock(src->mutex); src->focus_full = focus_full; src->focus = focus; mtx_unlock(src->mutex); } /** * Put a video frame into the video mixer * * @param src Video source * @param frame Video frame */ void vidmix_source_put(struct vidmix_source *src, const struct vidframe *frame) { if (!src || !frame || frame->fmt != src->mix->fmt) return; if (!src->frame_rx || !vidsz_cmp(&src->frame_rx->size, &frame->size)) { struct vidframe *frm; int err; err = vidframe_alloc(&frm, src->mix->fmt, &frame->size); if (err) return; mtx_lock(&src->mix->rwlock); mem_deref(src->frame_rx); src->frame_rx = frm; mtx_unlock(&src->mix->rwlock); } mtx_lock(&src->mix->rwlock); vidframe_copy(src->frame_rx, frame); mtx_unlock(&src->mix->rwlock); } ================================================ FILE: sonar-project.properties ================================================ sonar.projectKey=baresip_re sonar.organization=baresip sonar.cfamily.threads=2 ================================================ FILE: src/aes/apple/aes.c ================================================ /** * @file apple/aes.c AES using Apple CommonCrypto API * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include struct aes { CCCryptorRef cryptor; uint8_t key[64]; size_t key_bytes; }; static void destructor(void *arg) { struct aes *st = arg; if (st->cryptor) CCCryptorRelease(st->cryptor); } int aes_alloc(struct aes **stp, enum aes_mode mode, const uint8_t *key, size_t key_bits, const uint8_t *iv) { struct aes *st; size_t key_bytes = key_bits / 8; CCCryptorStatus status; int err = 0; if (!stp || !key) return EINVAL; if (mode != AES_MODE_CTR) return ENOTSUP; st = mem_zalloc(sizeof(*st), destructor); if (!st) return ENOMEM; if (key_bytes > sizeof(st->key)) { err = EINVAL; goto out; } st->key_bytes = key_bytes; memcpy(st->key, key, st->key_bytes); /* used for both encryption and decryption because CTR is symmetric */ status = CCCryptorCreateWithMode(kCCEncrypt, kCCModeCTR, kCCAlgorithmAES, ccNoPadding, iv, key, key_bytes, NULL, 0, 0, kCCModeOptionCTR_BE, &st->cryptor); if (status != kCCSuccess) { err = EPROTO; goto out; } out: if (err) mem_deref(st); else *stp = st; return err; } void aes_set_iv(struct aes *st, const uint8_t *iv) { CCCryptorStatus status; if (!st) return; /* we must reset the state when updating IV */ if (st->cryptor) { CCCryptorRelease(st->cryptor); st->cryptor = NULL; } status = CCCryptorCreateWithMode(kCCEncrypt, kCCModeCTR, kCCAlgorithmAES, ccNoPadding, iv, st->key, st->key_bytes, NULL, 0, 0, kCCModeOptionCTR_BE, &st->cryptor); if (status != kCCSuccess) { re_fprintf(stderr, "aes: CCCryptorCreateWithMode error (%d)\n", status); } } int aes_encr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len) { CCCryptorStatus status; size_t moved; if (!st || !out || !in) return EINVAL; status = CCCryptorUpdate(st->cryptor, in, len, out, len, &moved); if (status != kCCSuccess) { re_fprintf(stderr, "aes: CCCryptorUpdate error (%d)\n", status); return EPROTO; } return 0; } int aes_decr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len) { return aes_encr(st, out, in, len); } int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen) { (void)aes; (void)tag; (void)taglen; return ENOSYS; } int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen) { (void)aes; (void)tag; (void)taglen; return ENOSYS; } ================================================ FILE: src/aes/openssl/aes.c ================================================ /** * @file openssl/aes.c AES (Advanced Encryption Standard) using OpenSSL * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include struct aes { EVP_CIPHER_CTX *ctx; enum aes_mode mode; bool encr; }; static const EVP_CIPHER *aes_cipher(enum aes_mode mode, size_t key_bits) { if (mode == AES_MODE_CTR) { switch (key_bits) { case 128: return EVP_aes_128_ctr(); case 256: return EVP_aes_256_ctr(); default: return NULL; } } else if (mode == AES_MODE_GCM) { switch (key_bits) { case 128: return EVP_aes_128_gcm(); case 256: return EVP_aes_256_gcm(); default: return NULL; } } else { return NULL; } } static inline bool set_crypt_dir(struct aes *aes, bool encr) { if (aes->encr != encr) { /* update the encrypt/decrypt direction */ if (!EVP_CipherInit_ex(aes->ctx, NULL, NULL, NULL, NULL, encr)) { ERR_clear_error(); return false; } aes->encr = encr; } return true; } static void destructor(void *arg) { struct aes *st = arg; if (st->ctx) EVP_CIPHER_CTX_free(st->ctx); } int aes_alloc(struct aes **aesp, enum aes_mode mode, const uint8_t *key, size_t key_bits, const uint8_t *iv) { const EVP_CIPHER *cipher; struct aes *st; int err = 0, r; if (!aesp || !key) return EINVAL; cipher = aes_cipher(mode, key_bits); if (!cipher) return ENOTSUP; st = mem_zalloc(sizeof(*st), destructor); if (!st) return ENOMEM; st->mode = mode; st->encr = true; st->ctx = EVP_CIPHER_CTX_new(); if (!st->ctx) { ERR_clear_error(); err = ENOMEM; goto out; } r = EVP_EncryptInit_ex(st->ctx, cipher, NULL, key, iv); if (!r) { ERR_clear_error(); err = EPROTO; } out: if (err) mem_deref(st); else *aesp = st; return err; } void aes_set_iv(struct aes *aes, const uint8_t *iv) { int r; if (!aes || !iv) return; r = EVP_CipherInit_ex(aes->ctx, NULL, NULL, NULL, iv, -1); if (!r) ERR_clear_error(); } int aes_encr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len) { int c_len = (int)len; if (!aes || !in) return EINVAL; if (!set_crypt_dir(aes, true)) return EPROTO; if (!EVP_EncryptUpdate(aes->ctx, out, &c_len, in, (int)len)) { ERR_clear_error(); return EPROTO; } return 0; } int aes_decr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len) { int c_len = (int)len; if (!aes || !in) return EINVAL; if (!set_crypt_dir(aes, false)) return EPROTO; if (!EVP_DecryptUpdate(aes->ctx, out, &c_len, in, (int)len)) { ERR_clear_error(); return EPROTO; } return 0; } /** * Get the authentication tag for an AEAD cipher (e.g. GCM) * * @param aes AES Context * @param tag Authentication tag * @param taglen Length of Authentication tag * * @return 0 if success, otherwise errorcode */ int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen) { int tmplen; if (!aes || !tag || !taglen) return EINVAL; switch (aes->mode) { case AES_MODE_GCM: if (!EVP_EncryptFinal_ex(aes->ctx, NULL, &tmplen)) { ERR_clear_error(); return EPROTO; } if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_GET_TAG, (int)taglen, tag)) { ERR_clear_error(); return EPROTO; } return 0; default: return ENOTSUP; } } /** * Authenticate a decryption tag for an AEAD cipher (e.g. GCM) * * @param aes AES Context * @param tag Authentication tag * @param taglen Length of Authentication tag * * @return 0 if success, otherwise errorcode * * @retval EAUTH if authentication failed */ int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen) { int tmplen; if (!aes || !tag || !taglen) return EINVAL; switch (aes->mode) { case AES_MODE_GCM: if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_SET_TAG, (int)taglen, (void *)tag)) { ERR_clear_error(); return EPROTO; } if (EVP_DecryptFinal_ex(aes->ctx, NULL, &tmplen) <= 0) { ERR_clear_error(); return EAUTH; } return 0; default: return ENOTSUP; } } ================================================ FILE: src/aes/stub.c ================================================ /** * @file aes/stub.c AES stub * * Copyright (C) 2010 Creytiv.com */ #include #include int aes_alloc(struct aes **stp, enum aes_mode mode, const uint8_t *key, size_t key_bits, const uint8_t *iv) { (void)stp; (void)mode; (void)key; (void)key_bits; (void)iv; return ENOSYS; } void aes_set_iv(struct aes *aes, const uint8_t *iv) { (void)aes; (void)iv; } int aes_encr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len) { (void)st; (void)out; (void)in; (void)len; return ENOSYS; } int aes_decr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len) { (void)st; (void)out; (void)in; (void)len; return ENOSYS; } int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen) { (void)aes; (void)tag; (void)taglen; return ENOSYS; } int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen) { (void)aes; (void)tag; (void)taglen; return ENOSYS; } ================================================ FILE: src/async/async.c ================================================ /** * @file async.c Async API * * Copyright (C) 2022 Sebastian Reimers */ #include #include #include #include #include #include #include #define DEBUG_MODULE "async" #define DEBUG_LEVEL 5 #include struct async_work { struct le le; mtx_t *mtx; re_async_work_h *workh; re_async_h *cb; void *arg; int err; intptr_t id; }; struct re_async { thrd_t *thrd; uint16_t workers; volatile bool run; cnd_t wait; mtx_t mtx; struct list freel; struct list workl; struct list curl; struct tmr tmr; struct mqueue *mqueue; }; static int worker_thread(void *arg) { struct re_async *a = arg; struct le *le; struct async_work *work; for (;;) { mtx_lock(&a->mtx); if (!a->run) { mtx_unlock(&a->mtx); break; } if (list_isempty(&a->workl)) { cnd_wait(&a->wait, &a->mtx); if (list_isempty(&a->workl) || !a->run) { mtx_unlock(&a->mtx); continue; } } le = list_head(&a->workl); list_move(le, &a->curl); mtx_unlock(&a->mtx); work = le->data; mtx_lock(work->mtx); if (work->workh) { work->err = work->workh(work->arg); work->workh = NULL; } mtx_unlock(work->mtx); mtx_lock(&a->mtx); mqueue_push(a->mqueue, 0, work); mtx_unlock(&a->mtx); } return 0; } static void async_destructor(void *data) { struct re_async *async = data; tmr_cancel(&async->tmr); mtx_lock(&async->mtx); async->run = false; cnd_broadcast(&async->wait); mtx_unlock(&async->mtx); for (int i = 0; i < async->workers; i++) { thrd_join(async->thrd[i], NULL); } /* Notify worker callbacks (so they can call destructors) */ struct le *le; LIST_FOREACH(&async->workl, le) { struct async_work *work = le->data; if (work->cb) { work->cb(ECANCELED, work->arg); work->cb = NULL; } } LIST_FOREACH(&async->curl, le) { struct async_work *work = le->data; if (work->cb) { work->cb(ECANCELED, work->arg); work->cb = NULL; } } list_flush(&async->workl); list_flush(&async->curl); list_flush(&async->freel); cnd_destroy(&async->wait); mtx_destroy(&async->mtx); mem_deref(async->mqueue); mem_deref(async->thrd); } static void worker_check(void *arg) { struct re_async *async = arg; mtx_lock(&async->mtx); if (!list_isempty(&async->workl)) { if (async->workers == list_count(&async->curl)) DEBUG_WARNING("all async workers are busy (%u)\n", async->workers); else cnd_broadcast(&async->wait); } mtx_unlock(&async->mtx); tmr_start(&async->tmr, 100, worker_check, async); } /* called by re main event loop */ static void queueh(int id, void *data, void *arg) { struct async_work *work = data; struct re_async *async = arg; (void)id; mtx_lock(work->mtx); re_async_h *cb = work->cb; void *cb_arg = work->arg; int err = work->err; work->cb = NULL; mtx_unlock(work->mtx); if (cb) cb(err, cb_arg); mtx_lock(&async->mtx); list_move(&work->le, &async->freel); mtx_unlock(&async->mtx); } static void work_destruct(void *arg) { struct async_work *work = arg; mem_deref(work->mtx); } static int work_alloc(struct async_work **workp) { int err; struct async_work *work; work = mem_zalloc(sizeof(struct async_work), NULL); if (!work) { err = ENOMEM; return err; } err = mutex_alloc(&work->mtx); if (err) { mem_deref(work); return err; } mem_destructor(work, work_destruct); *workp = work; return 0; } /** * Allocate a new async object * * @param asyncp Pointer to allocated async object * @param workers Number of worker threads * * @return 0 if success, otherwise errorcode */ int re_async_alloc(struct re_async **asyncp, uint16_t workers) { int err; struct re_async *async; struct async_work *work; if (!asyncp || !workers) return EINVAL; async = mem_zalloc(sizeof(struct re_async), NULL); if (!async) return ENOMEM; err = mqueue_alloc(&async->mqueue, queueh, async); if (err) goto err; async->thrd = mem_zalloc(sizeof(thrd_t) * workers, NULL); if (!async->thrd) { err = ENOMEM; mem_deref(async->mqueue); goto err; } mtx_init(&async->mtx, mtx_plain); cnd_init(&async->wait); tmr_init(&async->tmr); mem_destructor(async, async_destructor); async->run = true; for (int i = 0; i < workers; i++) { err = thread_create_name(&async->thrd[i], "async worker thread", worker_thread, async); if (err) goto err; async->workers++; /* preallocate */ err = work_alloc(&work); if (err) goto err; list_append(&async->freel, &work->le, work); } tmr_start(&async->tmr, 10, worker_check, async); *asyncp = async; return 0; err: mem_deref(async); return err; } /** * Execute work handler async and get a callback from re main thread * * @param async Pointer to async object * @param id Work identifier * @param workh Work handler * @param cb Callback handler (called by re main thread) * @param arg Handler argument (has to be thread-safe) * * @return 0 if success, otherwise errorcode */ int re_async(struct re_async *async, intptr_t id, re_async_work_h *workh, re_async_h *cb, void *arg) { int err = 0; struct async_work *work; if (unlikely(!async)) return EINVAL; mtx_lock(&async->mtx); if (unlikely(list_isempty(&async->freel))) { err = work_alloc(&work); if (err) goto out; } else { work = list_head(&async->freel)->data; list_unlink(&work->le); } work->workh = workh; work->cb = cb; work->arg = arg; work->id = id; list_append(&async->workl, &work->le, work); cnd_signal(&async->wait); out: mtx_unlock(&async->mtx); return err; } /** * Cancel pending async work and callback * * @param async Pointer to async object * @param id Work identifier */ void re_async_cancel(struct re_async *async, intptr_t id) { struct le *le; if (unlikely(!async)) return; mtx_lock(&async->mtx); le = list_head(&async->workl); while (le) { struct async_work *w = le->data; le = le->next; if (w->id != id) continue; mtx_lock(w->mtx); w->id = 0; w->workh = NULL; w->cb = NULL; w->arg = mem_deref(w->arg); list_move(&w->le, &async->freel); mtx_unlock(w->mtx); } le = list_head(&async->curl); while (le) { struct async_work *w = le->data; le = le->next; if (w->id != id) continue; mtx_lock(w->mtx); w->id = 0; w->workh = NULL; w->cb = NULL; w->arg = mem_deref(w->arg); /* No move to free list since queueh must always handled if * mqueue_push is called */ mtx_unlock(w->mtx); } mtx_unlock(&async->mtx); } ================================================ FILE: src/av1/depack.c ================================================ /** * @file av1/depack.c AV1 De-packetizer * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include /** * Decode an AV1 Aggregation header from mbuffer * * @param hdr Decoded aggregation header * @param mb Mbuffer to decode from * * @return 0 if success, otherwise errorcode */ int av1_aggr_hdr_decode(struct av1_aggr_hdr *hdr, struct mbuf *mb) { uint8_t v; if (!hdr || !mb) return EINVAL; memset(hdr, 0, sizeof(*hdr)); if (mbuf_get_left(mb) < 1) return EBADMSG; v = mbuf_read_u8(mb); hdr->z = v>>7 & 0x1; hdr->y = v>>6 & 0x1; hdr->w = v>>4 & 0x3; hdr->n = v>>3 & 0x1; return 0; } ================================================ FILE: src/av1/obu.c ================================================ /** * @file av1/obu.c AV1 Open Bitstream Unit (OBU) * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include #include #define DEBUG_MODULE "av1" #define DEBUG_LEVEL 5 #include bool obu_allowed_rtp(enum obu_type type) { switch (type) { case AV1_OBU_SEQUENCE_HEADER: case AV1_OBU_FRAME_HEADER: case AV1_OBU_METADATA: case AV1_OBU_FRAME: case AV1_OBU_REDUNDANT_FRAME_HEADER: case AV1_OBU_TILE_GROUP: return true; default: return false; } } /** * Encode a number into LEB128 format, which is an unsigned integer * represented by a variable number of little-endian bytes. * * @param mb Mbuffer to encode into * @param value Value to encode * * @return 0 if success, otherwise errorcode */ int av1_leb128_encode(struct mbuf *mb, uint64_t value) { int err = 0; if (!mb) return EINVAL; while (value >= 0x80) { uint8_t u8 = 0x80 | (value & 0x7f); err |= mbuf_write_u8(mb, u8); value >>= 7; } err |= mbuf_write_u8(mb, (uint8_t)value); return err; } /** * Decode a number in LEB128 format, which is an unsigned integer * represented by a variable number of little-endian bytes. * * @param mb Mbuffer to decode from * @param value Decoded value, set on return * * @return 0 if success, otherwise errorcode */ int av1_leb128_decode(struct mbuf *mb, uint64_t *value) { uint64_t ret = 0; if (!mb || !value) return EINVAL; for (unsigned i = 0; i < 8; i++) { size_t byte; if (mbuf_get_left(mb) < 1) return EBADMSG; byte = mbuf_read_u8(mb); ret |= (uint64_t)(byte & 0x7f) << (i * 7); if (!(byte & 0x80)) break; } *value = ret; return 0; } /** * Encode an OBU into an mbuffer * * @param mb Mbuffer to encode into * @param type OBU type * @param has_size True to use the 'has_size' field * @param len Number of bytes * @param payload Optional OBU payload * * @return 0 if success, otherwise errorcode */ int av1_obu_encode(struct mbuf *mb, uint8_t type, bool has_size, size_t len, const uint8_t *payload) { uint8_t val; int err; if (!mb) return EINVAL; val = (type&0xf) << 3; val |= (unsigned)has_size << 1; err = mbuf_write_u8(mb, val); if (has_size) err |= av1_leb128_encode(mb, len); if (payload && len) err |= mbuf_write_mem(mb, payload, len); return err; } /** * Decode an OBU header from mbuffer * * @param hdr Decoded OBU header * @param mb Mbuffer to decode from * * @return 0 if success, otherwise errorcode */ int av1_obu_decode(struct av1_obu_hdr *hdr, struct mbuf *mb) { uint8_t val; bool f; int err; if (!hdr || !mb) return EINVAL; if (mbuf_get_left(mb) < 1) return EBADMSG; memset(hdr, 0, sizeof(*hdr)); val = mbuf_read_u8(mb); f = (val >> 7) & 0x1; hdr->type = (val >> 3) & 0xf; hdr->x = (val >> 2) & 0x1; hdr->s = (val >> 1) & 0x1; if (f) { DEBUG_WARNING("av1: header: obu forbidden bit!" " [type=%u, x=%d, s=%d, left=%zu bytes]\n", hdr->type, hdr->x, hdr->s, mbuf_get_left(mb)); return EBADMSG; } if (hdr->x) { DEBUG_WARNING("av1: header: extension not supported (%u)\n", hdr->type); return ENOTSUP; } if (hdr->s) { uint64_t size; err = av1_leb128_decode(mb, &size); if (err) return err; if (size > mbuf_get_left(mb)) { DEBUG_WARNING("av1: obu decode: short packet:" " %llu > %zu\n", size, mbuf_get_left(mb)); return EBADMSG; } hdr->size = (size_t)size; } else { hdr->size = mbuf_get_left(mb); } return 0; } int av1_obu_print(struct re_printf *pf, const struct av1_obu_hdr *hdr) { if (!hdr) return 0; return re_hprintf(pf, "type=%u,%-24s x=%d s=%d size=%zu", hdr->type, av1_obu_name(hdr->type), hdr->x, hdr->s, hdr->size); } /** * Count number of OBUs in the bitstream * * @param buf Bitstream buffer * @param size Number of bytes in buffer * * @return Number of OBUs */ unsigned av1_obu_count(const uint8_t *buf, size_t size) { struct mbuf wrap = { .buf = (uint8_t *)buf, .size = size, .pos = 0, .end = size }; unsigned count = 0; while (mbuf_get_left(&wrap) > 1) { struct av1_obu_hdr hdr; int err = av1_obu_decode(&hdr, &wrap); if (err) { DEBUG_WARNING("count: could not decode OBU" " [%zu bytes]: %m\n", size, err); return 0; } mbuf_advance(&wrap, hdr.size); ++count; } return count; } /** * Count number of OBUs in the bitstream allowed for RTP transmission * * @param buf Bitstream buffer * @param size Number of bytes in buffer * * @return Number of OBUs */ unsigned av1_obu_count_rtp(const uint8_t *buf, size_t size) { struct mbuf wrap = { .buf = (uint8_t *)buf, .size = size, .pos = 0, .end = size }; unsigned count = 0; while (mbuf_get_left(&wrap) > 1) { struct av1_obu_hdr hdr; int err = av1_obu_decode(&hdr, &wrap); if (err) { DEBUG_WARNING("count: could not decode OBU" " [%zu bytes]: %m\n", size, err); return 0; } if (obu_allowed_rtp(hdr.type)) ++count; mbuf_advance(&wrap, hdr.size); } return count; } const char *av1_obu_name(enum obu_type type) { switch (type) { case AV1_OBU_SEQUENCE_HEADER: return "OBU_SEQUENCE_HEADER"; case AV1_OBU_TEMPORAL_DELIMITER: return "OBU_TEMPORAL_DELIMITER"; case AV1_OBU_FRAME_HEADER: return "OBU_FRAME_HEADER"; case AV1_OBU_REDUNDANT_FRAME_HEADER: return "OBU_REDUNDANT_FRAME_HEADER"; case AV1_OBU_FRAME: return "OBU_FRAME"; case AV1_OBU_TILE_GROUP: return "OBU_TILE_GROUP"; case AV1_OBU_METADATA: return "OBU_METADATA"; case AV1_OBU_TILE_LIST: return "OBU_TILE_LIST"; case AV1_OBU_PADDING: return "OBU_PADDING"; default: return "???"; } } ================================================ FILE: src/av1/pkt.c ================================================ /** * @file av1/pkt.c AV1 Packetizer * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include #include #include #define DEBUG_MODULE "av1" #define DEBUG_LEVEL 5 #include enum { MAX_OBUS = 3, /* Maximum number of OBUs for W field */ AV1_OBU_HEADER_SIZE = 1, }; struct av1_context { /* The current RTP packet being created */ struct mbuf *mb_pkt; /* The current OBU being packetized. This only contains the OBU * payload, not the header or size */ struct mbuf *curr_obu; /* The input buffer provided by the application. * The position always points to immediately after curr_obu */ struct mbuf *mb_buf; /* The OBU header for the current OBU being packetized */ struct av1_obu_hdr curr_hdr; /* The number of bytes which still need to be written from the * current OBU. This can be greater than the OBU size if the OBU * header hasn't been written yet. * If this is zero at the end of a packet, packetization is done */ size_t curr_remaining; /* The max length of each RTP packet */ size_t maxlen; }; /* * Calculate length of LEB128 field * * Add high 1 bits on all but last (most significant) group to form bytes */ static size_t leb128_calc_size(uint64_t value) { size_t bytes = 1; /* Bit7: 1=more bytes coming, 0=complete */ while (value >= 0x80) { ++bytes; value >>= 7; } return bytes; } /* * Serialize the AV1 RTP aggregation header * * Z: 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: 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. */ static void aggr_hdr_encode(uint8_t hdr[AV1_AGGR_HDR_SIZE], bool z, bool y, uint8_t w, bool n) { hdr[0] = z<<7 | y<<6 | w<<4 | n<<3; } /** * @returns the size the given OBU will be once we packetize it. * We force has_size to false in the OBU header, so this is the size of the OBU * and the one-byte OBU header. */ static size_t packetized_obu_size(const struct av1_obu_hdr* hdr) { return AV1_OBU_HEADER_SIZE + hdr->size; } /** * Searches through mb_buf until it finds an OBU which should be packetized, * and updates the current OBU when one is found. * If there are no more OBUs, curr_remaining will be set to 0 * * @return 0 if success, otherwise errorcode */ static int update_curr_obu(struct av1_context* context) { int err = 0; do { size_t remaining = mbuf_get_left(context->mb_buf); /* OBUs must be at least 2 bytes */ if (remaining < 2) { if (remaining > 0) { DEBUG_WARNING( "av1: encode: leftover data " "(%zu bytes)\n", remaining); mbuf_advance(context->mb_buf, remaining); } context->curr_obu->pos = context->curr_obu->end; context->curr_remaining = 0; break; } err = av1_obu_decode(&context->curr_hdr, context->mb_buf); if (err) { break; } context->curr_obu->buf = mbuf_buf(context->mb_buf); context->curr_obu->size = context->curr_hdr.size; context->curr_obu->pos = 0; context->curr_obu->end = context->curr_hdr.size; context->curr_remaining = packetized_obu_size(&context->curr_hdr); mbuf_advance(context->mb_buf, context->curr_hdr.size); } while (!obu_allowed_rtp(context->curr_hdr.type)); return err; } /** * Copies len_to_copy of the current OBU to the RTP packet, taking the OBU * header into account. The caller is responsible for ensuring there is * enough space left in the packet for this many bytes: * len_to_copy + * include_prefix ? leb128_calc_size(len_to_copy) : 0 * * @param context Packetization context * @param include_prefix Whether to include the size prefix before the fragment * @param len_to_copy The length of the OBU fragment to write. This is the * number of bytes to copy from the OBU * * @return 0 if success, otherwise errorcode */ static int copy_fragment(struct av1_context* context, bool include_prefix, size_t len_to_copy) { int err = 0; if (include_prefix) { err = av1_leb128_encode(context->mb_pkt, len_to_copy); if (err) { goto out; } } /* * If this is the first time we're writing this OBU, we need to take * the OBU header into account. The header can't be copied normally * because we might need to modify it to remove the size information. */ if (context->curr_remaining == packetized_obu_size(&context->curr_hdr)) { uint8_t obu_hdr = (context->curr_hdr.type & 0xf) << 3; err |= mbuf_write_u8(context->mb_pkt, obu_hdr); if (err) { goto out; } --len_to_copy; --context->curr_remaining; } err = mbuf_write_mem(context->mb_pkt, mbuf_buf(context->curr_obu), len_to_copy); if (err) { goto out; } mbuf_advance(context->curr_obu, len_to_copy); context->curr_remaining -= len_to_copy; out: return err; } /** * Calculates the length of a fragment and the length of the leb128-encoded * size prefix for it. * * @param context Packetization context * @param remaining_pkt The number of bytes remaining in the current packet. * Must be greater than zero * @param len_to_copy_out Pointer to receive the length of the fragment to be * written * @param prefix_len_out Pointer to receive the length of the size prefix to * be written */ static void calc_fragment_len_with_prefix( struct av1_context* context, size_t remaining_pkt, size_t* len_to_copy_out, size_t* prefix_len_out) { /* The size always uses at least 1 byte, so subtract one from the * remaining packet space */ size_t len_to_copy = min(context->curr_remaining, remaining_pkt - 1); size_t prefix_len = leb128_calc_size(len_to_copy); if (len_to_copy + prefix_len > remaining_pkt) { /* If there's not enough room in the packet for the initial * estimate, reserve space in the packet for the length of the * prefix and try again. Note that prefix_len can change here * if len_to_copy changes from, say, 128 to 127, but the new * prefix_len will always be <= the old one. */ len_to_copy = remaining_pkt - prefix_len; prefix_len = leb128_calc_size(len_to_copy); } *len_to_copy_out = len_to_copy; *prefix_len_out = prefix_len; } /** * Calculates the length of the next fragment to write to the RTP packet and * whether it needs a size prefix. * * @param context Packetization context * @param count The number of fragments already written to the RTP * packet * @param include_prefix_out Pointer to receive whether the size prefix should * be written before this fragment * @param len_to_copy_out Pointer to receive the length of the fragment to * be written. If set to zero, there was not enough * room in the packet for another fragment * * @returns 0 on success and non-zero otherwise. On success, check * len_to_copy_out to determine if there was room in the packet for * another fragment */ static int calc_fragment_len( struct av1_context* context, size_t count, bool* include_prefix_out, size_t* len_to_copy_out) { /* Note: This checks for space left in the entire buffer, * not the current OBU */ bool is_last_obu = mbuf_get_left(context->mb_buf) == 0; size_t pkt_len = mbuf_pos(context->mb_pkt); size_t remaining_pkt = 0; size_t len_to_copy = 0; size_t fragment_size_len = 0; bool include_prefix = false; *include_prefix_out = 0; *len_to_copy_out = 0; if (pkt_len > context->maxlen) { DEBUG_WARNING("av1: encode: packet too large (%zu > %zu)\n", pkt_len, context->maxlen); return ERANGE; } remaining_pkt = context->maxlen - pkt_len; if (remaining_pkt < 1) { return 0; } /* The size prefix can be elided for the last fragment only when there * are 3 or fewer fragments in the packet. */ if (count > MAX_OBUS) { include_prefix = true; } else if (!is_last_obu && remaining_pkt > context->curr_remaining) { /* If the next fragment would need a prefix, we need a minimum * of 2 bytes for it. */ size_t next_fragment = count > 2 ? 2 : 1; calc_fragment_len_with_prefix(context, remaining_pkt, &len_to_copy, &fragment_size_len); /* Only include a prefix if there * is room for another fragment. */ include_prefix = len_to_copy + fragment_size_len <= remaining_pkt - next_fragment; } if (include_prefix) { /* A prefixed fragment needs a minimum of 2 bytes: * 1 for the prefix itself and at least 1 for the data. */ if (remaining_pkt < 2) { return 0; } /* Only calculate this if it wasn't calculated above when * count <= 3 */ if (len_to_copy == 0) { calc_fragment_len_with_prefix(context, remaining_pkt, &len_to_copy, &fragment_size_len); } } else { /* No prefix is needed, fill as much * of the packet as possible. */ len_to_copy = min(context->curr_remaining, remaining_pkt); } *include_prefix_out = include_prefix; *len_to_copy_out = len_to_copy; return 0; } /** * Copy as many OBU fragments as possible to the current RTP packet. * * @param context Packetization context * @param w The value of w for this packet (whether each fragment is * prefixed or the last one is elided) * @param y The value of y for this packet (whether the last fragment * will continue to the next packet) */ static int copy_obus_to_packet(struct av1_context* context, uint8_t* w, bool *y) { unsigned count = 0; int err = 0; bool include_prefix = true; /* Stop copying OBUs when: * 1. There are no more OBUs left (curr_remaining == 0), or * 2. The last fragment didn't include a prefix. We aren't allowed to * copy another fragment even if there's space in the packet, or * 3. calc_fragment_len determines there isn't enough room for another * fragment */ while (context->curr_remaining > 0 && include_prefix) { size_t len_to_copy = 0; err = calc_fragment_len(context, count + 1, &include_prefix, &len_to_copy); if (err) { goto out; } /* Not enough room for another fragment */ if (len_to_copy == 0) { break; } err = copy_fragment(context, include_prefix, len_to_copy); if (err) { goto out; } ++count; if (context->curr_remaining == 0) { /* We finished packetizing the current OBU, * move onto the next one. */ *y = false; err = update_curr_obu(context); if (err) { goto out; } } else { /* The current OBU still has data left to packetize. */ *y = true; } } /* It's possible for copy_obu_to_packet to accidentally include the * size prefix before the last OBU in the packet if the last OBU in * the buffer is skipped. We need to set w = 0 in that case even if * there are <= 3 OBU fragments */ *w = count > MAX_OBUS || include_prefix ? 0 : count; out: return err; } /** * Packetize an AV1 bitstream with one or more OBUs * * @param newp Pointer to new stream flag * @param marker Set marker bit * @param rtp_ts RTP timestamp * @param buf Input buffer * @param len Buffer length * @param maxlen Maximum RTP packet size * @param pkth Packet handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int av1_packetize(bool *newp, bool marker, uint64_t rtp_ts, const uint8_t *buf, size_t len, size_t maxlen, av1_packet_h *pkth, void *arg) { struct mbuf *mb_pkt; uint8_t w; int err; bool continuing_to_next_packet = false; bool continued_from_previous_packet = false; struct mbuf mb_buf = { .buf = (uint8_t *)buf, .size = len, .pos = 0, .end = len }; struct mbuf curr_obu; uint8_t aggr_hdr[AV1_AGGR_HDR_SIZE]; if (!newp || !buf || !len || maxlen < (AV1_AGGR_HDR_SIZE + 1) || !pkth) return EINVAL; maxlen -= sizeof(aggr_hdr); mb_pkt = mbuf_alloc(maxlen); if (!mb_pkt) return ENOMEM; mbuf_init(&curr_obu); struct av1_context context = { .mb_pkt = mb_pkt, .mb_buf = &mb_buf, .curr_obu = &curr_obu, .curr_remaining = 0, .maxlen = maxlen }; err = update_curr_obu(&context); if (err) { goto out; } while (context.curr_remaining > 0) { continued_from_previous_packet = continuing_to_next_packet; err = copy_obus_to_packet(&context, &w, &continuing_to_next_packet); if (err) { goto out; } aggr_hdr_encode(aggr_hdr, continued_from_previous_packet, continuing_to_next_packet, w, *newp); *newp = false; mbuf_set_pos(context.mb_pkt, 0); err = pkth(marker && context.curr_remaining == 0, rtp_ts, aggr_hdr, sizeof(aggr_hdr), mbuf_buf(context.mb_pkt), mbuf_get_left(context.mb_pkt), arg); if (err) { goto out; } mbuf_rewind(context.mb_pkt); } out: mem_deref(mb_pkt); return err; } ================================================ FILE: src/base64/b64.c ================================================ /** * @file b64.c Base64 encoding/decoding functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include static const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /* Base 64 Encoding with URL and Filename Safe Alphabet, RFC 4648 section 5 */ static const char b64url_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789-_"; /** * Base-64 encode a buffer * * @param in Input buffer * @param ilen Length of input buffer * @param out Output buffer * @param olen Size of output buffer, actual written on return * * @return 0 if success, otherwise errorcode */ int base64_encode(const uint8_t *in, size_t ilen, char *out, size_t *olen) { const uint8_t *in_end = in + ilen; const char *o = out; if (!in || !out || !olen) return EINVAL; if (*olen < 4 * ((ilen+2)/3)) return EOVERFLOW; for (; in < in_end; ) { uint32_t v; int pad = 0; v = *in++ << 16; if (in < in_end) { v |= *in++ << 8; } else { ++pad; } if (in < in_end) { v |= *in++ << 0; } else { ++pad; } *out++ = b64_table[v >> 18 & 0x3f]; *out++ = b64_table[v >> 12 & 0x3f]; *out++ = (pad >= 2) ? '=' : b64_table[v >> 6 & 0x3f]; *out++ = (pad >= 1) ? '=' : b64_table[v >> 0 & 0x3f]; } *olen = out - o; return 0; } /** * Base-64 url encode a buffer (without padding) * * @param in Input buffer * @param ilen Length of input buffer * @param out Output buffer * @param olen Size of output buffer, actual written on return * * @return 0 if success, otherwise errorcode */ int base64url_encode(const uint8_t *in, size_t ilen, char *out, size_t *olen) { if (!in || !out || !olen) return EINVAL; const uint8_t *in_end = in + ilen; const char *o = out; if (*olen < 4 * ((ilen+2)/3)) return EOVERFLOW; for (; in < in_end; ) { uint32_t v; int pad = 0; v = *in++ << 16; if (in < in_end) { v |= *in++ << 8; } else { ++pad; } if (in < in_end) { v |= *in++ << 0; } else { ++pad; } *out++ = b64url_table[v >> 18 & 0x3f]; *out++ = b64url_table[v >> 12 & 0x3f]; if (pad < 2) *out++ = b64url_table[v >> 6 & 0x3f]; if (pad < 1) *out++ = b64url_table[v >> 0 & 0x3f]; } *olen = out - o; return 0; } int base64_print(struct re_printf *pf, const uint8_t *ptr, size_t len) { char buf[256]; if (!pf || !ptr) return EINVAL; while (len > 0) { size_t l, sz = sizeof(buf); int err; l = min(len, 3 * (sizeof(buf)/4)); err = base64_encode(ptr, l, buf, &sz); if (err) return err; err = pf->vph(buf, sz, pf->arg); if (err) return err; ptr += l; len -= l; } return 0; } /* convert char -> 6-bit value */ static inline uint32_t b64val(char c) { if ('A' <= c && c <= 'Z') return c - 'A' + 0; else if ('a' <= c && c <= 'z') return c - 'a' + 26; else if ('0' <= c && c <= '9') return c - '0' + 52; else if ('+' == c || '-' == c) return 62; else if ('/' == c || '_' == c) return 63; else if ('=' == c) return 1<<24; /* special trick */ else return 0; } /** * Decode a Base-64 encoded string * * @param in Input buffer * @param ilen Length of input buffer * @param out Output buffer * @param olen Size of output buffer, actual written on return * * @return 0 if success, otherwise errorcode */ int base64_decode(const char *in, size_t ilen, uint8_t *out, size_t *olen) { if (!in || !out || !olen) return EINVAL; const char *in_end = in + ilen; const uint8_t *o = out; if (*olen < 3 * (ilen/4)) return EOVERFLOW; for (;in+1 < in_end; ) { uint32_t v; v = b64val(*in++) << 18; v |= b64val(*in++) << 12; if (in < in_end) v |= b64val(*in++) << 6; else v |= (1<<24) << 6; /* padding fallback */ if (in < in_end) v |= b64val(*in++) << 0; else v |= (1<<24) << 0; /* padding fallback */ *out++ = v>>16; if (!(v & (1<<30))) *out++ = (v>>8) & 0xff; if (!(v & (1<<24))) *out++ = (v>>0) & 0xff; } *olen = out - o; return 0; } ================================================ FILE: src/bfcp/attr.c ================================================ /** * @file bfcp/attr.c BFCP Attributes * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "bfcp.h" enum { BFCP_ATTR_HDR_SIZE = 2, }; static void destructor(void *arg) { struct bfcp_attr *attr = arg; switch (attr->type) { case BFCP_ERROR_CODE: mem_deref(attr->v.errcode.details); break; case BFCP_ERROR_INFO: case BFCP_PART_PROV_INFO: case BFCP_STATUS_INFO: case BFCP_USER_DISP_NAME: case BFCP_USER_URI: mem_deref(attr->v.str); break; case BFCP_SUPPORTED_ATTRS: mem_deref(attr->v.supattr.attrv); break; case BFCP_SUPPORTED_PRIMS: mem_deref(attr->v.supprim.primv); break; default: break; } list_flush(&attr->attrl); list_unlink(&attr->le); } static int attr_encode(struct mbuf *mb, bool mand, enum bfcp_attrib type, const void *v) { const struct bfcp_reqstatus *reqstatus = v; const struct bfcp_errcode *errcode = v; const struct bfcp_supattr *supattr = v; const struct bfcp_supprim *supprim = v; const enum bfcp_priority *priority = v; const uint16_t *u16 = v; size_t start, i; uint8_t len; int err; start = mb->pos; mb->pos += BFCP_ATTR_HDR_SIZE; switch (type) { case BFCP_BENEFICIARY_ID: case BFCP_FLOOR_ID: case BFCP_FLOOR_REQUEST_ID: case BFCP_BENEFICIARY_INFO: case BFCP_FLOOR_REQ_INFO: case BFCP_REQUESTED_BY_INFO: case BFCP_FLOOR_REQ_STATUS: case BFCP_OVERALL_REQ_STATUS: err = mbuf_write_u16(mb, htons(*u16)); break; case BFCP_PRIORITY: err = mbuf_write_u8(mb, *priority << 5); err |= mbuf_write_u8(mb, 0x00); break; case BFCP_REQUEST_STATUS: err = mbuf_write_u8(mb, reqstatus->status); err |= mbuf_write_u8(mb, reqstatus->qpos); break; case BFCP_ERROR_CODE: err = mbuf_write_u8(mb, errcode->code); if (errcode->details && errcode->len) err |= mbuf_write_mem(mb, errcode->details, errcode->len); break; case BFCP_ERROR_INFO: case BFCP_PART_PROV_INFO: case BFCP_STATUS_INFO: case BFCP_USER_DISP_NAME: case BFCP_USER_URI: err = mbuf_write_str(mb, v); break; case BFCP_SUPPORTED_ATTRS: for (i=0, err=0; iattrc; i++) err |= mbuf_write_u8(mb, supattr->attrv[i] << 1); break; case BFCP_SUPPORTED_PRIMS: for (i=0, err=0; iprimc; i++) err |= mbuf_write_u8(mb, supprim->primv[i]); break; default: err = EINVAL; break; } /* header */ len = (uint8_t)(mb->pos - start); mb->pos = start; err |= mbuf_write_u8(mb, (type<<1) | (mand ? 1 : 0)); err |= mbuf_write_u8(mb, len); mb->pos += (size_t)(len - BFCP_ATTR_HDR_SIZE); /* padding */ while ((mb->pos - start) & 0x03) err |= mbuf_write_u8(mb, 0x00); return err; } /** * Encode BFCP Attributes with variable arguments * * @param mb Mbuf to encode into * @param attrc Number of attributes * @param ap Variable argument of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_attrs_vencode(struct mbuf *mb, unsigned attrc, va_list *ap) { unsigned i; if (!mb) return EINVAL; for (i=0; ipos; if (type == BFCP_ENCODE_HANDLER) { const struct bfcp_encode *enc = v; if (enc->ench) { err = enc->ench(mb, enc->arg); if (err) return err; } continue; } err = attr_encode(mb, type>>7, type & 0x7f, v); if (err) return err; if (subc == 0) continue; err = bfcp_attrs_vencode(mb, subc, ap); if (err) return err; /* update total length for grouped attributes */ len = mb->pos - start; mb->pos = start + 1; err = mbuf_write_u8(mb, (uint8_t)len); if (err) return err; mb->pos += (len - BFCP_ATTR_HDR_SIZE); } return 0; } /** * Encode BFCP Attributes * * @param mb Mbuf to encode into * @param attrc Number of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_attrs_encode(struct mbuf *mb, unsigned attrc, ...) { va_list ap; int err; va_start(ap, attrc); err = bfcp_attrs_vencode(mb, attrc, &ap); va_end(ap); return err; } static int attr_decode(struct bfcp_attr **attrp, struct mbuf *mb, struct bfcp_unknown_attr *uma) { struct bfcp_attr *attr; union bfcp_union *v; size_t i, start, len; int err = 0; uint8_t b; if (mbuf_get_left(mb) < BFCP_ATTR_HDR_SIZE) return EBADMSG; attr = mem_zalloc(sizeof(*attr), destructor); if (!attr) return ENOMEM; start = mb->pos; b = mbuf_read_u8(mb); attr->type = b >> 1; attr->mand = b & 1; len = mbuf_read_u8(mb); if (len < BFCP_ATTR_HDR_SIZE) goto badmsg; len -= BFCP_ATTR_HDR_SIZE; if (mbuf_get_left(mb) < len) goto badmsg; v = &attr->v; switch (attr->type) { case BFCP_BENEFICIARY_ID: case BFCP_FLOOR_ID: case BFCP_FLOOR_REQUEST_ID: if (len < 2) goto badmsg; v->u16 = ntohs(mbuf_read_u16(mb)); break; case BFCP_PRIORITY: if (len < 2) goto badmsg; v->priority = mbuf_read_u8(mb) >> 5; (void)mbuf_read_u8(mb); break; case BFCP_REQUEST_STATUS: if (len < 2) goto badmsg; v->reqstatus.status = mbuf_read_u8(mb); v->reqstatus.qpos = mbuf_read_u8(mb); break; case BFCP_ERROR_CODE: if (len < 1) goto badmsg; v->errcode.code = mbuf_read_u8(mb); v->errcode.len = len - 1; if (v->errcode.len == 0) break; v->errcode.details = mem_alloc(v->errcode.len, NULL); if (!v->errcode.details) { err = ENOMEM; goto error; } (void)mbuf_read_mem(mb, v->errcode.details, v->errcode.len); break; case BFCP_ERROR_INFO: case BFCP_PART_PROV_INFO: case BFCP_STATUS_INFO: case BFCP_USER_DISP_NAME: case BFCP_USER_URI: err = mbuf_strdup(mb, &v->str, len); break; case BFCP_SUPPORTED_ATTRS: v->supattr.attrc = len; v->supattr.attrv = mem_alloc(len*sizeof(*v->supattr.attrv), NULL); if (!v->supattr.attrv) { err = ENOMEM; goto error; } for (i=0; isupattr.attrv[i] = mbuf_read_u8(mb) >> 1; break; case BFCP_SUPPORTED_PRIMS: v->supprim.primc = len; v->supprim.primv = mem_alloc(len * sizeof(*v->supprim.primv), NULL); if (!v->supprim.primv) { err = ENOMEM; goto error; } for (i=0; isupprim.primv[i] = mbuf_read_u8(mb); break; /* grouped attributes */ case BFCP_BENEFICIARY_INFO: case BFCP_FLOOR_REQ_INFO: case BFCP_REQUESTED_BY_INFO: case BFCP_FLOOR_REQ_STATUS: case BFCP_OVERALL_REQ_STATUS: if (len < 2) goto badmsg; v->u16 = ntohs(mbuf_read_u16(mb)); err = bfcp_attrs_decode(&attr->attrl, mb, len - 2, uma); break; default: mb->pos += len; if (!attr->mand) break; if (uma && uma->typec < RE_ARRAY_SIZE(uma->typev)) uma->typev[uma->typec++] = attr->type<<1; break; } if (err) goto error; /* padding */ while (((mb->pos - start) & 0x03) && mbuf_get_left(mb)) ++mb->pos; *attrp = attr; return 0; badmsg: err = EBADMSG; error: mem_deref(attr); return err; } int bfcp_attrs_decode(struct list *attrl, struct mbuf *mb, size_t len, struct bfcp_unknown_attr *uma) { int err = 0; size_t end; if (!attrl || !mb || mbuf_get_left(mb) < len) return EINVAL; end = mb->end; mb->end = mb->pos + len; while (mbuf_get_left(mb) >= BFCP_ATTR_HDR_SIZE) { struct bfcp_attr *attr; err = attr_decode(&attr, mb, uma); if (err) break; list_append(attrl, &attr->le, attr); } mb->end = end; return err; } struct bfcp_attr *bfcp_attrs_find(const struct list *attrl, enum bfcp_attrib type) { struct le *le = list_head(attrl); while (le) { struct bfcp_attr *attr = le->data; le = le->next; if (attr->type == type) return attr; } return NULL; } struct bfcp_attr *bfcp_attrs_apply(const struct list *attrl, bfcp_attr_h *h, void *arg) { struct le *le = list_head(attrl); while (le) { struct bfcp_attr *attr = le->data; le = le->next; if (h && h(attr, arg)) return attr; } return NULL; } /** * Get a BFCP sub-attribute from a BFCP attribute * * @param attr BFCP attribute * @param type Attribute type * * @return Matching BFCP attribute if found, otherwise NULL */ struct bfcp_attr *bfcp_attr_subattr(const struct bfcp_attr *attr, enum bfcp_attrib type) { if (!attr) return NULL; return bfcp_attrs_find(&attr->attrl, type); } /** * Apply a function handler to all sub-attributes in a BFCP attribute * * @param attr BFCP attribute * @param h Handler * @param arg Handler argument * * @return BFCP attribute returned by handler, or NULL */ struct bfcp_attr *bfcp_attr_subattr_apply(const struct bfcp_attr *attr, bfcp_attr_h *h, void *arg) { if (!attr) return NULL; return bfcp_attrs_apply(&attr->attrl, h, arg); } /** * Print a BFCP attribute * * @param pf Print function * @param attr BFCP attribute * * @return 0 if success, otherwise errorcode */ int bfcp_attr_print(struct re_printf *pf, const struct bfcp_attr *attr) { const union bfcp_union *v; size_t i; int err; if (!attr) return 0; err = re_hprintf(pf, "%c%-28s", attr->mand ? '*' : ' ', bfcp_attr_name(attr->type)); v = &attr->v; switch (attr->type) { case BFCP_BENEFICIARY_ID: case BFCP_FLOOR_ID: case BFCP_FLOOR_REQUEST_ID: err |= re_hprintf(pf, "%u", v->u16); break; case BFCP_PRIORITY: err |= re_hprintf(pf, "%d", v->priority); break; case BFCP_REQUEST_STATUS: err |= re_hprintf(pf, "%s (%d), qpos=%u", bfcp_reqstatus_name(v->reqstatus.status), v->reqstatus.status, v->reqstatus.qpos); break; case BFCP_ERROR_CODE: err |= re_hprintf(pf, "%d (%s)", v->errcode.code, bfcp_errcode_name(v->errcode.code)); if (v->errcode.code == BFCP_UNKNOWN_MAND_ATTR) { for (i=0; ierrcode.len; i++) { uint8_t type = v->errcode.details[i] >> 1; err |= re_hprintf(pf, " %s", bfcp_attr_name(type)); } } break; case BFCP_ERROR_INFO: case BFCP_PART_PROV_INFO: case BFCP_STATUS_INFO: case BFCP_USER_DISP_NAME: case BFCP_USER_URI: err |= re_hprintf(pf, "\"%s\"", v->str); break; case BFCP_SUPPORTED_ATTRS: err |= re_hprintf(pf, "%zu:", v->supattr.attrc); for (i=0; isupattr.attrc; i++) { const enum bfcp_attrib type = v->supattr.attrv[i]; err |= re_hprintf(pf, " %s", bfcp_attr_name(type)); } break; case BFCP_SUPPORTED_PRIMS: err |= re_hprintf(pf, "%zu:", v->supprim.primc); for (i=0; isupprim.primc; i++) { const enum bfcp_prim prim = v->supprim.primv[i]; err |= re_hprintf(pf, " %s", bfcp_prim_name(prim)); } break; /* Grouped Attributes */ case BFCP_BENEFICIARY_INFO: err |= re_hprintf(pf, "beneficiary-id=%u", v->beneficiaryid); break; case BFCP_FLOOR_REQ_INFO: err |= re_hprintf(pf, "floor-request-id=%u", v->floorreqid); break; case BFCP_REQUESTED_BY_INFO: err |= re_hprintf(pf, "requested-by-id=%u", v->reqbyid); break; case BFCP_FLOOR_REQ_STATUS: err |= re_hprintf(pf, "floor-id=%u", v->floorid); break; case BFCP_OVERALL_REQ_STATUS: err |= re_hprintf(pf, "floor-request-id=%u", v->floorreqid); break; default: err |= re_hprintf(pf, "???"); break; } return err; } int bfcp_attrs_print(struct re_printf *pf, const struct list *attrl, unsigned level) { struct le *le; int err = 0; for (le=list_head(attrl); le; le=le->next) { const struct bfcp_attr *attr = le->data; unsigned i; for (i=0; iattrl, level + 1); } return err; } /** * Get the BFCP attribute name * * @param type BFCP attribute type * * @return String with BFCP attribute name */ const char *bfcp_attr_name(enum bfcp_attrib type) { switch (type) { case BFCP_BENEFICIARY_ID: return "BENEFICIARY-ID"; case BFCP_FLOOR_ID: return "FLOOR-ID"; case BFCP_FLOOR_REQUEST_ID: return "FLOOR-REQUEST-ID"; case BFCP_PRIORITY: return "PRIORITY"; case BFCP_REQUEST_STATUS: return "REQUEST-STATUS"; case BFCP_ERROR_CODE: return "ERROR-CODE"; case BFCP_ERROR_INFO: return "ERROR-INFO"; case BFCP_PART_PROV_INFO: return "PARTICIPANT-PROVIDED-INFO"; case BFCP_STATUS_INFO: return "STATUS-INFO"; case BFCP_SUPPORTED_ATTRS: return "SUPPORTED-ATTRIBUTES"; case BFCP_SUPPORTED_PRIMS: return "SUPPORTED-PRIMITIVES"; case BFCP_USER_DISP_NAME: return "USER-DISPLAY-NAME"; case BFCP_USER_URI: return "USER-URI"; case BFCP_BENEFICIARY_INFO: return "BENEFICIARY-INFORMATION"; case BFCP_FLOOR_REQ_INFO: return "FLOOR-REQUEST-INFORMATION"; case BFCP_REQUESTED_BY_INFO: return "REQUESTED-BY-INFORMATION"; case BFCP_FLOOR_REQ_STATUS: return "FLOOR-REQUEST-STATUS"; case BFCP_OVERALL_REQ_STATUS: return "OVERALL-REQUEST-STATUS"; default: return "???"; } } /** * Get the BFCP Request status name * * @param status Request status * * @return String with BFCP Request status name */ const char *bfcp_reqstatus_name(enum bfcp_reqstat status) { switch (status) { case BFCP_PENDING: return "Pending"; case BFCP_ACCEPTED: return "Accepted"; case BFCP_GRANTED: return "Granted"; case BFCP_DENIED: return "Denied"; case BFCP_CANCELLED: return "Cancelled"; case BFCP_RELEASED: return "Released"; case BFCP_REVOKED: return "Revoked"; default: return "???"; } } /** * Get the BFCP Error code name * * @param code BFCP Error code * * @return String with error code */ const char *bfcp_errcode_name(enum bfcp_err code) { switch (code) { case BFCP_CONF_NOT_EXIST: return "Conference does not Exist"; case BFCP_USER_NOT_EXIST: return "User does not Exist"; case BFCP_UNKNOWN_PRIM: return "Unknown Primitive"; case BFCP_UNKNOWN_MAND_ATTR: return "Unknown Mandatory Attribute"; case BFCP_UNAUTH_OPERATION: return "Unauthorized Operation"; case BFCP_INVALID_FLOOR_ID: return "Invalid Floor ID"; case BFCP_FLOOR_REQ_ID_NOT_EXIST: return "Floor Request ID Does Not Exist"; case BFCP_MAX_FLOOR_REQ_REACHED: return "You have Already Reached the Maximum Number " "of Ongoing Floor Requests for this Floor"; case BFCP_USE_TLS: return "Use TLS"; case BFCP_PARSE_ERROR: return "Unable to Parse Message"; case BFCP_USE_DTLS: return "Use DTLS"; case BFCP_UNSUPPORTED_VERSION: return "Unsupported Version"; case BFCP_BAD_LENGTH: return "Incorrect Message Length"; case BFCP_GENERIC_ERROR: return "Generic Error"; default: return "???"; } } ================================================ FILE: src/bfcp/bfcp.h ================================================ /** * @file bfcp.h Internal interface to Binary Floor Control Protocol (BFCP) * * Copyright (C) 2010 Creytiv.com */ struct bfcp_strans { enum bfcp_prim prim; uint32_t confid; uint16_t tid; uint16_t userid; }; struct bfcp_conn { struct bfcp_strans st; struct list ctransl; struct tmr tmr1; struct tmr tmr2; struct udp_sock *us; struct tcp_sock *ts; struct tcp_conn *tc; struct sa sa_peer; struct mbuf *mb; bfcp_conn_h *connh; bfcp_estab_h *estabh; bfcp_recv_h *recvh; bfcp_close_h *closeh; void *arg; enum bfcp_transp tp; unsigned txc; uint16_t tid; }; /* attributes */ int bfcp_attrs_decode(struct list *attrl, struct mbuf *mb, size_t len, struct bfcp_unknown_attr *uma); struct bfcp_attr *bfcp_attrs_find(const struct list *attrl, enum bfcp_attrib type); struct bfcp_attr *bfcp_attrs_apply(const struct list *attrl, bfcp_attr_h *h, void *arg); int bfcp_attrs_print(struct re_printf *pf, const struct list *attrl, unsigned level); /* connection */ int bfcp_send(struct bfcp_conn *bc, const struct sa *dst, struct mbuf *mb); /* request */ bool bfcp_handle_response(struct bfcp_conn *bc, const struct bfcp_msg *msg); int bfcp_vrequest(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver, enum bfcp_prim prim, uint32_t confid, uint16_t userid, bfcp_resp_h *resph, void *arg, unsigned attrc, va_list *ap); ================================================ FILE: src/bfcp/conn.c ================================================ /** * @file bfcp/conn.c BFCP Connection * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include "bfcp.h" static void destructor(void *arg) { struct bfcp_conn *bc = arg; list_flush(&bc->ctransl); tmr_cancel(&bc->tmr1); tmr_cancel(&bc->tmr2); mem_deref(bc->us); mem_deref(bc->tc); mem_deref(bc->ts); mem_deref(bc->mb); } static bool strans_cmp(const struct bfcp_strans *st, const struct bfcp_msg *msg) { if (st->tid != msg->tid) return false; if (st->prim != msg->prim) return false; if (st->confid != msg->confid) return false; if (st->userid != msg->userid) return false; return true; } static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { struct bfcp_conn *bc = arg; struct bfcp_msg *msg; int err; err = bfcp_msg_decode(&msg, mb); if (err) return; msg->src = *src; if (bfcp_handle_response(bc, msg)) goto out; if (bc->mb && strans_cmp(&bc->st, msg)) { (void)bfcp_send(bc, &msg->src, bc->mb); goto out; } if (bc->recvh) bc->recvh(msg, bc->arg); out: mem_deref(msg); } static void tcp_recv_handler(struct mbuf *mb, void *arg) { struct bfcp_conn *bc = arg; struct bfcp_msg *msg; int err; while (mb->pos < mb->end) { err = bfcp_msg_decode(&msg, mb); if (err) return; msg->src = bc->sa_peer; if (bfcp_handle_response(bc, msg)) goto out; if (bc->mb && strans_cmp(&bc->st, msg)) { (void)bfcp_send(bc, &msg->src, bc->mb); goto out; } if (bc->recvh) bc->recvh(msg, bc->arg); out: mem_deref(msg); } } static void tcp_estab_handler(void *arg) { struct bfcp_conn *bc = arg; if (bc->estabh) bc->estabh(bc->arg); } static void tcp_close_handler(int err, void *arg) { struct bfcp_conn *bc = arg; bc->tc = NULL; if (bc->closeh) bc->closeh(err, bc->arg); } static void tcp_conn_handler(const struct sa *peer, void *arg) { struct bfcp_conn *bc = arg; if (bc->connh) { bc->connh(peer, bc->arg); } else { int err; if (bc->tc) { tcp_reject(bc->ts); return; } err = tcp_accept(&bc->tc, bc->ts, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, bc); if (err == 0) bc->sa_peer = *peer; } } /** * Create BFCP connection. For TCP, creates a listening socket for incoming * connections. * * @param bcp Pointer to BFCP connection * @param tp BFCP Transport type * @param laddr Optional listening address/port * @param tls TLS Context (optional) * @param connh Incoming connection handler (optional) * @param estabh Connection established handler (optional) * @param recvh Receive handler * @param closeh Connection closed handler (optional) * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int bfcp_listen(struct bfcp_conn **bcp, enum bfcp_transp tp, struct sa *laddr, struct tls *tls, bfcp_conn_h *connh, bfcp_estab_h *estabh, bfcp_recv_h *recvh, bfcp_close_h *closeh, void *arg) { struct bfcp_conn *bc; int err; (void)tls; if (!bcp) return EINVAL; bc = mem_zalloc(sizeof(*bc), destructor); if (!bc) return ENOMEM; bc->tp = tp; bc->connh = connh; bc->estabh = estabh; bc->recvh = recvh; bc->closeh = closeh; bc->arg = arg; switch (bc->tp) { case BFCP_UDP: err = udp_listen(&bc->us, laddr, udp_recv_handler, bc); if (err) goto out; if (laddr) { err = udp_local_get(bc->us, laddr); if (err) goto out; } break; case BFCP_TCP: err = tcp_listen(&bc->ts, laddr, tcp_conn_handler, bc); if (err) goto out; if (laddr) { err = tcp_local_get(bc->ts, laddr); if (err) goto out; } break; default: err = ENOSYS; goto out; } out: if (err) mem_deref(bc); else *bcp = bc; return err; } /** * Create BFCP connection. For TCP, creates an outgoing connection. * * @param bcp Pointer to BFCP connection * @param tp BFCP Transport type * @param laddr Optional local address/port * @param peer Remote address/port * @param estabh Connection established handler (optional) * @param recvh Receive handler * @param closeh Connection closed handler (optional) * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int bfcp_connect(struct bfcp_conn **bcp, enum bfcp_transp tp, struct sa *laddr, const struct sa *peer, bfcp_estab_h *estabh, bfcp_recv_h *recvh, bfcp_close_h *closeh, void *arg) { struct bfcp_conn *bc; int err; if (!bcp) return EINVAL; bc = mem_zalloc(sizeof(*bc), destructor); if (!bc) return ENOMEM; bc->tp = tp; bc->estabh = estabh; bc->recvh = recvh; bc->closeh = closeh; bc->arg = arg; switch (bc->tp) { case BFCP_UDP: err = udp_open(&bc->us, laddr ? sa_af(laddr) : AF_UNSPEC); if (err) goto out; udp_handler_set(bc->us, udp_recv_handler, bc); if (peer) { err = udp_connect(bc->us, peer); if (err) goto out; bc->sa_peer = *peer; } err = udp_thread_attach(bc->us); if (err) goto out; if (laddr) { err = udp_local_get(bc->us, laddr); if (err) goto out; } break; case BFCP_TCP: err = tcp_connect(&bc->tc, peer, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, bc); if (err) goto out; bc->sa_peer = *peer; if (laddr) { err = tcp_conn_local_get(bc->tc, laddr); if (err) goto out; } break; default: err = ENOSYS; goto out; } out: if (err) mem_deref(bc); else *bcp = bc; return err; } /** * Accept pending inbound TCP connection for the BFCP connection. * Only one TCP connection is supported. * * @param bc Pointer to BFCP connection * * @return 0 if success, otherwise errorcode */ int bfcp_accept(struct bfcp_conn *bc) { if (!bc) return EINVAL; if (bc->tp != BFCP_TCP) return ENOSYS; if (bc->tc) return EALREADY; return tcp_accept(&bc->tc, bc->ts, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, bc); } /** * Reject pending inbound TCP connection for the BFCP connection. * * @param bc Pointer to BFCP connection */ void bfcp_reject(struct bfcp_conn *bc) { if (!bc || bc->tp != BFCP_TCP) return; tcp_reject(bc->ts); } int bfcp_send(struct bfcp_conn *bc, const struct sa *dst, struct mbuf *mb) { if (!bc || !mb) return EINVAL; switch (bc->tp) { case BFCP_UDP: if (!dst) return EINVAL; return udp_send(bc->us, dst, mb); case BFCP_TCP: return tcp_send(bc->tc, mb); default: return ENOSYS; } } /** * Returns socket used to send messages over BFCP connection. For TCP, * TCP connection socket is returned. * * @param bc Pointer to BFCP connection * * @return Pointer to socket/connection or NULL. */ void *bfcp_sock(const struct bfcp_conn *bc) { if (!bc) return NULL; switch (bc->tp) { case BFCP_UDP: return bc->us; case BFCP_TCP: return bc->tc; default: return NULL; } } ================================================ FILE: src/bfcp/msg.c ================================================ /** * @file bfcp/msg.c BFCP Message * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "bfcp.h" enum { BFCP_HDR_SIZE = 12, }; static void destructor(void *arg) { struct bfcp_msg *msg = arg; list_flush(&msg->attrl); } static int hdr_encode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim, uint16_t len, uint32_t confid, uint16_t tid, uint16_t userid) { int err; err = mbuf_write_u8(mb, (ver << 5) | ((r ? 1 : 0) << 4)); err |= mbuf_write_u8(mb, prim); err |= mbuf_write_u16(mb, htons(len)); err |= mbuf_write_u32(mb, htonl(confid)); err |= mbuf_write_u16(mb, htons(tid)); err |= mbuf_write_u16(mb, htons(userid)); return err; } static int hdr_decode(struct bfcp_msg *msg, struct mbuf *mb) { uint8_t b; if (mbuf_get_left(mb) < BFCP_HDR_SIZE) return ENODATA; b = mbuf_read_u8(mb); msg->ver = b >> 5; msg->r = (b >> 4) & 1; msg->f = (b >> 3) & 1; msg->prim = mbuf_read_u8(mb); msg->len = ntohs(mbuf_read_u16(mb)); msg->confid = ntohl(mbuf_read_u32(mb)); msg->tid = ntohs(mbuf_read_u16(mb)); msg->userid = ntohs(mbuf_read_u16(mb)); if (msg->ver != BFCP_VER1 && msg->ver != BFCP_VER2) return EBADMSG; /* fragmentation not supported */ if (msg->f) return ENOSYS; if (mbuf_get_left(mb) < (size_t)(4*msg->len)) return ENODATA; return 0; } /** * Encode a BFCP message with variable arguments * * @param mb Mbuf to encode into * @param ver Protocol version * @param r Transaction responder flag * @param prim BFCP Primitive * @param confid Conference ID * @param tid Transaction ID * @param userid User ID * @param attrc Number of attributes * @param ap Variable argument of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_msg_vencode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim, uint32_t confid, uint16_t tid, uint16_t userid, unsigned attrc, va_list *ap) { size_t start, len; int err; if (!mb) return EINVAL; start = mb->pos; mb->pos += BFCP_HDR_SIZE; err = bfcp_attrs_vencode(mb, attrc, ap); if (err) return err; /* header */ len = mb->pos - start - BFCP_HDR_SIZE; mb->pos = start; err = hdr_encode(mb, ver, r, prim, (uint16_t)(len/4), confid, tid, userid); mb->pos += len; return err; } /** * Encode a BFCP message * * @param mb Mbuf to encode into * @param ver Protocol version * @param r Transaction responder flag * @param prim BFCP Primitive * @param confid Conference ID * @param tid Transaction ID * @param userid User ID * @param attrc Number of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_msg_encode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim, uint32_t confid, uint16_t tid, uint16_t userid, unsigned attrc, ...) { va_list ap; int err; va_start(ap, attrc); err = bfcp_msg_vencode(mb, ver, r, prim, confid, tid, userid, attrc, &ap); va_end(ap); return err; } /** * Decode a BFCP message from a buffer * * @param msgp Pointer to allocated and decoded BFCP message * @param mb Mbuf to decode from * * @return 0 if success, otherwise errorcode */ int bfcp_msg_decode(struct bfcp_msg **msgp, struct mbuf *mb) { struct bfcp_msg *msg; size_t start; int err; if (!msgp || !mb) return EINVAL; msg = mem_zalloc(sizeof(*msg), destructor); if (!msg) return ENOMEM; start = mb->pos; err = hdr_decode(msg, mb); if (err) { mb->pos = start; goto out; } err = bfcp_attrs_decode(&msg->attrl, mb, 4*msg->len, &msg->uma); if (err) goto out; out: if (err) mem_deref(msg); else *msgp = msg; return err; } /** * Get a BFCP attribute from a BFCP message * * @param msg BFCP message * @param type Attribute type * * @return Matching BFCP attribute if found, otherwise NULL */ struct bfcp_attr *bfcp_msg_attr(const struct bfcp_msg *msg, enum bfcp_attrib type) { if (!msg) return NULL; return bfcp_attrs_find(&msg->attrl, type); } /** * Apply a function handler to all attributes in a BFCP message * * @param msg BFCP message * @param h Handler * @param arg Handler argument * * @return BFCP attribute returned by handler, or NULL */ struct bfcp_attr *bfcp_msg_attr_apply(const struct bfcp_msg *msg, bfcp_attr_h *h, void *arg) { if (!msg) return NULL; return bfcp_attrs_apply(&msg->attrl, h, arg); } /** * Print a BFCP message * * @param pf Print function * @param msg BFCP message * * @return 0 if success, otherwise errorcode */ int bfcp_msg_print(struct re_printf *pf, const struct bfcp_msg *msg) { int err; if (!msg) return 0; err = re_hprintf(pf, "%s (confid=%u tid=%u userid=%u)\n", bfcp_prim_name(msg->prim), msg->confid, msg->tid, msg->userid); err |= bfcp_attrs_print(pf, &msg->attrl, 0); return err; } /** * Get the BFCP primitive name * * @param prim BFCP primitive * * @return String with BFCP primitive name */ const char *bfcp_prim_name(enum bfcp_prim prim) { switch (prim) { case BFCP_FLOOR_REQUEST: return "FloorRequest"; case BFCP_FLOOR_RELEASE: return "FloorRelease"; case BFCP_FLOOR_REQUEST_QUERY: return "FloorRequestQuery"; case BFCP_FLOOR_REQUEST_STATUS: return "FloorRequestStatus"; case BFCP_USER_QUERY: return "UserQuery"; case BFCP_USER_STATUS: return "UserStatus"; case BFCP_FLOOR_QUERY: return "FloorQuery"; case BFCP_FLOOR_STATUS: return "FloorStatus"; case BFCP_CHAIR_ACTION: return "ChairAction"; case BFCP_CHAIR_ACTION_ACK: return "ChairActionAck"; case BFCP_HELLO: return "Hello"; case BFCP_HELLO_ACK: return "HelloAck"; case BFCP_ERROR: return "Error"; case BFCP_FLOOR_REQ_STATUS_ACK: return "FloorRequestStatusAck"; case BFCP_FLOOR_STATUS_ACK: return "FloorStatusAck"; case BFCP_GOODBYE: return "Goodbye"; case BFCP_GOODBYE_ACK: return "GoodbyeAck"; default: return "???"; } } ================================================ FILE: src/bfcp/reply.c ================================================ /** * @file bfcp/reply.c BFCP Reply * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "bfcp.h" enum { BFCP_T2 = 10000, }; static void tmr_handler(void *arg) { struct bfcp_conn *bc = arg; bc->mb = mem_deref(bc->mb); } /** * Send a BFCP response * * @param bc BFCP connection * @param req BFCP request message * @param prim BFCP Primitive * @param attrc Number of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_reply(struct bfcp_conn *bc, const struct bfcp_msg *req, enum bfcp_prim prim, unsigned attrc, ...) { va_list ap; int err; if (!bc || !req) return EINVAL; bc->mb = mem_deref(bc->mb); tmr_cancel(&bc->tmr2); bc->mb = mbuf_alloc(64); if (!bc->mb) return ENOMEM; va_start(ap, attrc); err = bfcp_msg_vencode(bc->mb, req->ver, true, prim, req->confid, req->tid, req->userid, attrc, &ap); va_end(ap); if (err) goto out; bc->mb->pos = 0; err = bfcp_send(bc, &req->src, bc->mb); if (err) goto out; bc->st.prim = req->prim; bc->st.confid = req->confid; bc->st.tid = req->tid; bc->st.userid = req->userid; tmr_start(&bc->tmr2, BFCP_T2, tmr_handler, bc); out: if (err) bc->mb = mem_deref(bc->mb); return err; } /** * Send a BFCP error response with details * * @param bc BFCP connection * @param req BFCP request message * @param code Error code * @param details Error details * @param len Details length * * @return 0 if success, otherwise errorcode */ int bfcp_edreply(struct bfcp_conn *bc, const struct bfcp_msg *req, enum bfcp_err code, const uint8_t *details, size_t len) { struct bfcp_errcode errcode; errcode.code = code; errcode.details = (uint8_t *)details; errcode.len = len; return bfcp_reply(bc, req, BFCP_ERROR, 1, BFCP_ERROR_CODE, 0, &errcode); } /** * Send a BFCP error response * * @param bc BFCP connection * @param req BFCP request message * @param code Error code * * @return 0 if success, otherwise errorcode */ int bfcp_ereply(struct bfcp_conn *bc, const struct bfcp_msg *req, enum bfcp_err code) { return bfcp_edreply(bc, req, code, NULL, 0); } ================================================ FILE: src/bfcp/request.c ================================================ /** * @file bfcp/request.c BFCP Request * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "bfcp.h" enum { BFCP_T1 = 500, BFCP_TXC = 4, }; struct bfcp_ctrans { struct le le; struct sa dst; struct mbuf *mb; bfcp_resp_h *resph; void *arg; uint32_t confid; uint16_t userid; uint16_t tid; }; static void tmr_handler(void *arg); static void dummy_resp_handler(int err, const struct bfcp_msg *msg, void *arg) { (void)err; (void)msg; (void)arg; } static void destructor(void *arg) { struct bfcp_ctrans *ct = arg; list_unlink(&ct->le); mem_deref(ct->mb); } static void dispatch(struct bfcp_conn *bc) { struct le *le = bc->ctransl.head; while (le) { struct bfcp_ctrans *ct = le->data; int err; le = le->next; err = bfcp_send(bc, &ct->dst, ct->mb); if (err) { ct->resph(err, NULL, ct->arg); mem_deref(ct); continue; } tmr_start(&bc->tmr1, BFCP_T1, tmr_handler, bc); bc->txc = 1; break; } } static void tmr_handler(void *arg) { struct bfcp_conn *bc = arg; struct bfcp_ctrans *ct; uint32_t timeout; int err; ct = list_ledata(bc->ctransl.head); if (!ct) return; timeout = BFCP_T1<txc; if (++bc->txc > BFCP_TXC) { err = ETIMEDOUT; goto out; } err = bfcp_send(bc, &ct->dst, ct->mb); if (err) goto out; tmr_start(&bc->tmr1, timeout, tmr_handler, bc); return; out: ct->resph(err, NULL, ct->arg); mem_deref(ct); dispatch(bc); } bool bfcp_handle_response(struct bfcp_conn *bc, const struct bfcp_msg *msg) { struct bfcp_ctrans *ct; if (!bc || !msg) return false; ct = list_ledata(bc->ctransl.head); if (!ct) return false; if (msg->tid != ct->tid) return false; if (msg->confid != ct->confid) return false; if (msg->userid != ct->userid) return false; tmr_cancel(&bc->tmr1); ct->resph(0, msg, ct->arg); mem_deref(ct); dispatch(bc); return true; } int bfcp_vrequest(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver, enum bfcp_prim prim, uint32_t confid, uint16_t userid, bfcp_resp_h *resph, void *arg, unsigned attrc, va_list *ap) { struct bfcp_ctrans *ct; int err; if (!bc || !dst) return EINVAL; ct = mem_zalloc(sizeof(*ct), destructor); if (!ct) return ENOMEM; if (bc->tid == 0) bc->tid = 1; ct->dst = *dst; ct->confid = confid; ct->userid = userid; ct->tid = bc->tid++; ct->resph = resph ? resph : dummy_resp_handler; ct->arg = arg; ct->mb = mbuf_alloc(128); if (!ct->mb) { err = ENOMEM; goto out; } err = bfcp_msg_vencode(ct->mb, ver, false, prim, confid, ct->tid, userid, attrc, ap); if (err) goto out; ct->mb->pos = 0; if (!bc->ctransl.head) { err = bfcp_send(bc, &ct->dst, ct->mb); if (err) goto out; tmr_start(&bc->tmr1, BFCP_T1, tmr_handler, bc); bc->txc = 1; } list_append(&bc->ctransl, &ct->le, ct); out: if (err) mem_deref(ct); return err; } /** * Send a BFCP request * * @param bc BFCP connection * @param dst Destination address * @param ver BFCP Version * @param prim BFCP Primitive * @param confid Conference ID * @param userid User ID * @param resph Response handler * @param arg Response handler argument * @param attrc Number of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_request(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver, enum bfcp_prim prim, uint32_t confid, uint16_t userid, bfcp_resp_h *resph, void *arg, unsigned attrc, ...) { va_list ap; int err; va_start(ap, attrc); err = bfcp_vrequest(bc, dst, ver, prim, confid, userid, resph, arg, attrc, &ap); va_end(ap); return err; } /** * Send a BFCP notification/subsequent response * * @param bc BFCP connection * @param dst Destination address * @param ver BFCP Version * @param prim BFCP Primitive * @param confid Conference ID * @param userid User ID * @param attrc Number of attributes * * @return 0 if success, otherwise errorcode */ int bfcp_notify(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver, enum bfcp_prim prim, uint32_t confid, uint16_t userid, unsigned attrc, ...) { va_list ap; int err; va_start(ap, attrc); err = bfcp_vrequest(bc, dst, ver, prim, confid, userid, NULL, NULL, attrc, &ap); va_end(ap); return err; } ================================================ FILE: src/btrace/btrace.c ================================================ /** * @file btrace.c Backtrace API * * Copyright (C) 2023 Sebastian Reimers */ #ifdef HAVE_UNISTD_H #include #endif #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #endif #include #include #include #include #include #define DEBUG_MODULE "btrace" #define DEBUG_LEVEL 5 #include enum print_type { BTRACE_CSV, BTRACE_NEWLINE, BTRACE_JSON }; static int print_debug(struct re_printf *pf, struct btrace *bt, enum print_type type) { #if (!defined(HAVE_EXECINFO) && !defined(WIN32)) || defined(RELEASE) (void)pf; (void)bt; (void)type; return 0; #elif defined(WIN32) SYMBOL_INFO *symbol; DWORD displacement = 0; IMAGEHLP_LINE line; HANDLE hProcess = GetCurrentProcess(); (void)type; /* Initialize the symbol buffer. */ symbol = mem_zalloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), NULL); if (!symbol) return ENOMEM; SymInitialize(hProcess, NULL, TRUE); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); /* Initialize the line buffer. */ ZeroMemory(&line, sizeof(line)); line.SizeOfStruct = sizeof(line); for (size_t i = 0; i < bt->len; i++) { SymFromAddr(hProcess, (DWORD64)(bt->stack[i]), NULL, symbol); SymGetLineFromAddr(hProcess, (DWORD64)(bt->stack[i]), &displacement, &line); re_hprintf(pf, "%zu: %s (%s:%lu)\n", i, symbol->Name, line.FileName, line.LineNumber); } mem_deref(symbol); SymCleanup(hProcess); return 0; #else char **symbols; if (!pf || !bt) return EINVAL; if (!bt->len) return 0; #if defined(FREEBSD) || defined(OPENBSD) symbols = backtrace_symbols(bt->stack, bt->len); #else symbols = backtrace_symbols(bt->stack, (int)bt->len); #endif if (!symbols) return 0; switch (type) { case BTRACE_CSV: for (size_t j = 0; j < bt->len; j++) { re_hprintf(pf, "%s%s", symbols[j], ((j + 1) < bt->len) ? ", " : ""); } break; case BTRACE_NEWLINE: for (size_t j = 0; j < bt->len; j++) { re_hprintf(pf, "%s\n", symbols[j]); #ifdef LINUX struct pl file = PL_INIT; struct pl addr = PL_INIT; char addr2l[512] = {0}; char addr2l_out[256] = {0}; FILE *pipe; re_regex(symbols[j], str_len(symbols[j]), "[^(]+([^)]+", &file, &addr); (void)re_snprintf(addr2l, sizeof(addr2l), "addr2line -p -f -e %r %r", &file, &addr); pipe = popen(addr2l, "r"); if (!pipe) continue; while (fgets(addr2l_out, sizeof(addr2l_out), pipe)) { re_hprintf(pf, "\t%s", addr2l_out); } pclose(pipe); #endif } break; case BTRACE_JSON: re_hprintf(pf, "["); for (size_t j = 0; j < bt->len; j++) { re_hprintf(pf, "\"%s\"%s", symbols[j], ((j + 1) < bt->len) ? ", " : ""); } re_hprintf(pf, "]"); break; } free(symbols); return 0; #endif } /** * Print debug backtrace (comma separated) * * @param pf Print function for debug output * @param bt Backtrace object * * @return 0 if success, otherwise errorcode */ int btrace_print(struct re_printf *pf, struct btrace *bt) { return print_debug(pf, bt, BTRACE_CSV); } /** * Print debug backtrace with newlines * * @param pf Print function for debug output * @param bt Backtrace object * * @return 0 if success, otherwise errorcode */ int btrace_println(struct re_printf *pf, struct btrace *bt) { return print_debug(pf, bt, BTRACE_NEWLINE); } /** * Print debug backtrace as json array * * @param pf Print function for debug output * @param bt Backtrace object * * @return 0 if success, otherwise errorcode */ int btrace_print_json(struct re_printf *pf, struct btrace *bt) { return print_debug(pf, bt, BTRACE_JSON); } ================================================ FILE: src/conf/conf.c ================================================ /** * @file conf.c Configuration file parser * * Copyright (C) 2010 Creytiv.com */ #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_IO_H #include #endif #include #include #include #include #include #ifdef WIN32 #define open _open #define read _read #define close _close #endif /** * Defines a Configuration state. The configuration data is stored in a * linear buffer which can be used for reading key-value pairs of * configuration data. The config data can be strings or numeric values. */ struct conf { struct mbuf *mb; }; static int load_file(struct mbuf *mb, const char *filename) { int err = 0, fd = open(filename, O_RDONLY); if (fd < 0) return errno; for (;;) { uint8_t buf[1024]; const ssize_t n = read(fd, (void *)buf, sizeof(buf)); if (n < 0) { err = errno; break; } else if (n == 0) break; if ((size_t)n > sizeof(buf)) { err = EBADMSG; break; } err |= mbuf_write_mem(mb, buf, n); } (void)close(fd); return err; } static void conf_destructor(void *data) { struct conf *conf = data; mem_deref(conf->mb); } /** * Load configuration from file * * @param confp Configuration object to be allocated * @param filename Name of configuration file * * @return 0 if success, otherwise errorcode */ int conf_alloc(struct conf **confp, const char *filename) { struct conf *conf; int err = 0; if (!confp) return EINVAL; conf = mem_zalloc(sizeof(*conf), conf_destructor); if (!conf) return ENOMEM; conf->mb = mbuf_alloc(1024); if (!conf->mb) { err = ENOMEM; goto out; } err |= mbuf_write_u8(conf->mb, '\n'); if (filename) err |= load_file(conf->mb, filename); out: if (err) mem_deref(conf); else *confp = conf; return err; } /** * Allocate configuration from a buffer * * @param confp Configuration object to be allocated * @param buf Buffer containing configuration * @param sz Size of configuration buffer * * @return 0 if success, otherwise errorcode */ int conf_alloc_buf(struct conf **confp, const uint8_t *buf, size_t sz) { struct conf *conf; int err; err = conf_alloc(&conf, NULL); if (err) return err; err = mbuf_write_mem(conf->mb, buf, sz); if (err) mem_deref(conf); else *confp = conf; return err; } /** * Get the value of a configuration item PL string * * @param conf Configuration object * @param name Name of config item key * @param pl Value of config item, if present * * @return 0 if success, otherwise errorcode */ int conf_get(const struct conf *conf, const char *name, struct pl *pl) { char expr[512]; struct pl spl; if (!conf || !name || !pl) return EINVAL; spl.p = (const char *)conf->mb->buf; spl.l = conf->mb->end; (void)re_snprintf(expr, sizeof(expr), "[\r\n]+[ \t]*%s[ \t]+[~ \t\r\n]+", name); return re_regex(spl.p, spl.l, expr, NULL, NULL, NULL, pl); } /** * Get the value of a configuration item string * * @param conf Configuration object * @param name Name of config item key * @param str Value of config item, if present * @param size Size of string to store value * * @return 0 if success, otherwise errorcode */ int conf_get_str(const struct conf *conf, const char *name, char *str, size_t size) { struct pl pl; int err; if (!conf || !name || !str || !size) return EINVAL; err = conf_get(conf, name, &pl); if (err) return err; return pl_strcpy(&pl, str, size); } /** * Get the numeric value of a configuration item * * @param conf Configuration object * @param name Name of config item key * @param num Returned numeric value of config item, if present * * @return 0 if success, otherwise errorcode */ int conf_get_u32(const struct conf *conf, const char *name, uint32_t *num) { struct pl pl; int err; if (!conf || !name || !num) return EINVAL; err = conf_get(conf, name, &pl); if (err) return err; *num = pl_u32(&pl); return 0; } /** * Get the numeric signed value of a configuration item * * @param conf Configuration object * @param name Name of config item key * @param num Returned numeric value of config item, if present * * @return 0 if success, otherwise errorcode */ int conf_get_i32(const struct conf *conf, const char *name, int32_t *num) { struct pl pl; int err; if (!conf || !name || !num) return EINVAL; err = conf_get(conf, name, &pl); if (err) return err; *num = pl_i32(&pl); return 0; } /** * Get the numeric floating point value of a configuration item * * @param conf Configuration object * @param name Name of config item key * @param num Returned numeric value of config item, if present * * @return 0 if success, otherwise errorcode */ int conf_get_float(const struct conf *conf, const char *name, double *num) { struct pl opt; int err; if (!conf || !name || !num) return EINVAL; err = conf_get(conf, name, &opt); if (err) return err; *num = pl_float(&opt); return 0; } /** * Get the boolean value of a configuration item * * @param conf Configuration object * @param name Name of config item key * @param val Returned boolean value of config item, if present * * @return 0 if success, otherwise errorcode */ int conf_get_bool(const struct conf *conf, const char *name, bool *val) { struct pl pl; int err; if (!conf || !name || !val) return EINVAL; err = conf_get(conf, name, &pl); if (err) return err; if (!pl_strcasecmp(&pl, "true")) *val = true; else if (!pl_strcasecmp(&pl, "yes")) *val = true; else if (!pl_strcasecmp(&pl, "1")) *val = true; else *val = false; return 0; } /** * Apply a function handler to all config items of a certain key * * @param conf Configuration object * @param name Name of config item key * @param ch Config item handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int conf_apply(const struct conf *conf, const char *name, conf_h *ch, void *arg) { char expr[512]; struct pl pl, val; int err = 0; if (!conf || !name || !ch) return EINVAL; pl.p = (const char *)conf->mb->buf; pl.l = conf->mb->end; (void)re_snprintf(expr, sizeof(expr), "[\r\n]+[ \t]*%s[ \t]+[~ \t\r\n]+", name); while (!re_regex(pl.p, pl.l, expr, NULL, NULL, NULL, &val)) { err = ch(&val, arg); if (err) break; pl.l -= val.p + val.l - pl.p; pl.p = val.p + val.l; } return err; } ================================================ FILE: src/crc32/crc32.c ================================================ /** * @file crc32.c CRC32 Implementation * * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or * code or tables extracted from it, as desired without restriction. */ /* * First, the polynomial itself and its table of feedback terms. The * polynomial is * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 * * Note that we take it "backwards" and put the highest-order term in * the lowest-order bit. The X^32 term is "implied"; the LSB is the * X^31 term, etc. The X^0 term (usually shown as "+1") results in * the MSB being 1 * * Note that the usual hardware shift register implementation, which * is what we're using (we're merely optimizing it by doing eight-bit * chunks at a time) shifts bits into the lowest-order term. In our * implementation, that means shifting towards the right. Why do we * do it this way? Because the calculated CRC must be transmitted in * order from highest-order term to lowest-order term. UARTs transmit * characters in order from LSB to MSB. By storing the CRC this way * we hand it to the UART in the order low-byte to high-byte; the UART * sends each low-bit to high-bit; and the result is transmission bit * by bit from highest- to lowest-order term without requiring any bit * shuffling on our part. Reception works similarly * * The feedback terms table consists of 256, 32-bit entries. Notes * * The table can be generated at runtime if desired; code to do so * is shown later. It might not be obvious, but the feedback * terms simply represent the results of eight shift/xor opera * tions for all combinations of data and CRC register values * * The values must be right-shifted by eight bits by the "updcrc * logic; the shift must be unsigned (bring in zeroes). On some * hardware you could probably optimize the shift in assembler by * using byte-swap instructions * polynomial $edb88320 * * * CRC32 code derived from work by Gary S. Brown. */ #include #include #ifdef USE_ZLIB #include #else static const uint32_t crc32_tab[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; #endif /** * A function that calculates the CRC-32 based on the table above is * given below for documentation purposes. An equivalent implementation * of this function that's actually used in the kernel can be found * in sys/libkern.h, where it can be inlined. * * @param crc Initial CRC value * @param buf Buffer to generate CRC from * @param size Number of bytes in buffer * * @return CRC value */ uint32_t re_crc32(uint32_t crc, const void *buf, uint32_t size) { #ifdef USE_ZLIB return (uint32_t)crc32(crc, buf, size); #else const uint8_t *p = buf; crc = ~crc; while (size--) crc = crc32_tab[(crc ^ *p++) & 0xff] ^ (crc >> 8); return crc ^ ~0U; #endif } ================================================ FILE: src/dbg/dbg.c ================================================ /** * @file dbg.c Debug printing * * Copyright (C) 2010 Creytiv.com */ #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #define DEBUG_MODULE "dbg" #define DEBUG_LEVEL 0 #include /** Debug configuration */ static struct { uint64_t tick; /**< Init ticks */ int level; /**< Current debug level */ enum dbg_flags flags; /**< Debug flags */ dbg_print_h *ph; /**< Optional print handler */ void *arg; /**< Handler argument */ } dbg = { 0, DBG_INFO, DBG_ANSI, NULL, NULL, }; static once_flag flag = ONCE_FLAG_INIT; static mtx_t mtx; static void mem_lock_init(void) { mtx_init(&mtx, mtx_plain); } static inline void dbg_lock(void) { call_once(&flag, mem_lock_init); mtx_lock(&mtx); } static inline void dbg_unlock(void) { mtx_unlock(&mtx); } /** * Initialise debug printing * * @param level Debug level * @param flags Debug flags */ void dbg_init(int level, enum dbg_flags flags) { dbg_lock(); dbg.tick = tmr_jiffies(); dbg.level = level; dbg.flags = flags; dbg_unlock(); } /** * Set optional debug print handler * * @param ph Print handler * @param arg Handler argument */ void dbg_handler_set(dbg_print_h *ph, void *arg) { dbg_lock(); dbg.ph = ph; dbg.arg = arg; dbg_unlock(); } /* NOTE: This function should not allocate memory */ static void dbg_vprintf(int level, const char *fmt, va_list ap) { dbg_lock(); if (level > dbg.level) goto out; /* Print handler? */ if (dbg.ph) goto out; if (dbg.flags & DBG_ANSI) { switch (level) { case DBG_WARNING: (void)re_fprintf(stderr, "\x1b[31m"); /* Red */ break; case DBG_NOTICE: (void)re_fprintf(stderr, "\x1b[33m"); /* Yellow */ break; case DBG_INFO: (void)re_fprintf(stderr, "\x1b[32m"); /* Green */ break; default: break; } } if (dbg.flags & DBG_TIME) { const uint64_t ticks = tmr_jiffies(); if (0 == dbg.tick) dbg.tick = tmr_jiffies(); (void)re_fprintf(stderr, "[%09llu] ", ticks - dbg.tick); } (void)re_vfprintf(stderr, fmt, ap); if (dbg.flags & DBG_ANSI && level < DBG_DEBUG) (void)re_fprintf(stderr, "\x1b[;m"); out: dbg_unlock(); } /* Formatted output to print handler */ static void dbg_fmt_vprintf(int level, const char *fmt, va_list ap) { char buf[256]; dbg_lock(); int dbg_level = dbg.level; dbg_print_h *ph = dbg.ph; void *arg = dbg.arg; dbg_unlock(); if (level > dbg_level) return; /* Print handler? */ if (ph) { int len = re_vsnprintf(buf, sizeof(buf), fmt, ap); if (len <= 0) return; ph(level, buf, len, arg); } } /** * Print a formatted debug message * * @param level Debug level * @param fmt Formatted string */ void dbg_printf(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); dbg_vprintf(level, fmt, ap); va_end(ap); va_start(ap, fmt); dbg_fmt_vprintf(level, fmt, ap); va_end(ap); } /** * Get the name of the debug level * * @param level Debug level * * @return String with debug level name */ const char *dbg_level_str(int level) { switch (level) { case DBG_EMERG: return "EMERGENCY"; case DBG_ALERT: return "ALERT"; case DBG_CRIT: return "CRITICAL"; case DBG_ERR: return "ERROR"; case DBG_WARNING: return "WARNING"; case DBG_NOTICE: return "NOTICE"; case DBG_INFO: return "INFO"; case DBG_DEBUG: return "DEBUG"; default: return "???"; } } ================================================ FILE: src/dd/dd.c ================================================ /** * @file dd.c Dependency Descriptor (DD) -- decoder * * Copyright (C) 2023 Alfred E. Heggestad */ #include #include #include #define dd_f(n) get_bits(gb, (n)) static const char *dti_name(enum dd_dti dti) { switch (dti) { case DD_DTI_NOT_PRESENT: return "NOT_PRESENT"; case DD_DTI_DISCARDABLE: return "DISCARDABLE"; case DD_DTI_SWITCH: return "SWITCH"; case DD_DTI_REQUIRED: return "REQUIRED"; } return "???"; } #if 0 static const char *next_layer_name(enum dd_next_layer_idc idc) { switch (idc) { case DD_SAME_LAYER: return "Same"; case DD_NEXT_TEMPORAL_LAYER: return "Temporal"; case DD_NEXT_SPATIAL_LAYER: return "Spatial"; case DD_NO_MORE_TEMPLATES: return "None"; } return "???"; } #endif static int mandatory_descriptor_fields(struct dd *dd, struct getbit *gb) { if (getbit_get_left(gb) < 24) return EBADMSG; dd->start_of_frame = dd_f(1); dd->end_of_frame = dd_f(1); dd->frame_dependency_template_id = dd_f(6); dd->frame_number = dd_f(16); return 0; } /* * 0 next template has the same spatial ID and temporal ID as current template * * 1 next template has the same spatial ID and temporal ID plus 1 compared * with the current Frame dependency template. * * 2 next Frame dependency template has temporal ID equal to 0 and * spatial ID plus 1 compared with the current Frame dependency template. * * 3 No more Frame dependency templates are present in the * Frame dependency structure. */ static int template_layers(struct dd *dd, struct getbit *gb) { uint8_t temporalId = 0; uint8_t spatialId = 0; uint8_t TemplateCnt = 0; uint8_t MaxTemporalId = 0; uint8_t next_layer_idc = DD_SAME_LAYER; do { if (TemplateCnt >= DD_MAX_TEMPLATES) return EOVERFLOW; dd->template_spatial_id[TemplateCnt] = spatialId; dd->template_temporal_id[TemplateCnt] = temporalId; ++TemplateCnt; if (getbit_get_left(gb) < 2) return EBADMSG; next_layer_idc = dd_f(2); /* next_layer_idc == 0 - same sid and tid */ if (next_layer_idc == DD_NEXT_TEMPORAL_LAYER) { ++temporalId; if (temporalId > MaxTemporalId) { MaxTemporalId = temporalId; } } else if (next_layer_idc == DD_NEXT_SPATIAL_LAYER) { temporalId = 0; ++spatialId; } } while (next_layer_idc != DD_NO_MORE_TEMPLATES); dd->max_spatial_id = spatialId; dd->template_cnt = TemplateCnt; return 0; } static int template_dtis(struct dd *dd, struct getbit *gb) { for (uint8_t templateIndex = 0; templateIndex < dd->template_cnt; templateIndex++) { if (templateIndex >= DD_MAX_TEMPLATES) return EOVERFLOW; for (uint8_t dtIndex = 0; dtIndex < dd->dt_cnt; dtIndex++) { if (getbit_get_left(gb) < 2) return EBADMSG; /* See table A.1 below for meaning of DTI values. */ dd->template_dti[templateIndex][dtIndex] = dd_f(2); } } return 0; } static int template_fdiffs(struct dd *dd, struct getbit *gb) { for (uint8_t templateIndex = 0; templateIndex < dd->template_cnt; templateIndex++) { uint8_t fdiffCnt = 0; if (getbit_get_left(gb) < 1) return EBADMSG; bool fdiff_follows_flag = dd_f(1); while (fdiff_follows_flag) { if (getbit_get_left(gb) < 5) return EBADMSG; uint8_t fdiff_minus_one = dd_f(4); uint8_t fdiff = fdiff_minus_one + 1; dd->template_fdiff[templateIndex][fdiffCnt] = fdiff; ++fdiffCnt; fdiff_follows_flag = dd_f(1); } dd->template_fdiff_cnt[templateIndex] = fdiffCnt; } return 0; } static int template_chains(struct dd *dd, struct getbit *gb) { /* todo: check bits left */ dd->chain_cnt = getbit_read_ns(gb, dd->dt_cnt + 1); if (dd->chain_cnt == 0) return 0; for (uint8_t dtIndex = 0; dtIndex < dd->dt_cnt; dtIndex++) { uint8_t v = getbit_read_ns(gb, dd->chain_cnt); dd->decode_target_protected_by[dtIndex] = v; } for (uint8_t templateIndex = 0; templateIndex < dd->template_cnt; templateIndex++) { for (uint8_t chainIndex = 0; chainIndex < dd->chain_cnt; chainIndex++) { if (getbit_get_left(gb) < 4) return EBADMSG; dd->template_chain_fdiff[templateIndex][chainIndex] = dd_f(4); } } return 0; } static int render_resolutions(struct dd *dd, struct getbit *gb) { for (uint8_t spatial_id = 0; spatial_id <= dd->max_spatial_id; spatial_id++) { if (getbit_get_left(gb) < 32) return EBADMSG; dd->max_render_width_minus_1[spatial_id] = dd_f(16); dd->max_render_height_minus_1[spatial_id] = dd_f(16); ++dd->render_count; } return 0; } static int template_dependency_structure(struct dd *dd, struct getbit *gb) { if (getbit_get_left(gb) < 11) return EBADMSG; dd->template_id_offset = dd_f(6); uint8_t dt_cnt_minus_one = dd_f(5); dd->dt_cnt = dt_cnt_minus_one + 1; int err = template_layers(dd, gb); if (err) return err; err = template_dtis(dd, gb); if (err) return err; err = template_fdiffs(dd, gb); if (err) return err; template_chains(dd, gb); /* note: decode_target_layers() */ if (getbit_get_left(gb) < 1) return EBADMSG; dd->resolutions_present_flag = dd_f(1); if (dd->resolutions_present_flag) { err = render_resolutions(dd, gb); if (err) return err; } return 0; } static int extended_descriptor_fields(struct dd *dd, struct getbit *gb) { if (getbit_get_left(gb) < 5) return EBADMSG; dd->template_dependency_structure_present_flag = dd_f(1); dd->active_decode_targets_present_flag = dd_f(1); dd->custom_dtis_flag = dd_f(1); dd->custom_fdiffs_flag = dd_f(1); dd->custom_chains_flag = dd_f(1); if (dd->template_dependency_structure_present_flag) { int err = template_dependency_structure(dd, gb); if (err) return err; dd->active_decode_targets_bitmask = (1u << dd->dt_cnt) - 1; } if (dd->active_decode_targets_present_flag) { dd->active_decode_targets_bitmask = dd_f(dd->dt_cnt); } return 0; } static void no_extended_descriptor_fields(struct dd *dd) { dd->custom_dtis_flag = 0; dd->custom_fdiffs_flag = 0; dd->custom_chains_flag = 0; } int dd_decode(struct dd *dd, const uint8_t *buf, size_t sz) { if (!dd || !buf) return EINVAL; memset(dd, 0, sizeof(*dd)); struct getbit gb; getbit_init(&gb, buf, sz*8); int err = mandatory_descriptor_fields(dd, &gb); if (err) return err; if (sz > 3) { err = extended_descriptor_fields(dd, &gb); if (err) return err; dd->ext = true; } else { no_extended_descriptor_fields(dd); } return 0; } int dd_print(struct re_printf *pf, const struct dd *dd) { if (!dd) return 0; int err = re_hprintf(pf, "~~~~ DD: ~~~~\n"); err |= re_hprintf(pf, ".... start=%d, end=%d," " frame_dependency_template_id=%u," " frame_number=%u\n", dd->start_of_frame, dd->end_of_frame, dd->frame_dependency_template_id, dd->frame_number); err |= re_hprintf(pf, ".... ext: %d\n", dd->ext); if (err) return err; if (dd->ext) { err = re_hprintf(pf, ".... template_dependency_structure_present: %u\n", dd->template_dependency_structure_present_flag); err |= re_hprintf(pf, ".... active_decode_targets_present_flag: %u\n", dd->active_decode_targets_present_flag); err |= re_hprintf(pf, ".... custom_dtis_flag: %u\n", dd->custom_dtis_flag); err |= re_hprintf(pf, ".... custom_fdiffs_flag: %u\n", dd->custom_fdiffs_flag); err |= re_hprintf(pf, ".... custom_chains_flag: %u\n", dd->custom_chains_flag); err |= re_hprintf(pf, "\n"); err |= re_hprintf(pf, ".... active_decode_targets_bitmask: 0x%x\n", dd->active_decode_targets_bitmask); err |= re_hprintf(pf, ".... template_id_offset: %u\n", dd->template_id_offset); err |= re_hprintf(pf, ".... dt_cnt: %u\n", dd->dt_cnt); err |= re_hprintf(pf, ".... template_cnt: %u\n", dd->template_cnt); err |= re_hprintf(pf, ".... max_spatial_id: %u\n", dd->max_spatial_id); err |= re_hprintf(pf, "\n"); if (err) return err; err = re_hprintf(pf, ".... template spatial/temporal ids:\n"); for (uint8_t i=0; itemplate_cnt; i++) { err |= re_hprintf(pf, ".... [%u] spatial=%u temporal=%u\n", i, dd->template_spatial_id[i], dd->template_temporal_id[i]); } err |= re_hprintf(pf, "\n"); err |= re_hprintf(pf, ".... resolutions_present_flag: %u\n", dd->resolutions_present_flag); err |= re_hprintf(pf, ".... render_count: %u\n", dd->render_count); for (uint8_t i = 0; i < dd->render_count; i++) { err |= re_hprintf(pf, ".... max_render %u: %u x %u\n", i, dd->max_render_width_minus_1[i] + 1, dd->max_render_height_minus_1[i] + 1); } err |= re_hprintf(pf, "\n"); for (uint8_t i = 0; i < dd->template_cnt; i++) { uint8_t fdiffCnt = dd->template_fdiff_cnt[i]; err |= re_hprintf(pf, ".... [%u] template_fdiff_cnt: %u", i, fdiffCnt); for (uint8_t j = 0; j < fdiffCnt; j++) { uint8_t fdiff; fdiff = dd->template_fdiff[i][j]; err |= re_hprintf(pf, " ", fdiff); } err |= re_hprintf(pf, "\n"); } err |= re_hprintf(pf, "\n"); err |= re_hprintf(pf, ".... chain_cnt: %u\n", dd->chain_cnt); err |= re_hprintf(pf, "\n"); err |= re_hprintf(pf, ".... template_dti: 2D\n"); for (uint8_t tix = 0; tix < dd->template_cnt; tix++) { for (uint8_t dtix = 0; dtix < dd->dt_cnt; dtix++) { uint8_t val = dd->template_dti[tix][dtix]; err |= re_hprintf(pf, ".... DTI: [%u][%u] %u %s\n", tix, dtix, val, dti_name(val)); } } } err |= re_hprintf(pf, "~~~~~~~~~~~~\n"); err |= re_hprintf(pf, "\n"); return err; } ================================================ FILE: src/dd/dd_enc.c ================================================ /** * @file dd_enc.c Dependency Descriptor (DD) -- encoder * * Copyright (C) 2023 Alfred E. Heggestad */ #include #include #include #define DEBUG_MODULE "dd" #define DEBUG_LEVEL 5 #include static int mandatory_descriptor_fields(struct putbit *pb, const struct dd *dd) { int err = 0; err |= putbit_one(pb, dd->start_of_frame); err |= putbit_one(pb, dd->end_of_frame); err |= putbit_write(pb, 6, dd->frame_dependency_template_id); err |= putbit_write(pb, 16, dd->frame_number); return err; } static uint8_t next_layer(const struct dd *dd, unsigned prev, unsigned next) { if (dd->template_spatial_id[next] == dd->template_spatial_id[prev] && dd->template_temporal_id[next] == dd->template_temporal_id[prev]) { return DD_SAME_LAYER; } else if (dd->template_spatial_id[next] == dd->template_spatial_id[prev] && dd->template_temporal_id[next] == dd->template_temporal_id[prev] + 1) { return DD_NEXT_TEMPORAL_LAYER; } else if (dd->template_spatial_id[next] == dd->template_spatial_id[prev] + 1 && dd->template_temporal_id[next] == 0) { return DD_NEXT_SPATIAL_LAYER; } return DD_NO_MORE_TEMPLATES; } static int template_layers(struct putbit *pb, const struct dd *dd) { int err = 0; for (unsigned i = 1; i < dd->template_cnt; ++i) { uint8_t next_layer_idc = next_layer(dd, i - 1, i); if (next_layer_idc == DD_NO_MORE_TEMPLATES) return EBADMSG; err |= putbit_write(pb, 2, next_layer_idc); } /* end of layers */ err |= putbit_write(pb, 2, DD_NO_MORE_TEMPLATES); return err; } static int template_dtis(struct putbit *pb, const struct dd *dd) { for (uint8_t templateIndex = 0; templateIndex < dd->template_cnt; templateIndex++) { for (uint8_t dtIndex = 0; dtIndex < dd->dt_cnt; dtIndex++) { /* See table A.1 below for meaning of DTI values. */ uint8_t v = dd->template_dti[templateIndex][dtIndex]; int err = putbit_write(pb, 2, v); if (err) return err; } } return 0; } static int template_fdiffs(struct putbit *pb, const struct dd *dd) { int err; for (uint8_t templateIndex = 0; templateIndex < dd->template_cnt; templateIndex++) { uint8_t fdiffCnt = dd->template_fdiff_cnt[templateIndex]; for (uint8_t j = 0; j < fdiffCnt; j++) { uint8_t fdiff; fdiff = dd->template_fdiff[templateIndex][j]; /* fdiff_follows_flag */ err = putbit_write(pb, 1, true); if (err) return err; err = putbit_write(pb, 4, fdiff - 1); if (err) return err; } /* fdiff_follows_flag */ err = putbit_write(pb, 1, false); if (err) return err; } return 0; } static int template_chains(struct putbit *pb, const struct dd *dd) { int err = putbit_write_ns(pb, dd->dt_cnt + 1, dd->chain_cnt); if (err) return err; if (dd->chain_cnt == 0) return 0; for (uint8_t dtIndex = 0; dtIndex < dd->dt_cnt; dtIndex++) { uint8_t val = dd->decode_target_protected_by[dtIndex]; err = putbit_write_ns(pb, dd->chain_cnt, val); if (err) return err; } for (uint8_t templateIndex = 0; templateIndex < dd->template_cnt; templateIndex++) { for (uint8_t chainIndex = 0; chainIndex < dd->chain_cnt; chainIndex++) { uint8_t val; val=dd->template_chain_fdiff[templateIndex][chainIndex]; err = putbit_write(pb, 4, val); if (err) return err; } } return 0; } static int render_resolutions(struct putbit *pb, const struct dd *dd) { for (uint8_t i=0; irender_count; i++) { putbit_write(pb, 16, dd->max_render_width_minus_1[i]); putbit_write(pb, 16, dd->max_render_height_minus_1[i]); } return 0; } static int template_dependency_structure(struct putbit *pb, const struct dd *dd) { int err; uint8_t dt_cnt_minus_one = dd->dt_cnt - 1; err = putbit_write(pb, 6, dd->template_id_offset); err |= putbit_write(pb, 5, dt_cnt_minus_one); if (err) return err; err = template_layers(pb, dd); if (err) return err; err = template_dtis(pb, dd); if (err) return err; err = template_fdiffs(pb, dd); if (err) return err; err = template_chains(pb, dd); if (err) return err; err = putbit_one(pb, dd->resolutions_present_flag); if (err) return err; if (dd->resolutions_present_flag) { render_resolutions(pb, dd); } /* XXX decode_target_layers() */ return 0; } static int extended_descriptor_fields(struct putbit *pb, const struct dd *dd) { int err = 0; err |= putbit_one(pb, dd->template_dependency_structure_present_flag); err |= putbit_one(pb, dd->active_decode_targets_present_flag); err |= putbit_one(pb, dd->custom_dtis_flag); err |= putbit_one(pb, dd->custom_fdiffs_flag); err |= putbit_one(pb, dd->custom_chains_flag); if (err) return err; if (dd->template_dependency_structure_present_flag) { err = template_dependency_structure(pb, dd); if (err) return err; } if (dd->active_decode_targets_present_flag) { DEBUG_WARNING("no active_decode_targets_present_flag\n"); return ENOTSUP; } return 0; } int dd_encode(struct mbuf *mb, const struct dd *dd) { struct putbit pb; if (!mb || !dd) return EINVAL; putbit_init(&pb, mb); int err = mandatory_descriptor_fields(&pb, dd); if (err) return err; if (dd->ext) { err = extended_descriptor_fields(&pb, dd); if (err) return err; } return 0; } ================================================ FILE: src/dd/putbit.c ================================================ /** * @file putbit.c Put bits helper * * Copyright (C) 2023 Alfred E. Heggestad */ #include #include #include void putbit_init(struct putbit *pb, struct mbuf *mb) { if (!pb || !mb) return; pb->mb = mb; pb->bit_pos = 0; memset(pb->mb->buf, 0, pb->mb->size); } int putbit_one(struct putbit *pb, unsigned bit) { if (!pb) return EINVAL; size_t byte_pos = pb->bit_pos >> 0x3; /* resize mbuf */ if (byte_pos >= pb->mb->size) { int err = mbuf_resize(pb->mb, pb->mb->size * 2); if (err) return err; } uint8_t *p = pb->mb->buf; size_t bit_pos = (size_t)(1u << (0x7 - (pb->bit_pos & 0x7))); if (bit) { p[byte_pos] |= bit_pos; } else { p[byte_pos] &= ~bit_pos; } ++pb->bit_pos; /* NOTE: mb->pos not used */ mbuf_set_end(pb->mb, (pb->bit_pos + 7) >> 0x3); return 0; } int putbit_write(struct putbit *pb, unsigned count, unsigned val) { if (!pb) return EINVAL; if (count > 32) return EINVAL; for (unsigned i=0; i> shift) & 0x1; int err = putbit_one(pb, bit); if (err) return err; } return 0; } int putbit_write_ns(struct putbit *pb, unsigned n, unsigned v) { if (!pb) return EINVAL; int err; #if 0 /* TODO: check this */ if (n == 1) return EINVAL; #endif unsigned w = 0; unsigned x = n; while (x != 0) { x = x >> 1; ++w; } unsigned m = (1 << w) - n; if (v < m) err = putbit_write(pb, w - 1, v); else err = putbit_write(pb, w, v + m); return err; } ================================================ FILE: src/dns/client.c ================================================ /** * @file dns/client.c DNS Client * * Copyright (C) 2010 Creytiv.com * Copyright (C) 2022 Sebastian Reimers */ #ifndef WIN32 #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MODULE "dnsc" #define DEBUG_LEVEL 5 #include enum { NTX_MAX = 4, QUERY_HASH_SIZE = 16, TCP_HASH_SIZE = 2, CONN_TIMEOUT = 10 * 1000, IDLE_TIMEOUT = 30 * 1000, SRVC_MAX = 32, RR_MAX = 32, CACHE_TTL_MAX = 1800, GETADDRINFO_TTL = 60, RRLV_MAX = 3 }; struct tcpconn { struct le le; struct list ql; struct tmr tmr; struct sa srv; struct tcp_conn *conn; struct mbuf *mb; bool connected; uint16_t flen; struct dnsc *dnsc; /* parent */ }; struct dns_query { struct le le; struct le le_hdl; struct le le_tc; struct dnshdr hdr; struct tmr tmr; struct tmr tmr_ttl; struct mbuf mb; struct list *rrlv[RRLV_MAX]; char *name; const struct sa *srvv; const uint32_t *srvc; struct tcpconn *tc; struct dnsc *dnsc; /* parent */ struct dns_query **qp; /* app ref */ uint32_t ntx; uint16_t id; uint16_t type; uint16_t dnsclass; uint8_t opcode; dns_query_h *qh; void *arg; }; struct dnsquery { struct dnshdr hdr; char *name; uint16_t type; uint16_t dnsclass; struct list *rrlv; bool cache; struct dnsc *dnsc; /* parent */ }; struct dnsc { struct dnsc_conf conf; struct tmr hdl_tmr; struct list hdl_cache; struct hash *ht_query; struct hash *ht_query_cache; struct hash *ht_tcpconn; struct udp_sock *us; struct udp_sock *us6; struct sa srvv[SRVC_MAX]; uint32_t srvc; }; static const struct dnsc_conf default_conf = { QUERY_HASH_SIZE, TCP_HASH_SIZE, CONN_TIMEOUT, IDLE_TIMEOUT, CACHE_TTL_MAX, false }; static void tcpconn_close(struct tcpconn *tc, int err); static int send_tcp(struct dns_query *q); static void udp_timeout_handler(void *arg); static bool rr_unlink_handler(struct le *le, void *arg) { struct dnsrr *rr = le->data; (void)arg; if (mem_nrefs(rr) < 2) list_unlink(&rr->le_priv); mem_deref(rr); return false; } static void query_abort(struct dns_query *q) { if (q->tc) { list_unlink(&q->le_tc); q->tc = mem_deref(q->tc); } tmr_cancel(&q->tmr); hash_unlink(&q->le); } static void query_destructor(void *data) { struct dns_query *q = data; query_abort(q); tmr_cancel(&q->tmr_ttl); mbuf_reset(&q->mb); mem_deref(q->name); list_unlink(&q->le_hdl); for (int i = 0; i < RRLV_MAX; i++) { (void)list_apply(q->rrlv[i], true, rr_unlink_handler, NULL); mem_deref(q->rrlv[i]); } } static void query_handler(struct dns_query *q, int err, struct list *ansl, struct list *authl, struct list *addl) { /* deref here - before calling handler */ if (q->qp) *q->qp = NULL; /* The handler must only be called _once_ */ if (q->qh) { q->qh(err, &q->hdr, ansl, authl, addl, q->arg); q->qh = NULL; } /* in case we have more (than one) q refs */ query_abort(q); } static bool query_close_handler(struct le *le, void *arg) { struct dns_query *q = le->data; (void)arg; query_handler(q, ECONNABORTED, NULL, NULL, NULL); mem_deref(q); return false; } static bool query_cmp_handler(struct le *le, void *arg) { struct dns_query *q = le->data; struct dnsquery *dq = arg; if (!dq->cache && q->id != dq->hdr.id) return false; if (q->opcode != dq->hdr.opcode) return false; if (q->type != dq->type) return false; if (q->dnsclass != dq->dnsclass) return false; if (str_casecmp(q->name, dq->name)) return false; return true; } static void ttl_timeout_handler(void *arg) { struct dns_query *q = arg; DEBUG_INFO("ttl cache delete (id: %d): %s.\t%s\t%s\n", q->id, q->name, dns_rr_classname(q->dnsclass), dns_rr_typename(q->type)); mem_deref(q); } static int reply_recv(struct dnsc *dnsc, struct mbuf *mb) { struct dns_query *q = NULL; uint32_t nv[3]; struct dnsquery dq; int err = 0; int64_t ttl; if (!dnsc || !mb) return EINVAL; ttl = dnsc->conf.cache_ttl_max; dq.name = NULL; dq.cache = false; if (dns_hdr_decode(mb, &dq.hdr) || !dq.hdr.qr) { err = EBADMSG; goto out; } err = dns_dname_decode(mb, &dq.name, 0); if (err) goto out; if (mbuf_get_left(mb) < 4) { err = EBADMSG; goto out; } dq.type = ntohs(mbuf_read_u16(mb)); dq.dnsclass = ntohs(mbuf_read_u16(mb)); q = list_ledata(hash_lookup(dnsc->ht_query, hash_joaat_str_ci(dq.name), query_cmp_handler, &dq)); if (!q) { err = ENOENT; goto out; } /* try next server */ if (dq.hdr.rcode == DNS_RCODE_SRV_FAIL && q->ntx < *q->srvc) { if (!q->tc) /* try next UDP server immediately */ tmr_start(&q->tmr, 0, udp_timeout_handler, q); err = EPROTO; goto out; } nv[0] = dq.hdr.nans; nv[1] = dq.hdr.nauth; nv[2] = dq.hdr.nadd; DEBUG_INFO("--- ANSWER SECTION id: %d ---\n", q->id); for (uint32_t i = 0; i < RE_ARRAY_SIZE(nv); i++) { uint32_t l = nv[i]; if (l > RR_MAX) { l = RR_MAX; DEBUG_WARNING("limit rr records %d\n", l); } for (uint32_t j = 0; j < l; j++) { struct dnsrr *rr = NULL; err = dns_rr_decode(mb, &rr, 0); if (err) { query_handler(q, err, NULL, NULL, NULL); mem_deref(q); goto out; } DEBUG_INFO("%H\n", dns_rr_print, rr); list_append(q->rrlv[i], &rr->le_priv, rr); if (rr->ttl < ttl) ttl = rr->ttl; } } if (q->type == DNS_QTYPE_AXFR) { struct dnsrr *rrh, *rrt; rrh = list_ledata(list_head(q->rrlv[0])); rrt = list_ledata(list_tail(q->rrlv[0])); /* Wait for last AXFR reply with terminating SOA record */ if (dq.hdr.rcode == DNS_RCODE_OK && dq.hdr.nans > 0 && (!rrt || rrt->type != DNS_TYPE_SOA || rrh == rrt)) { DEBUG_INFO("waiting for last SOA record in reply\n"); goto out; } } q->hdr = dq.hdr; query_handler(q, 0, q->rrlv[0], q->rrlv[1], q->rrlv[2]); if (!dnsc->conf.cache_ttl_max || q->type == DNS_QTYPE_AXFR) { mem_deref(q); goto out; } /* Don't cache empty RR answer if authority is also empty. */ if (!dq.hdr.nans && !dq.hdr.nauth) { mem_deref(q); goto out; } /* Cache negative answer with SOA minimum value (RFC 2308) */ if (!dq.hdr.nans && dq.hdr.nauth) { const struct dnsrr *rr = list_ledata(list_head(q->rrlv[1])); if (!rr || rr->type != DNS_TYPE_SOA) { mem_deref(q); goto out; } if (rr->rdata.soa.ttlmin < dnsc->conf.cache_ttl_max) ttl = rr->rdata.soa.ttlmin; } /* Cache DNS query with TTL timeout */ hash_append(dnsc->ht_query_cache, hash_joaat_str_ci(q->name), &q->le, q); DEBUG_INFO("cache %s. (id: %d) %d secs\n", q->name, q->id, ttl); /* Fallback to 100ms for faster unit tests */ tmr_start(&q->tmr_ttl, ttl > 1 ? ttl * 1000 : 100, ttl_timeout_handler, q); out: mem_deref(dq.name); return err; } static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { (void)src; (void)reply_recv(arg, mb); } static void tcp_recv_handler(struct mbuf *mbrx, void *arg) { struct tcpconn *tc = arg; struct mbuf *mb = tc->mb; int err = 0; size_t n; next: /* frame length */ if (!tc->flen) { n = min(2 - mb->end, mbuf_get_left(mbrx)); err = mbuf_write_mem(mb, mbuf_buf(mbrx), n); if (err) goto error; mbrx->pos += n; if (mb->end < 2) return; mb->pos = 0; tc->flen = ntohs(mbuf_read_u16(mb)); mb->pos = 0; mb->end = 0; } /* content */ n = min(tc->flen - mb->end, mbuf_get_left(mbrx)); err = mbuf_write_mem(mb, mbuf_buf(mbrx), n); if (err) goto error; mbrx->pos += n; if (mb->end < tc->flen) return; mb->pos = 0; err = reply_recv(tc->dnsc, mb); if (err) goto error; /* reset tcp buffer */ tc->flen = 0; mb->pos = 0; mb->end = 0; /* more data ? */ if (mbuf_get_left(mbrx) > 0) { DEBUG_INFO("%u bytes of tcp data left\n", mbuf_get_left(mbrx)); goto next; } return; error: tcpconn_close(tc, err); } static void tcpconn_timeout_handler(void *arg) { struct tcpconn *tc = arg; DEBUG_NOTICE("tcp (%J) %s timeout \n", &tc->srv, tc->connected ? "idle" : "connect"); tcpconn_close(tc, ETIMEDOUT); } static void tcp_estab_handler(void *arg) { struct tcpconn *tc = arg; struct le *le = list_head(&tc->ql); int err = 0; DEBUG_INFO("connection (%J) established\n", &tc->srv); while (le) { struct dns_query *q = le->data; le = le->next; q->mb.pos = 0; err = tcp_send(tc->conn, &q->mb); if (err) break; DEBUG_INFO("tcp send %J\n", &tc->srv); } if (err) { tcpconn_close(tc, err); return; } tmr_start(&tc->tmr, tc->dnsc->conf.idle_timeout, tcpconn_timeout_handler, tc); tc->connected = true; } static void tcp_close_handler(int err, void *arg) { struct tcpconn *tc = arg; DEBUG_NOTICE("connection (%J) closed: %m\n", &tc->srv, err); tcpconn_close(tc, err); } static bool tcpconn_cmp_handler(struct le *le, void *arg) { const struct tcpconn *tc = le->data; /* avoid trying this connection if dead */ if (!tc->conn) return false; return sa_cmp(&tc->srv, arg, SA_ALL); } static bool tcpconn_fail_handler(struct le *le, void *arg) { struct dns_query *q = le->data; int err = *((int *)arg); list_unlink(&q->le_tc); q->tc = mem_deref(q->tc); if (q->ntx >= *q->srvc) { DEBUG_WARNING("all servers failed, giving up!!\n"); err = err ? err : ECONNREFUSED; goto out; } /* try next server(s) */ err = send_tcp(q); if (err) { DEBUG_WARNING("all servers failed, giving up\n"); goto out; } out: if (err) { query_handler(q, err, NULL, NULL, NULL); mem_deref(q); } return false; } static void tcpconn_close(struct tcpconn *tc, int err) { if (!tc) return; /* avoid trying this connection again (e.g. same address) */ tc->conn = mem_deref(tc->conn); (void)list_apply(&tc->ql, true, tcpconn_fail_handler, &err); mem_deref(tc); } static void tcpconn_destructor(void *arg) { struct tcpconn *tc = arg; hash_unlink(&tc->le); tmr_cancel(&tc->tmr); mem_deref(tc->conn); mem_deref(tc->mb); } static int tcpconn_alloc(struct tcpconn **tcpp, struct dnsc *dnsc, const struct sa *srv) { struct tcpconn *tc; int err = ENOMEM; if (!tcpp || !dnsc || !srv) return EINVAL; tc = mem_zalloc(sizeof(struct tcpconn), tcpconn_destructor); if (!tc) goto out; hash_append(dnsc->ht_tcpconn, sa_hash(srv, SA_ALL), &tc->le, tc); tc->srv = *srv; tc->dnsc = dnsc; tc->mb = mbuf_alloc(1500); if (!tc->mb) goto out; err = tcp_connect(&tc->conn, srv, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, tc); if (err) goto out; tmr_start(&tc->tmr, tc->dnsc->conf.conn_timeout, tcpconn_timeout_handler, tc); out: if (err) mem_deref(tc); else *tcpp = tc; return err; } static int send_tcp(struct dns_query *q) { const struct sa *srv; struct tcpconn *tc; int err = 0; if (!q) return EINVAL; while (q->ntx < *q->srvc) { srv = &q->srvv[q->ntx++]; DEBUG_NOTICE("trying tcp server#%u: %J\n", q->ntx-1, srv); tc = list_ledata(hash_lookup(q->dnsc->ht_tcpconn, sa_hash(srv, SA_ALL), tcpconn_cmp_handler, (void *)srv)); if (!tc) { err = tcpconn_alloc(&tc, q->dnsc, srv); if (err) continue; } if (tc->connected) { q->mb.pos = 0; err = tcp_send(tc->conn, &q->mb); if (err) { tcpconn_close(tc, err); continue; } tmr_start(&tc->tmr, tc->dnsc->conf.idle_timeout, tcpconn_timeout_handler, tc); DEBUG_NOTICE("tcp send %J\n", srv); } list_append(&tc->ql, &q->le_tc, q); q->tc = mem_ref(tc); break; } return err; } static void tcp_timeout_handler(void *arg) { struct dns_query *q = arg; query_handler(q, ETIMEDOUT, NULL, NULL, NULL); mem_deref(q); } static int send_udp(struct dns_query *q) { const struct sa *srv; int err = ETIMEDOUT; uint32_t i; if (!q) return EINVAL; for (i=0; i<*q->srvc; i++) { struct udp_sock *us; srv = &q->srvv[q->ntx++%*q->srvc]; DEBUG_INFO("trying udp server#%u: %J\n", i, srv); switch (sa_af(srv)) { case AF_INET: us = q->dnsc->us; break; case AF_INET6: us = q->dnsc->us6; break; default: continue; } q->mb.pos = 0; err = udp_send(us, srv, &q->mb); if (!err) break; } return err; } static void udp_timeout_handler(void *arg) { struct dns_query *q = arg; int err = ETIMEDOUT; if (q->ntx >= NTX_MAX * *q->srvc) goto out; err = send_udp(q); if (err) goto out; int timeout = 500 << MIN(2, (q->ntx - 1) / *q->srvc); DEBUG_INFO("waiting udp timeout: %dms\n", timeout); tmr_start(&q->tmr, timeout, udp_timeout_handler, q); out: if (err) { query_handler(q, err, NULL, NULL, NULL); mem_deref(q); } } static void hdl_tmr_cache(void *arg) { struct list *l = arg; struct le *le; LIST_FOREACH(l, le) { struct dns_query *q = le->data; #if DEBUG_LEVEL > 5 struct le *re_rr; DEBUG_INFO("--- ANSWER SECTION (CACHED) id: %d ---\n", q->id); LIST_FOREACH(q->rrlv[0], re_rr) { struct dnsrr *rr = re_rr->data; DEBUG_INFO("%H\n", dns_rr_print, rr); } #endif query_handler(q, 0, q->rrlv[0], q->rrlv[1], q->rrlv[2]); } list_flush(l); } static bool query_cache_handler(struct dns_query *q) { struct dnsquery dq; const struct dns_query *qc = NULL; struct le *le; dq.hdr = q->hdr; dq.type = q->type; dq.dnsclass = q->dnsclass; dq.name = q->name; dq.cache = true; qc = list_ledata(hash_lookup(q->dnsc->ht_query_cache, hash_joaat_str_ci(q->name), query_cmp_handler, &dq)); if (!qc) return false; for (int i = 0; i < RRLV_MAX; i++) { LIST_FOREACH(qc->rrlv[i], le) { struct dnsrr *rr = le->data; mem_ref(rr); } q->rrlv[i] = mem_ref(qc->rrlv[i]); } hash_unlink(&q->le); list_append(&q->dnsc->hdl_cache, &q->le_hdl, q); tmr_start(&q->dnsc->hdl_tmr, 0, hdl_tmr_cache, &q->dnsc->hdl_cache); return true; } static bool getaddr_dup(struct le *le, void *arg) { struct dnsrr *r1 = list_ledata(le); struct dnsrr *r2 = arg; if (r1->type == DNS_TYPE_A && r2->type == DNS_TYPE_A) { if (r1->rdata.a.addr == r2->rdata.a.addr) return true; } if (r1->type == DNS_TYPE_AAAA && r2->type == DNS_TYPE_AAAA) { if (0 == memcmp(r1->rdata.aaaa.addr, r2->rdata.aaaa.addr, 16)) return true; } return false; } static int async_getaddrinfo(void *arg) { struct dnsquery *dq = arg; int err; struct addrinfo *res0 = NULL; struct addrinfo *res; struct addrinfo hints; struct sa sa; memset(&hints, 0, sizeof(hints)); if (dq->type == DNS_TYPE_A) hints.ai_family = AF_INET; if (dq->type == DNS_TYPE_AAAA) hints.ai_family = AF_INET6; hints.ai_flags = AI_ADDRCONFIG; err = getaddrinfo(dq->name, NULL, &hints, &res0); if (err) return EADDRNOTAVAIL; for (res = res0; res; res = res->ai_next) { struct dnsrr *rr = dns_rr_alloc(); struct le *le; if (!rr) { err = ENOMEM; goto out; } str_dup(&rr->name, dq->name); rr->dnsclass = DNS_CLASS_IN; rr->ttl = GETADDRINFO_TTL; err = sa_set_sa(&sa, res->ai_addr); if (err) { mem_deref(rr); continue; } if (sa_af(&sa) == AF_INET) { rr->type = DNS_TYPE_A; rr->rdlen = 4; rr->rdata.a.addr = sa_in(&sa); } if (sa_af(&sa) == AF_INET6) { rr->type = DNS_TYPE_AAAA; rr->rdlen = 16; sa_in6(&sa, rr->rdata.aaaa.addr); } le = list_apply(dq->rrlv, false, getaddr_dup, rr); if (le) { mem_deref(rr); continue; } list_append(dq->rrlv, &rr->le_priv, rr); } out: if (err) list_flush(dq->rrlv); freeaddrinfo(res0); return err; } static void getaddrinfo_h(int err, void *arg) { struct dnsquery *dq = arg; struct dns_query *q; q = list_ledata(hash_lookup(dq->dnsc->ht_query, hash_joaat_str_ci(dq->name), query_cmp_handler, dq)); if (!q) { DEBUG_WARNING("getaddrinfo_h: no query found\n"); list_flush(dq->rrlv); mem_deref(dq->rrlv); goto out; } mem_deref(q->rrlv[0]); q->rrlv[0] = dq->rrlv; const bool cache = q->dnsc->conf.cache_ttl_max > 0; DEBUG_INFO("--- ANSWER SECTION (getaddrinfo) id: %d %s ---\n", q->id, cache ? "(caching)" : ""); if (err) { DEBUG_INFO("getaddrinfo_h: err %m\n", err); } else { struct le *le; LIST_FOREACH(q->rrlv[0], le) { DEBUG_INFO("%H%s\n", dns_rr_print, le->data); } } query_handler(q, err, q->rrlv[0], q->rrlv[1], q->rrlv[2]); if (err || !cache) { mem_deref(q); goto out; } hash_append(q->dnsc->ht_query_cache, hash_joaat_str_ci(q->name), &q->le, q); tmr_start(&q->tmr_ttl, GETADDRINFO_TTL * 1000, ttl_timeout_handler, q); out: mem_deref(dq); } static void dq_deref(void *arg) { struct dnsquery *dq = arg; mem_deref(dq->dnsc); mem_deref(dq->name); } static int query_getaddrinfo(struct dns_query *q) { int err; struct dnsquery *dq = mem_zalloc(sizeof(struct dnsquery), dq_deref); if (!dq) return ENOMEM; err = str_dup(&dq->name, q->name); if (err) goto out; dq->type = q->type; dq->hdr.id = q->id; dq->hdr.opcode = q->opcode; dq->dnsclass = q->dnsclass; dq->dnsc = mem_ref(q->dnsc); dq->rrlv = mem_alloc(sizeof(struct list), NULL); if (!dq->rrlv) { err = ENOMEM; goto out; } list_init(dq->rrlv); err = re_thread_async(async_getaddrinfo, getaddrinfo_h, dq); if (err) DEBUG_WARNING("re_thread_async: %m\n", err); out: if (err) mem_deref(dq); return err; } static int query(struct dns_query **qp, struct dnsc *dnsc, uint8_t opcode, const char *name, uint16_t type, uint16_t dnsclass, const struct dnsrr *ans_rr, int proto, const struct sa *srvv, const uint32_t *srvc, bool aa, bool rd, dns_query_h *qh, void *arg) { struct dns_query *q = NULL; struct dnshdr hdr; int err = 0; bool use_getaddrinfo = false; bool srv_available = srvv && srvc && *srvc != 0; if (!dnsc || !name) return EINVAL; use_getaddrinfo = dnsc->conf.getaddrinfo && (type == DNS_TYPE_A || type == DNS_TYPE_AAAA); if (!srv_available && !use_getaddrinfo) return ENOTSUP; if (DNS_QTYPE_AXFR == type) proto = IPPROTO_TCP; q = mem_zalloc(sizeof(*q), query_destructor); if (!q) goto nmerr; hash_append(dnsc->ht_query, hash_joaat_str_ci(name), &q->le, q); tmr_init(&q->tmr); tmr_init(&q->tmr_ttl); mbuf_init(&q->mb); err = str_dup(&q->name, name); if (err) goto error; q->srvv = srvv; q->srvc = srvc; q->id = rand_u16(); q->type = type; q->opcode = opcode; q->dnsclass = dnsclass; q->dnsc = dnsc; memset(&hdr, 0, sizeof(hdr)); hdr.id = q->id; hdr.opcode = q->opcode; hdr.aa = aa; hdr.rd = rd; hdr.nq = 1; hdr.nans = ans_rr ? 1 : 0; q->qh = qh; q->arg = arg; q->hdr = hdr; DEBUG_INFO("--- QUESTION SECTION id: %d ---\n", q->id); DEBUG_INFO("%s.\t%s\t%s\n", q->name, dns_rr_classname(q->dnsclass), dns_rr_typename(q->type)); if (query_cache_handler(q)) goto out; for (int i = 0; i < RRLV_MAX; i++) { q->rrlv[i] = mem_alloc(sizeof(struct list), NULL); if (!q->rrlv[i]) goto nmerr; list_init(q->rrlv[i]); } if (use_getaddrinfo) { err = query_getaddrinfo(q); if (err) goto error; goto out; } if (proto == IPPROTO_TCP) q->mb.pos += 2; err = dns_hdr_encode(&q->mb, &hdr); if (err) goto error; err = dns_dname_encode(&q->mb, name, NULL, 0, false); if (err) goto error; err |= mbuf_write_u16(&q->mb, htons(type)); err |= mbuf_write_u16(&q->mb, htons(dnsclass)); if (err) goto error; if (ans_rr) { err = dns_rr_encode(&q->mb, ans_rr, 0, NULL, 0); if (err) goto error; } switch (proto) { case IPPROTO_TCP: q->mb.pos = 0; (void)mbuf_write_u16(&q->mb, htons((uint16_t)q->mb.end - 2)); err = send_tcp(q); if (err) goto error; tmr_start(&q->tmr, 60 * 1000, tcp_timeout_handler, q); break; case IPPROTO_UDP: err = send_udp(q); if (err) goto error; tmr_start(&q->tmr, 500, udp_timeout_handler, q); break; default: err = EPROTONOSUPPORT; goto error; } out: if (qp) { q->qp = qp; *qp = q; } return 0; nmerr: err = ENOMEM; error: mem_deref(q); return err; } /** * Query a DNS name * * @param qp Pointer to allocated DNS query * @param dnsc DNS Client * @param name DNS name * @param type DNS Resource Record type * @param dnsclass DNS Class * @param rd Recursion Desired (RD) flag * @param qh Query handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dnsc_query(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, bool rd, dns_query_h *qh, void *arg) { if (!dnsc) return EINVAL; return query(qp, dnsc, DNS_OPCODE_QUERY, name, type, dnsclass, NULL, IPPROTO_UDP, dnsc->srvv, &dnsc->srvc, false, rd, qh, arg); } /** * Query a DNS name using specific nameservers * * @param qp Pointer to allocated DNS query * @param dnsc DNS Client * @param name DNS name * @param type DNS Resource Record type * @param dnsclass DNS Class * @param proto Protocol * @param srvv DNS Nameservers * @param srvc Number of DNS nameservers * @param rd Recursion Desired (RD) flag * @param qh Query handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dnsc_query_srv(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, int proto, const struct sa *srvv, const uint32_t *srvc, bool rd, dns_query_h *qh, void *arg) { return query(qp, dnsc, DNS_OPCODE_QUERY, name, type, dnsclass, NULL, proto, srvv, srvc, false, rd, qh, arg); } /** * Send a DNS query with NOTIFY opcode * * @param qp Pointer to allocated DNS query * @param dnsc DNS Client * @param name DNS name * @param type DNS Resource Record type * @param dnsclass DNS Class * @param ans_rr Answer Resource Record * @param proto Protocol * @param srvv DNS Nameservers * @param srvc Number of DNS nameservers * @param qh Query handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dnsc_notify(struct dns_query **qp, struct dnsc *dnsc, const char *name, uint16_t type, uint16_t dnsclass, const struct dnsrr *ans_rr, int proto, const struct sa *srvv, const uint32_t *srvc, dns_query_h *qh, void *arg) { return query(qp, dnsc, DNS_OPCODE_NOTIFY, name, type, dnsclass, ans_rr, proto, srvv, srvc, true, false, qh, arg); } static void dnsc_destructor(void *data) { struct dnsc *dnsc = data; list_flush(&dnsc->hdl_cache); (void)hash_apply(dnsc->ht_query, query_close_handler, NULL); hash_flush(dnsc->ht_tcpconn); hash_flush(dnsc->ht_query_cache); tmr_cancel(&dnsc->hdl_tmr); mem_deref(dnsc->ht_tcpconn); mem_deref(dnsc->ht_query); mem_deref(dnsc->ht_query_cache); mem_deref(dnsc->us6); mem_deref(dnsc->us); } /** * Allocate a DNS Client * * @param dcpp Pointer to allocated DNS Client * @param conf Optional DNS configuration, NULL for default * @param srvv DNS servers * @param srvc Number of DNS Servers * * @return 0 if success, otherwise errorcode */ int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf, const struct sa *srvv, uint32_t srvc) { struct dnsc *dnsc; struct sa laddr; struct sa laddr6; int err; if (!dcpp) return EINVAL; dnsc = mem_zalloc(sizeof(*dnsc), dnsc_destructor); if (!dnsc) return ENOMEM; if (conf) dnsc->conf = *conf; else dnsc->conf = default_conf; err = dnsc_srv_set(dnsc, srvv, srvc); if (err) goto out; sa_set_str(&laddr, "0.0.0.0", 0); err = udp_listen(&dnsc->us, &laddr, udp_recv_handler, dnsc); sa_set_str(&laddr6, "::", 0); err &= udp_listen(&dnsc->us6, &laddr6, udp_recv_handler, dnsc); if (err) goto out; err = hash_alloc(&dnsc->ht_query, dnsc->conf.query_hash_size); if (err) goto out; err = hash_alloc(&dnsc->ht_query_cache, dnsc->conf.query_hash_size); if (err) goto out; err = hash_alloc(&dnsc->ht_tcpconn, dnsc->conf.tcp_hash_size); if (err) goto out; tmr_init(&dnsc->hdl_tmr); list_init(&dnsc->hdl_cache); out: if (err) mem_deref(dnsc); else *dcpp = dnsc; return err; } void dnsc_conf_set_timeout(struct dnsc *dnsc, uint32_t connect, uint32_t idle) { if (!dnsc) return; dnsc->conf.conn_timeout = connect; dnsc->conf.idle_timeout = idle; } int dnsc_conf_set(struct dnsc *dnsc, const struct dnsc_conf *conf) { int err; if (!dnsc) return EINVAL; if (conf) dnsc->conf = *conf; else dnsc->conf = default_conf; list_flush(&dnsc->hdl_cache); hash_flush(dnsc->ht_tcpconn); hash_flush(dnsc->ht_query_cache); dnsc->ht_query = mem_deref(dnsc->ht_query); dnsc->ht_query_cache = mem_deref(dnsc->ht_query_cache); dnsc->ht_tcpconn = mem_deref(dnsc->ht_tcpconn); err = hash_alloc(&dnsc->ht_query, dnsc->conf.query_hash_size); if (err) return err; err = hash_alloc(&dnsc->ht_query_cache, dnsc->conf.query_hash_size); if (err) return err; err = hash_alloc(&dnsc->ht_tcpconn, dnsc->conf.tcp_hash_size); return err; } /** * Set the DNS Servers on a DNS Client * * @param dnsc DNS Client * @param srvv DNS Nameservers * @param srvc Number of nameservers * * @return 0 if success, otherwise errorcode */ int dnsc_srv_set(struct dnsc *dnsc, const struct sa *srvv, uint32_t srvc) { uint32_t i; if (!dnsc) return EINVAL; dnsc->srvc = min((uint32_t)RE_ARRAY_SIZE(dnsc->srvv), srvc); if (srvv) { for (i=0; isrvc; i++) dnsc->srvv[i] = srvv[i]; } return 0; } /** * Flush DNS cache * * @param dnsc DNS Client */ void dnsc_cache_flush(struct dnsc *dnsc) { if (!dnsc) return; hash_flush(dnsc->ht_query_cache); } /** * Set max. Cache TTL * * @param dnsc DNS Client * @param max Value in [s] and 0 to disable caching */ void dnsc_cache_max(struct dnsc *dnsc, uint32_t max) { if (!dnsc) return; dnsc->conf.cache_ttl_max = max; if (!max) dnsc_cache_flush(dnsc); } /** * Enable/Disable getaddrinfo usage * * @param dnsc DNS Client * @param active true for enabled, otherwise disabled (default) */ void dnsc_getaddrinfo(struct dnsc *dnsc, bool active) { if (!dnsc) return; dnsc->conf.getaddrinfo = active; } /** * Return if getaddrinfo usage is enabled * * @param dnsc DNS Client * * @return true if getaddrinfo is used, false otherwise */ bool dnsc_getaddrinfo_enabled(struct dnsc *dnsc) { if (!dnsc) return false; return dnsc->conf.getaddrinfo; } /** * Return if getaddrinfo usage is enabled and exclusive, * i.e. there are no DNS servers known explicitly * * @param dnsc DNS Client * * @return true if DNS servers are available, false otherwise */ bool dnsc_getaddrinfo_only(const struct dnsc *dnsc) { if (!dnsc) return false; return dnsc->conf.getaddrinfo && dnsc->srvc == 0; } ================================================ FILE: src/dns/cstr.c ================================================ /** * @file cstr.c DNS character strings encoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include /** * Encode a DNS character string into a memory buffer * * @param mb Memory buffer to encode into * @param str Character string * * @return 0 if success, otherwise errorcode */ int dns_cstr_encode(struct mbuf *mb, const char *str) { uint8_t len; int err = 0; if (!mb || !str) return EINVAL; len = (uint8_t)strlen(str); err |= mbuf_write_u8(mb, len); err |= mbuf_write_mem(mb, (const uint8_t *)str, len); return err; } /** * Decode a DNS character string from a memory buffer * * @param mb Memory buffer to decode from * @param str Pointer to allocated character string * * @return 0 if success, otherwise errorcode */ int dns_cstr_decode(struct mbuf *mb, char **str) { uint8_t len; if (!mb || !str || (mbuf_get_left(mb) < 1)) return EINVAL; len = mbuf_read_u8(mb); if (mbuf_get_left(mb) < len) return EBADMSG; return mbuf_strdup(mb, str, len); } ================================================ FILE: src/dns/darwin/srv.c ================================================ /** * @file darwin/srv.c Get DNS Server IP code for Mac OS X * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include "../dns.h" #define __CF_USE_FRAMEWORK_INCLUDES__ #include int get_darwin_dns(struct sa *nsv, uint32_t *n) { #if TARGET_OS_IPHONE (void)nsv; (void)n; return ENOSYS; #else SCDynamicStoreContext context = {0, NULL, NULL, NULL, NULL}; CFArrayRef addresses; SCDynamicStoreRef store; CFStringRef key; CFDictionaryRef dict; uint32_t c, i; int err = ENOENT; if (!nsv || !n) return EINVAL; store = SCDynamicStoreCreate(NULL, CFSTR("get_darwin_dns"), NULL, &context); if (!store) return ENOENT; key = CFSTR("State:/Network/Global/DNS"); dict = SCDynamicStoreCopyValue(store, key); if (!dict) goto out1; addresses = CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses); if (!addresses) goto out; c = (uint32_t)CFArrayGetCount(addresses); *n = min(*n, c); for (i=0; i<*n; i++) { CFStringRef address = CFArrayGetValueAtIndex(addresses, i); char str[64]; CFStringGetCString(address, str, sizeof(str), kCFStringEncodingUTF8); err = sa_set_str(&nsv[i], str, DNS_PORT); if (err) break; } out: CFRelease(dict); out1: CFRelease(store); return err; #endif } ================================================ FILE: src/dns/dname.c ================================================ /** * @file dname.c DNS domain names * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #define COMP_MASK 0xc0 #define OFFSET_MASK 0x3fff #define COMP_LOOP 255 struct dname { struct le he; size_t pos; char *name; }; static void destructor(void *arg) { struct dname *dn = arg; hash_unlink(&dn->he); mem_deref(dn->name); } static void dname_append(struct hash *ht_dname, const char *name, size_t pos) { struct dname *dn; if (!ht_dname || pos > OFFSET_MASK || !*name) return; dn = mem_zalloc(sizeof(*dn), destructor); if (!dn) return; if (str_dup(&dn->name, name)) { mem_deref(dn); return; } hash_append(ht_dname, hash_joaat_str_ci(name), &dn->he, dn); dn->pos = pos; } static bool lookup_handler(struct le *le, void *arg) { struct dname *dn = le->data; return 0 == str_casecmp(dn->name, arg); } static inline struct dname *dname_lookup(struct hash *ht_dname, const char *name) { return list_ledata(hash_lookup(ht_dname, hash_joaat_str_ci(name), lookup_handler, (void *)name)); } static inline int dname_encode_pointer(struct mbuf *mb, size_t pos) { return mbuf_write_u16(mb, htons((uint16_t)pos | (COMP_MASK<<8))); } /** * Encode a DNS Domain name into a memory buffer * * @param mb Memory buffer * @param name Domain name * @param ht_dname Domain name hashtable * @param start Start position * @param comp Enable compression * * @return 0 if success, otherwise errorcode */ int dns_dname_encode(struct mbuf *mb, const char *name, struct hash *ht_dname, size_t start, bool comp) { struct dname *dn; size_t pos; int err; if (!mb || !name) return EINVAL; dn = dname_lookup(ht_dname, name); if (dn && comp) return dname_encode_pointer(mb, dn->pos); pos = mb->pos; if (!dn) dname_append(ht_dname, name, pos - start); err = mbuf_write_u8(mb, 0); if ('.' == name[0] && '\0' == name[1]) return err; while (err == 0) { const size_t lablen = mb->pos - pos - 1; if ('\0' == *name) { if (!lablen) break; mb->buf[pos] = (uint8_t)lablen; err |= mbuf_write_u8(mb, 0); break; } else if ('.' == *name) { if (!lablen) return EINVAL; mb->buf[pos] = (uint8_t)lablen; dn = dname_lookup(ht_dname, name + 1); if (dn && comp) { err |= dname_encode_pointer(mb, dn->pos); break; } pos = mb->pos; if (!dn) dname_append(ht_dname, name + 1, pos - start); err |= mbuf_write_u8(mb, 0); } else { err |= mbuf_write_u8(mb, *name); } ++name; } return err; } /** * Decode a DNS domain name from a memory buffer * * @param mb Memory buffer to decode from * @param name Pointer to allocated string with domain name * @param start Start position * * @return 0 if success, otherwise errorcode */ int dns_dname_decode(struct mbuf *mb, char **name, size_t start) { uint32_t i = 0, loopc = 0; bool comp = false; size_t pos = 0; char buf[256]; if (!mb || !name) return EINVAL; while (mb->pos < mb->end) { uint8_t len = mb->buf[mb->pos++]; if (!len) { if (comp) mb->pos = pos; buf[i++] = '\0'; *name = mem_alloc(i, NULL); if (!*name) return ENOMEM; str_ncpy(*name, buf, i); return 0; } else if ((len & COMP_MASK) == COMP_MASK) { uint16_t offset; if (loopc++ > COMP_LOOP) break; --mb->pos; if (mbuf_get_left(mb) < 2) break; offset = ntohs(mbuf_read_u16(mb)) & OFFSET_MASK; if (!comp) { pos = mb->pos; comp = true; } mb->pos = offset + start; continue; } else if (len > mbuf_get_left(mb)) break; else if (len + i + 2 > sizeof(buf)) break; if (i > 0) buf[i++] = '.'; while (len--) buf[i++] = mb->buf[mb->pos++]; } return EINVAL; } ================================================ FILE: src/dns/dns.h ================================================ /** * @file dns.h Internal DNS header file * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_RESOLV int get_resolv_dns(struct sa *nsv, uint32_t *n); #endif #ifdef WIN32 int get_windns(struct sa *nav, uint32_t *n); #endif #ifdef DARWIN int get_darwin_dns(struct sa *nsv, uint32_t *n); #endif ================================================ FILE: src/dns/hdr.c ================================================ /** * @file dns/hdr.c DNS header encoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include enum { QUERY_RESPONSE = 15, OPCODE = 11, AUTH_ANSWER = 10, TRUNCATED = 9, RECURSION_DESIRED = 8, RECURSION_AVAILABLE = 7, ZERO = 4 }; /** * Encode a DNS header * * @param mb Memory buffer to encode header into * @param hdr DNS header * * @return 0 if success, otherwise errorcode */ int dns_hdr_encode(struct mbuf *mb, const struct dnshdr *hdr) { uint16_t flags = 0; int err = 0; if (!mb || !hdr) return EINVAL; flags |= hdr->qr <opcode <aa <tc <rd <ra <z <rcode; err |= mbuf_write_u16(mb, htons(hdr->id)); err |= mbuf_write_u16(mb, htons(flags)); err |= mbuf_write_u16(mb, htons(hdr->nq)); err |= mbuf_write_u16(mb, htons(hdr->nans)); err |= mbuf_write_u16(mb, htons(hdr->nauth)); err |= mbuf_write_u16(mb, htons(hdr->nadd)); return err; } /** * Decode a DNS header from a memory buffer * * @param mb Memory buffer to decode header from * @param hdr DNS header (output) * * @return 0 if success, otherwise errorcode */ int dns_hdr_decode(struct mbuf *mb, struct dnshdr *hdr) { uint16_t flags = 0; if (!mb || !hdr || (mbuf_get_left(mb) < DNS_HEADER_SIZE)) return EINVAL; hdr->id = ntohs(mbuf_read_u16(mb)); flags = ntohs(mbuf_read_u16(mb)); hdr->qr = 0x1 & (flags >> QUERY_RESPONSE); hdr->opcode = 0xf & (flags >> OPCODE); hdr->aa = 0x1 & (flags >> AUTH_ANSWER); hdr->tc = 0x1 & (flags >> TRUNCATED); hdr->rd = 0x1 & (flags >> RECURSION_DESIRED); hdr->ra = 0x1 & (flags >> RECURSION_AVAILABLE); hdr->z = 0x7 & (flags >> ZERO); hdr->rcode = 0xf & (flags >> 0); hdr->nq = ntohs(mbuf_read_u16(mb)); hdr->nans = ntohs(mbuf_read_u16(mb)); hdr->nauth = ntohs(mbuf_read_u16(mb)); hdr->nadd = ntohs(mbuf_read_u16(mb)); return 0; } /** * Get the string of a DNS opcode * * @param opcode DNS opcode * * @return Opcode string */ const char *dns_hdr_opcodename(uint8_t opcode) { switch (opcode) { case DNS_OPCODE_QUERY: return "QUERY"; case DNS_OPCODE_IQUERY: return "IQUERY"; case DNS_OPCODE_STATUS: return "STATUS"; case DNS_OPCODE_NOTIFY: return "NOTIFY"; default: return "??"; } } /** * Get the string of a DNS response code * * @param rcode Response code * * @return Response code string */ const char *dns_hdr_rcodename(uint8_t rcode) { switch (rcode) { case DNS_RCODE_OK: return "OK"; case DNS_RCODE_FMT_ERR: return "Format Error"; case DNS_RCODE_SRV_FAIL: return "Server Failure"; case DNS_RCODE_NAME_ERR: return "Name Error"; case DNS_RCODE_NOT_IMPL: return "Not Implemented"; case DNS_RCODE_REFUSED: return "Refused"; case DNS_RCODE_NOT_AUTH: return "Server Not Authoritative for zone"; default: return "??"; } } ================================================ FILE: src/dns/ns.c ================================================ /** * @file ns.c DNS Nameserver configuration * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include "dns.h" #define DEBUG_MODULE "ns" #define DEBUG_LEVEL 5 #include static int parse_resolv_conf(struct sa *srvv, uint32_t *n) { FILE *f; uint32_t i = 0; int err = 0; if (!srvv || !n || !*n) return EINVAL; f = fopen("/etc/resolv.conf", "r"); if (!f) return errno; for (;;) { char line[128]; struct pl srv; size_t len; if (1 != fscanf(f, "%127[^\n]\n", line)) break; if ('#' == line[0] || ';' == line[0]) continue; len = str_len(line); /* Use the first entry */ if (i < *n && 0 == re_regex(line, len, "nameserver [0-9a-f.:]+", &srv)) { err = sa_set(&srvv[i], &srv, DNS_PORT); if (err) { DEBUG_WARNING("sa_set: %r (%m)\n", &srv, err); } ++i; } } *n = i; (void)fclose(f); return err; } /** * Get the DNS domain and nameservers * * @param domain Unused * @param dsize Unused * @param srvv Returned nameservers * @param n Nameservers capacity, actual on return * * @return 0 if success, otherwise errorcode */ int dns_srv_get(char *domain, size_t dsize, struct sa *srvv, uint32_t *n) { int err; (void)domain; (void)dsize; /* Try them all in prioritized order */ #ifdef HAVE_RESOLV err = get_resolv_dns(srvv, n); if (!err) return 0; #endif #ifdef DARWIN err = get_darwin_dns(srvv, n); if (!err) return 0; #endif err = parse_resolv_conf(srvv, n); if (!err) return 0; #ifdef WIN32 err = get_windns(srvv, n); #endif return err; } ================================================ FILE: src/dns/res.c ================================================ /** * @file res.c Get DNS Server IP using resolv * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dns.h" int get_resolv_dns(struct sa *nsv, uint32_t *n) { struct __res_state state; uint32_t i; int ret, err; #ifdef OPENBSD ret = res_init(); state = _res; #else memset(&state, 0, sizeof(state)); ret = res_ninit(&state); #endif if (0 != ret) return ENOENT; if (!state.nscount) { err = ENOENT; goto out; } err = 0; #ifdef DARWIN int memsize = state.nscount * sizeof(union res_sockaddr_union); union res_sockaddr_union *addr = mem_alloc(memsize, NULL); if (!addr) { err = ENOMEM; goto out; } int servers = res_getservers(&state, addr, state.nscount); for (i = 0; i < min(*n, (uint32_t)servers) && !err; i++) { if (addr[i].sin.sin_family == AF_INET) err |= sa_set_sa(&nsv[i], (struct sockaddr *)&addr[i].sin); else if (addr[i].sin6.sin6_family == AF_INET6) err |= sa_set_sa(&nsv[i], (struct sockaddr *)&addr[i].sin6); else (void)re_fprintf(stderr, "get_resolv_dns: Undefined family.\n"); } mem_deref(addr); #else for (i=0; i #include #include #include #include #include #include #include #include static void rr_destructor(void *data) { struct dnsrr *rr = data; mem_deref(rr->name); switch (rr->type) { case DNS_TYPE_NS: mem_deref(rr->rdata.ns.nsdname); break; case DNS_TYPE_CNAME: mem_deref(rr->rdata.cname.cname); break; case DNS_TYPE_SOA: mem_deref(rr->rdata.soa.mname); mem_deref(rr->rdata.soa.rname); break; case DNS_TYPE_PTR: mem_deref(rr->rdata.ptr.ptrdname); break; case DNS_TYPE_MX: mem_deref(rr->rdata.mx.exchange); break; case DNS_TYPE_TXT: mem_deref(rr->rdata.txt.data); break; case DNS_TYPE_SRV: mem_deref(rr->rdata.srv.target); break; case DNS_TYPE_NAPTR: mem_deref(rr->rdata.naptr.flags); mem_deref(rr->rdata.naptr.services); mem_deref(rr->rdata.naptr.regexp); mem_deref(rr->rdata.naptr.replace); break; } } /** * Allocate a new DNS Resource Record (RR) * * @return Newly allocated Resource Record, or NULL if no memory */ struct dnsrr *dns_rr_alloc(void) { return mem_zalloc(sizeof(struct dnsrr), rr_destructor); } /** * Duplicate a DNS Resource Record (RR) * * @param rrp Pointer to new Resource Record * @param rr Resource Record to duplicate * * @return 0 if success, otherwise errorcode */ int dns_rr_dup(struct dnsrr **rrp, const struct dnsrr *rr) { struct dnsrr *new_rr; int err = 0; if (!rrp || !rr) return EINVAL; new_rr = dns_rr_alloc(); if (!new_rr) return ENOMEM; new_rr->type = rr->type; new_rr->dnsclass = rr->dnsclass; new_rr->ttl = rr->ttl; new_rr->rdlen = rr->rdlen; err = str_dup(&new_rr->name, rr->name); if (err) goto error; switch (rr->type) { case DNS_TYPE_A: new_rr->rdata.a.addr = rr->rdata.a.addr; break; case DNS_TYPE_NS: err = str_dup(&new_rr->rdata.ns.nsdname, rr->rdata.ns.nsdname); if (err) goto error; break; case DNS_TYPE_CNAME: err = str_dup(&new_rr->rdata.cname.cname, rr->rdata.cname.cname); if (err) goto error; break; case DNS_TYPE_SOA: err = str_dup(&new_rr->rdata.soa.mname, rr->rdata.soa.mname); if (err) goto error; err = str_dup(&new_rr->rdata.soa.rname, rr->rdata.soa.rname); if (err) goto error; new_rr->rdata.soa.serial = rr->rdata.soa.serial; new_rr->rdata.soa.refresh = rr->rdata.soa.refresh; new_rr->rdata.soa.retry = rr->rdata.soa.retry; new_rr->rdata.soa.expire = rr->rdata.soa.expire; new_rr->rdata.soa.ttlmin = rr->rdata.soa.ttlmin; break; case DNS_TYPE_PTR: err = str_dup(&new_rr->rdata.ptr.ptrdname, rr->rdata.ptr.ptrdname); if (err) goto error; break; case DNS_TYPE_MX: new_rr->rdata.mx.pref = rr->rdata.mx.pref; err = str_dup(&new_rr->rdata.mx.exchange, rr->rdata.mx.exchange); if (err) goto error; break; case DNS_TYPE_TXT: err = str_dup(&new_rr->rdata.txt.data, rr->rdata.txt.data); if (err) goto error; break; case DNS_TYPE_AAAA: memcpy(new_rr->rdata.aaaa.addr, rr->rdata.aaaa.addr, 16); break; case DNS_TYPE_SRV: new_rr->rdata.srv.pri = rr->rdata.srv.pri; new_rr->rdata.srv.weight = rr->rdata.srv.weight; new_rr->rdata.srv.port = rr->rdata.srv.port; err = str_dup(&new_rr->rdata.srv.target, rr->rdata.srv.target); if (err) goto error; break; case DNS_TYPE_NAPTR: new_rr->rdata.naptr.order = rr->rdata.naptr.order; new_rr->rdata.naptr.pref = rr->rdata.naptr.pref; err = str_dup(&new_rr->rdata.naptr.flags, rr->rdata.naptr.flags); if (err) goto error; err = str_dup(&new_rr->rdata.naptr.services, rr->rdata.naptr.services); if (err) goto error; err = str_dup(&new_rr->rdata.naptr.regexp, rr->rdata.naptr.regexp); if (err) goto error; err = str_dup(&new_rr->rdata.naptr.replace, rr->rdata.naptr.replace); if (err) goto error; break; default: err = ENOSYS; goto error; } *rrp = new_rr; return 0; error: mem_deref(new_rr); return err; } /** * Encode a DNS Resource Record * * @param mb Memory buffer to encode into * @param rr DNS Resource Record * @param ttl_offs TTL Offset * @param ht_dname Domain name hash-table * @param start Start position * * @return 0 if success, otherwise errorcode */ int dns_rr_encode(struct mbuf *mb, const struct dnsrr *rr, int64_t ttl_offs, struct hash *ht_dname, size_t start) { uint32_t ttl; size_t start_rdata, dlen, len; char *ptr; int err = 0; if (!mb || !rr) return EINVAL; ttl = (uint32_t)((rr->ttl > ttl_offs) ? (rr->ttl - ttl_offs) : 0); err |= dns_dname_encode(mb, rr->name, ht_dname, start, true); err |= mbuf_write_u16(mb, htons(rr->type)); err |= mbuf_write_u16(mb, htons(rr->dnsclass)); err |= mbuf_write_u32(mb, htonl(ttl)); err |= mbuf_write_u16(mb, htons(rr->rdlen)); start_rdata = mb->pos; switch (rr->type) { case DNS_TYPE_A: err |= mbuf_write_u32(mb, htonl(rr->rdata.a.addr)); break; case DNS_TYPE_NS: err |= dns_dname_encode(mb, rr->rdata.ns.nsdname, ht_dname, start, true); break; case DNS_TYPE_CNAME: err |= dns_dname_encode(mb, rr->rdata.cname.cname, ht_dname, start, true); break; case DNS_TYPE_SOA: err |= dns_dname_encode(mb, rr->rdata.soa.mname, ht_dname, start, true); err |= dns_dname_encode(mb, rr->rdata.soa.rname, ht_dname, start, true); err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.serial)); err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.refresh)); err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.retry)); err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.expire)); err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.ttlmin)); break; case DNS_TYPE_PTR: err |= dns_dname_encode(mb, rr->rdata.ptr.ptrdname, ht_dname, start, true); break; case DNS_TYPE_MX: err |= mbuf_write_u16(mb, htons(rr->rdata.mx.pref)); err |= dns_dname_encode(mb, rr->rdata.mx.exchange, ht_dname, start, true); break; case DNS_TYPE_TXT: ptr = rr->rdata.txt.data; dlen = str_len(rr->rdata.txt.data); do { uint8_t slen = min((uint8_t)dlen, 0xff); err |= mbuf_write_u8(mb, slen); err |= mbuf_write_mem(mb, (uint8_t *)ptr, slen); ptr += slen; dlen -= slen; } while (dlen > 0); break; case DNS_TYPE_AAAA: err |= mbuf_write_mem(mb, rr->rdata.aaaa.addr, 16); break; case DNS_TYPE_SRV: err |= mbuf_write_u16(mb, htons(rr->rdata.srv.pri)); err |= mbuf_write_u16(mb, htons(rr->rdata.srv.weight)); err |= mbuf_write_u16(mb, htons(rr->rdata.srv.port)); err |= dns_dname_encode(mb, rr->rdata.srv.target, ht_dname, start, false); break; case DNS_TYPE_NAPTR: err |= mbuf_write_u16(mb, htons(rr->rdata.naptr.order)); err |= mbuf_write_u16(mb, htons(rr->rdata.naptr.pref)); err |= dns_cstr_encode(mb, rr->rdata.naptr.flags); err |= dns_cstr_encode(mb, rr->rdata.naptr.services); err |= dns_cstr_encode(mb, rr->rdata.naptr.regexp); err |= dns_dname_encode(mb, rr->rdata.naptr.replace, ht_dname, start, false); break; default: err = EINVAL; break; } len = mb->pos - start_rdata; if (len > 0xffff) return EOVERFLOW; mb->pos = start_rdata - 2; err |= mbuf_write_u16(mb, htons((uint16_t)len)); mb->pos += len; return err; } /** * Decode a DNS Resource Record (RR) from a memory buffer * * @param mb Memory buffer to decode from * @param rr Pointer to allocated Resource Record * @param start Start position * * @return 0 if success, otherwise errorcode */ int dns_rr_decode(struct mbuf *mb, struct dnsrr **rr, size_t start) { int err = 0; struct dnsrr *lrr; uint16_t rdlen; char *ptr; if (!mb || !rr) return EINVAL; lrr = dns_rr_alloc(); if (!lrr) return ENOMEM; err = dns_dname_decode(mb, &lrr->name, start); if (err) goto error; if (mbuf_get_left(mb) < 10) goto fmerr; lrr->type = ntohs(mbuf_read_u16(mb)); lrr->dnsclass = ntohs(mbuf_read_u16(mb)); lrr->ttl = ntohl(mbuf_read_u32(mb)); lrr->rdlen = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < lrr->rdlen) goto fmerr; switch (lrr->type) { case DNS_TYPE_A: if (lrr->rdlen != 4) goto fmerr; lrr->rdata.a.addr = ntohl(mbuf_read_u32(mb)); break; case DNS_TYPE_NS: err = dns_dname_decode(mb, &lrr->rdata.ns.nsdname, start); if (err) goto error; break; case DNS_TYPE_CNAME: err = dns_dname_decode(mb, &lrr->rdata.cname.cname, start); if (err) goto error; break; case DNS_TYPE_SOA: err = dns_dname_decode(mb, &lrr->rdata.soa.mname, start); if (err) goto error; err = dns_dname_decode(mb, &lrr->rdata.soa.rname, start); if (err) goto error; if (mbuf_get_left(mb) < 20) goto fmerr; lrr->rdata.soa.serial = ntohl(mbuf_read_u32(mb)); lrr->rdata.soa.refresh = ntohl(mbuf_read_u32(mb)); lrr->rdata.soa.retry = ntohl(mbuf_read_u32(mb)); lrr->rdata.soa.expire = ntohl(mbuf_read_u32(mb)); lrr->rdata.soa.ttlmin = ntohl(mbuf_read_u32(mb)); break; case DNS_TYPE_PTR: err = dns_dname_decode(mb, &lrr->rdata.ptr.ptrdname, start); if (err) goto error; break; case DNS_TYPE_MX: if (mbuf_get_left(mb) < 2) goto fmerr; lrr->rdata.mx.pref = ntohs(mbuf_read_u16(mb)); err = dns_dname_decode(mb, &lrr->rdata.mx.exchange, start); if (err) goto error; break; case DNS_TYPE_TXT: ptr = lrr->rdata.txt.data = mem_alloc(lrr->rdlen + 1, NULL); if (!lrr->rdata.txt.data) { err = ENOMEM; goto error; } rdlen = lrr->rdlen; while (rdlen > 0) { uint8_t len = mbuf_read_u8(mb); if (len > --rdlen) goto fmerr; err = mbuf_read_mem(mb, (uint8_t *)ptr, len); if (err) goto error; ptr += len; rdlen -= len; } *ptr = '\0'; break; case DNS_TYPE_AAAA: if (lrr->rdlen != 16) goto fmerr; err = mbuf_read_mem(mb, lrr->rdata.aaaa.addr, 16); if (err) goto error; break; case DNS_TYPE_SRV: if (mbuf_get_left(mb) < 6) goto fmerr; lrr->rdata.srv.pri = ntohs(mbuf_read_u16(mb)); lrr->rdata.srv.weight = ntohs(mbuf_read_u16(mb)); lrr->rdata.srv.port = ntohs(mbuf_read_u16(mb)); err = dns_dname_decode(mb, &lrr->rdata.srv.target, start); if (err) goto error; break; case DNS_TYPE_NAPTR: if (mbuf_get_left(mb) < 4) goto fmerr; lrr->rdata.naptr.order = ntohs(mbuf_read_u16(mb)); lrr->rdata.naptr.pref = ntohs(mbuf_read_u16(mb)); err = dns_cstr_decode(mb, &lrr->rdata.naptr.flags); if (err) goto error; err = dns_cstr_decode(mb, &lrr->rdata.naptr.services); if (err) goto error; err = dns_cstr_decode(mb, &lrr->rdata.naptr.regexp); if (err) goto error; err = dns_dname_decode(mb, &lrr->rdata.naptr.replace, start); if (err) goto error; break; default: mb->pos += lrr->rdlen; break; } *rr = lrr; return 0; fmerr: err = EINVAL; error: mem_deref(lrr); return err; } /** * Compare two DNS Resource Records * * @param rr1 First Resource Record * @param rr2 Second Resource Record * @param rdata If true, also compares Resource Record data * * @return True if match, false if not match */ bool dns_rr_cmp(const struct dnsrr *rr1, const struct dnsrr *rr2, bool rdata) { if (!rr1 || !rr2) return false; if (rr1 == rr2) return true; if (rr1->type != rr2->type) return false; if (rr1->dnsclass != rr2->dnsclass) return false; if (str_casecmp(rr1->name, rr2->name)) return false; if (!rdata) return true; switch (rr1->type) { case DNS_TYPE_A: if (rr1->rdata.a.addr != rr2->rdata.a.addr) return false; break; case DNS_TYPE_NS: if (str_casecmp(rr1->rdata.ns.nsdname, rr2->rdata.ns.nsdname)) return false; break; case DNS_TYPE_CNAME: if (str_casecmp(rr1->rdata.cname.cname, rr2->rdata.cname.cname)) return false; break; case DNS_TYPE_SOA: if (str_casecmp(rr1->rdata.soa.mname, rr2->rdata.soa.mname)) return false; if (str_casecmp(rr1->rdata.soa.rname, rr2->rdata.soa.rname)) return false; if (rr1->rdata.soa.serial != rr2->rdata.soa.serial) return false; if (rr1->rdata.soa.refresh != rr2->rdata.soa.refresh) return false; if (rr1->rdata.soa.retry != rr2->rdata.soa.retry) return false; if (rr1->rdata.soa.expire != rr2->rdata.soa.expire) return false; if (rr1->rdata.soa.ttlmin != rr2->rdata.soa.ttlmin) return false; break; case DNS_TYPE_PTR: if (str_casecmp(rr1->rdata.ptr.ptrdname, rr2->rdata.ptr.ptrdname)) return false; break; case DNS_TYPE_MX: if (rr1->rdata.mx.pref != rr2->rdata.mx.pref) return false; if (str_casecmp(rr1->rdata.mx.exchange, rr2->rdata.mx.exchange)) return false; break; case DNS_TYPE_TXT: if (str_casecmp(rr1->rdata.txt.data, rr2->rdata.txt.data)) return false; break; case DNS_TYPE_AAAA: if (memcmp(rr1->rdata.aaaa.addr, rr2->rdata.aaaa.addr, 16)) return false; break; case DNS_TYPE_SRV: if (rr1->rdata.srv.pri != rr2->rdata.srv.pri) return false; if (rr1->rdata.srv.weight != rr2->rdata.srv.weight) return false; if (rr1->rdata.srv.port != rr2->rdata.srv.port) return false; if (str_casecmp(rr1->rdata.srv.target, rr2->rdata.srv.target)) return false; break; case DNS_TYPE_NAPTR: if (rr1->rdata.naptr.order != rr2->rdata.naptr.order) return false; if (rr1->rdata.naptr.pref != rr2->rdata.naptr.pref) return false; /* todo check case sensitiveness */ if (str_casecmp(rr1->rdata.naptr.flags, rr2->rdata.naptr.flags)) return false; /* todo check case sensitiveness */ if (str_casecmp(rr1->rdata.naptr.services, rr2->rdata.naptr.services)) return false; /* todo check case sensitiveness */ if (str_casecmp(rr1->rdata.naptr.regexp, rr2->rdata.naptr.regexp)) return false; /* todo check case sensitiveness */ if (str_casecmp(rr1->rdata.naptr.replace, rr2->rdata.naptr.replace)) return false; break; default: return false; } return true; } /** * Get the DNS Resource Record (RR) name * * @param type DNS Resource Record type * * @return DNS Resource Record name */ const char *dns_rr_typename(uint16_t type) { switch (type) { case DNS_TYPE_A: return "A"; case DNS_TYPE_NS: return "NS"; case DNS_TYPE_CNAME: return "CNAME"; case DNS_TYPE_SOA: return "SOA"; case DNS_TYPE_PTR: return "PTR"; case DNS_TYPE_MX: return "MX"; case DNS_TYPE_TXT: return "TXT"; case DNS_TYPE_AAAA: return "AAAA"; case DNS_TYPE_SRV: return "SRV"; case DNS_TYPE_NAPTR: return "NAPTR"; case DNS_QTYPE_IXFR: return "IXFR"; case DNS_QTYPE_AXFR: return "AXFR"; case DNS_QTYPE_ANY: return "ANY"; default: return "??"; } } /** * Get the DNS Resource Record (RR) class name * * @param dnsclass DNS Class * * @return DNS Class name */ const char *dns_rr_classname(uint16_t dnsclass) { switch (dnsclass) { case DNS_CLASS_IN: return "IN"; case DNS_QCLASS_ANY: return "ANY"; default: return "??"; } } /** * Print a DNS Resource Record * * @param pf Print function * @param rr DNS Resource Record * * @return 0 if success, otherwise errorcode */ int dns_rr_print(struct re_printf *pf, const struct dnsrr *rr) { static const size_t w = 24; struct sa sa; size_t n, l; int err; if (!pf || !rr) return EINVAL; l = str_len(rr->name); n = (w > l) ? w - l : 0; err = re_hprintf(pf, "%s.", rr->name); while (n) { err |= pf->vph(" ", 1, pf->arg); --n; } err |= re_hprintf(pf, " %10lld %-4s %-7s ", rr->ttl, dns_rr_classname(rr->dnsclass), dns_rr_typename(rr->type)); switch (rr->type) { case DNS_TYPE_A: sa_set_in(&sa, rr->rdata.a.addr, 0); err |= re_hprintf(pf, "%j", &sa); break; case DNS_TYPE_NS: err |= re_hprintf(pf, "%s.", rr->rdata.ns.nsdname); break; case DNS_TYPE_CNAME: err |= re_hprintf(pf, "%s.", rr->rdata.cname.cname); break; case DNS_TYPE_SOA: err |= re_hprintf(pf, "%s. %s. %u %u %u %u %u", rr->rdata.soa.mname, rr->rdata.soa.rname, rr->rdata.soa.serial, rr->rdata.soa.refresh, rr->rdata.soa.retry, rr->rdata.soa.expire, rr->rdata.soa.ttlmin); break; case DNS_TYPE_PTR: err |= re_hprintf(pf, "%s.", rr->rdata.ptr.ptrdname); break; case DNS_TYPE_MX: err |= re_hprintf(pf, "%3u %s.", rr->rdata.mx.pref, rr->rdata.mx.exchange); break; case DNS_TYPE_TXT: err |= re_hprintf(pf, "\"%s\"", rr->rdata.txt.data); break; case DNS_TYPE_AAAA: sa_set_in6(&sa, rr->rdata.aaaa.addr, 0); err |= re_hprintf(pf, "%j", &sa); break; case DNS_TYPE_SRV: err |= re_hprintf(pf, "%3u %3u %u %s.", rr->rdata.srv.pri, rr->rdata.srv.weight, rr->rdata.srv.port, rr->rdata.srv.target); break; case DNS_TYPE_NAPTR: err |= re_hprintf(pf, "%3u %3u \"%s\" \"%s\" \"%s\" %s.", rr->rdata.naptr.order, rr->rdata.naptr.pref, rr->rdata.naptr.flags, rr->rdata.naptr.services, rr->rdata.naptr.regexp, rr->rdata.naptr.replace); break; default: err |= re_hprintf(pf, "?"); break; } return err; } ================================================ FILE: src/dns/rrlist.c ================================================ /** * @file rrlist.c DNS Resource Records list * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include enum { CNAME_RECURSE_MAX = 16, }; struct sort { uint16_t type; uint32_t key; }; static uint32_t sidx(const struct dnsrr *rr, uint32_t key) { uint32_t addr[4]; switch (rr->type) { case DNS_TYPE_A: return rr->rdata.a.addr ^ key; case DNS_TYPE_AAAA: memcpy(addr, rr->rdata.aaaa.addr, 16); return addr[0] ^ addr[1] ^ addr[2] ^ addr[3] ^ key; case DNS_TYPE_SRV: return ((hash_fast_str(rr->rdata.srv.target) & 0xfff) ^ key) + rr->rdata.srv.weight; default: return 0; } } static bool std_sort_handler(struct le *le1, struct le *le2, void *arg) { struct dnsrr *rr1 = le1->data; struct dnsrr *rr2 = le2->data; struct sort *sort = arg; if (sort->type != rr1->type) return sort->type != rr2->type; if (sort->type != rr2->type) return true; switch (sort->type) { case DNS_TYPE_MX: return rr1->rdata.mx.pref <= rr2->rdata.mx.pref; case DNS_TYPE_SRV: if (rr1->rdata.srv.pri == rr2->rdata.srv.pri) return sidx(rr1, sort->key) >= sidx(rr2, sort->key); return rr1->rdata.srv.pri < rr2->rdata.srv.pri; case DNS_TYPE_NAPTR: if (rr1->rdata.naptr.order == rr2->rdata.naptr.order) return rr1->rdata.naptr.pref <= rr2->rdata.naptr.pref; return rr1->rdata.naptr.order < rr2->rdata.naptr.order; default: break; } return true; } static bool addr_sort_handler(struct le *le1, struct le *le2, void *arg) { struct dnsrr *rr1 = le1->data; struct dnsrr *rr2 = le2->data; struct sort *sort = arg; return sidx(rr1, sort->key) >= sidx(rr2, sort->key); } /** * Sort a list of DNS Resource Records * * @param rrl DNS Resource Record list * @param type DNS Record type * @param key Sort key */ void dns_rrlist_sort(struct list *rrl, uint16_t type, size_t key) { struct sort sort = {type, (uint32_t)key>>5}; list_sort(rrl, std_sort_handler, &sort); } /** * Sort a list of A/AAAA DNS Resource Records * * @param rrl DNS Resource Record list * @param key Sort key */ void dns_rrlist_sort_addr(struct list *rrl, size_t key) { struct sort sort = {0, (uint32_t)key>>5}; list_sort(rrl, addr_sort_handler, &sort); } static struct dnsrr *rrlist_apply(struct list *rrl, const char *name, uint16_t type1, uint16_t type2, uint16_t dnsclass, bool recurse, uint32_t depth, dns_rrlist_h *rrlh, void *arg) { struct le *le = list_head(rrl); if (depth > CNAME_RECURSE_MAX) return NULL; while (le) { struct dnsrr *rr = le->data; le = le->next; if (name && str_casecmp(name, rr->name)) continue; if (type1 != DNS_QTYPE_ANY && type2 != DNS_QTYPE_ANY && rr->type != type1 && rr->type != type2 && (rr->type != DNS_TYPE_CNAME || !recurse)) continue; if (dnsclass != DNS_QCLASS_ANY && rr->dnsclass != dnsclass) continue; if (!rrlh || rrlh(rr, arg)) return rr; if (recurse && DNS_QTYPE_ANY != type1 && DNS_QTYPE_ANY != type2 && DNS_TYPE_CNAME != type1 && DNS_TYPE_CNAME != type2 && DNS_TYPE_CNAME == rr->type) { rr = rrlist_apply(rrl, rr->rdata.cname.cname, type1, type2, dnsclass, recurse, ++depth, rrlh, arg); if (rr) return rr; } } return NULL; } /** * Apply a function handler to a list of DNS Resource Records * * @param rrl DNS Resource Record list * @param name If set, filter on domain name * @param type If not DNS_QTYPE_ANY, filter on record type * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class * @param recurse Cname recursion * @param rrlh Resource record handler * @param arg Handler argument * * @return Matching Resource Record or NULL */ struct dnsrr *dns_rrlist_apply(struct list *rrl, const char *name, uint16_t type, uint16_t dnsclass, bool recurse, dns_rrlist_h *rrlh, void *arg) { return rrlist_apply(rrl, name, type, type, dnsclass, recurse, 0, rrlh, arg); } /** * Apply a function handler to a list of DNS Resource Records (two types) * * @param rrl DNS Resource Record list * @param name If set, filter on domain name * @param type1 If not DNS_QTYPE_ANY, filter on record type * @param type2 If not DNS_QTYPE_ANY, filter on record type * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class * @param recurse Cname recursion * @param rrlh Resource record handler * @param arg Handler argument * * @return Matching Resource Record or NULL */ struct dnsrr *dns_rrlist_apply2(struct list *rrl, const char *name, uint16_t type1, uint16_t type2, uint16_t dnsclass, bool recurse, dns_rrlist_h *rrlh, void *arg) { return rrlist_apply(rrl, name, type1, type2, dnsclass, recurse, 0, rrlh, arg); } static bool find_handler(struct dnsrr *rr, void *arg) { uint16_t type = *(uint16_t *)arg; return rr->type == type; } /** * Find a DNS Resource Record in a list * * @param rrl Resource Record list * @param name If set, filter on domain name * @param type If not DNS_QTYPE_ANY, filter on record type * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class * @param recurse Cname recursion * * @return Matching Resource Record or NULL */ struct dnsrr *dns_rrlist_find(struct list *rrl, const char *name, uint16_t type, uint16_t dnsclass, bool recurse) { return rrlist_apply(rrl, name, type, type, dnsclass, recurse, 0, find_handler, &type); } ================================================ FILE: src/dns/win32/srv.c ================================================ /** * @file win32/srv.c Get DNS Server IP code for Windows * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "../dns.h" #define DEBUG_MODULE "win32/srv" #define DEBUG_LEVEL 5 #include int get_windns(struct sa *srvv, uint32_t *n) { FIXED_INFO * FixedInfo = NULL; ULONG ulOutBufLen; DWORD dwRetVal; IP_ADDR_STRING * pIPAddr; HANDLE hLib; union { FARPROC proc; DWORD (WINAPI *_GetNetworkParams)(FIXED_INFO*, DWORD*); } u; uint32_t i; int err; if (!srvv || !n || !*n) return EINVAL; hLib = LoadLibrary(TEXT("iphlpapi.dll")); if (!hLib) return ENOSYS; u.proc = GetProcAddress(hLib, TEXT("GetNetworkParams")); if (!u.proc) { err = ENOSYS; goto out; } FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof( FIXED_INFO )); ulOutBufLen = sizeof( FIXED_INFO ); if (ERROR_BUFFER_OVERFLOW == (*u._GetNetworkParams)(FixedInfo, &ulOutBufLen)) { GlobalFree( FixedInfo ); FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, ulOutBufLen); } if ((dwRetVal = (*u._GetNetworkParams)( FixedInfo, &ulOutBufLen ))) { DEBUG_WARNING("couldn't get network params (%d)\n", dwRetVal); err = ENOENT; goto out; } #if 0 printf( "Host Name: %s\n", FixedInfo->HostName); printf( "Domain Name: %s\n", FixedInfo->DomainName); printf( "DNS Servers:\n" ); printf( "\t%s\n", FixedInfo->DnsServerList.IpAddress.String ); #endif i = 0; pIPAddr = &FixedInfo->DnsServerList; while (pIPAddr && strlen(pIPAddr->IpAddress.String) > 0) { err = sa_set_str(&srvv[i], pIPAddr->IpAddress.String, DNS_PORT); if (err) { DEBUG_WARNING("sa_set_str: %s (%m)\n", pIPAddr->IpAddress.String, err); } DEBUG_INFO("dns ip %u: %j\n", i, &srvv[i]); ++i; pIPAddr = pIPAddr ->Next; if (i >= *n) break; } *n = i; DEBUG_INFO("got %u nameservers\n", i); err = i>0 ? 0 : ENOENT; out: if (FixedInfo) GlobalFree(FixedInfo); FreeLibrary(hLib); return err; } ================================================ FILE: src/fmt/ch.c ================================================ /** * @file ch.c Character format functions * * Copyright (C) 2010 Creytiv.com */ #include #include /** * Convert an ASCII hex character to binary format * * @param ch ASCII hex character * * @return Binary value */ uint8_t ch_hex(char ch) { if ('0' <= ch && ch <= '9') return ch - '0'; else if ('A' <= ch && ch <= 'F') return ch - 'A' + 10; else if ('a' <= ch && ch <= 'f') return ch - 'a' + 10; return 0; } ================================================ FILE: src/fmt/hexdump.c ================================================ /** * @file hexdump.c Hexadecimal dumping * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Hexadecimal dump of binary buffer. Similar output to HEXDUMP(1) * * @param f File stream for output (e.g. stderr, stdout) * @param p Pointer to data * @param len Number of bytes */ void hexdump(FILE *f, const void *p, size_t len) { const uint8_t *buf = p; uint32_t j; size_t i; if (!f || !buf) return; for (i=0; i < len; i += 16) { (void)re_fprintf(f, "%08zx ", i); for (j=0; j<16; j++) { const size_t pos = i+j; if (pos < len) (void)re_fprintf(f, " %02x", buf[pos]); else (void)re_fprintf(f, " "); if (j == 7) (void)re_fprintf(f, " "); } (void)re_fprintf(f, " |"); for (j=0; j<16; j++) { const size_t pos = i+j; uint8_t v; if (pos >= len) break; v = buf[pos]; (void)re_fprintf(f, "%c", isprint(v) ? v : '.'); if (j == 7) (void)re_fprintf(f, " "); } (void)re_fprintf(f, "|\n"); } } ================================================ FILE: src/fmt/pl.c ================================================ /** * @file pl.c Pointer-length functions * * Copyright (C) 2010 Creytiv.com */ #include #include #ifdef HAVE_STRINGS_H #define __EXTENSIONS__ 1 #include #endif #include #include #include #include #include #include /** Pointer-length NULL initialiser */ const struct pl pl_null = {NULL, 0}; static void pl_alloc_destruct(void *arg) { struct pl *pl = arg; mem_deref((void *)pl->p); } /** * Allocate a pointer-length object from a NULL-terminated string * * @param str NULL-terminated string * * @return Allocated Pointer-length object or NULL */ struct pl *pl_alloc_str(const char *str) { struct pl *pl; if (!str) return NULL; size_t sz = strlen(str); pl = mem_zalloc(sizeof(struct pl), pl_alloc_destruct); if (!pl) return NULL; pl->p = mem_alloc(sz, NULL); if (!pl->p) { mem_deref(pl); return NULL; } memcpy((void *)pl->p, str, sz); pl->l = sz; return pl; } /** * Duplicate a pointer-length object * * @param src Pointer-length object to duplicate * * @return Allocated Pointer-length object or NULL */ struct pl *pl_alloc_dup(const struct pl *src) { struct pl *pl; if (!src) return NULL; size_t sz = src->l; pl = mem_zalloc(sizeof(struct pl), pl_alloc_destruct); if (!pl) return NULL; if (!pl_isset(src)) return pl; pl->p = mem_alloc(sz, NULL); if (!pl->p) { mem_deref(pl); return NULL; } memcpy((void *)pl->p, src->p, sz); pl->l = sz; return pl; } /** * Initialise a pointer-length object from a NULL-terminated string * * @param pl Pointer-length object to be initialised * @param str NULL-terminated string */ void pl_set_str(struct pl *pl, const char *str) { if (!pl || !str) return; pl->p = str; pl->l = strlen(str); } /** * Initialise a pointer-length object from current position and * length of a memory buffer * * @param pl Pointer-length object to be initialised * @param mb Memory buffer */ void pl_set_mbuf(struct pl *pl, const struct mbuf *mb) { if (!pl || !mb) return; pl->p = (char *)mbuf_buf(mb); pl->l = mbuf_get_left(mb); } /** * Convert a pointer-length object to an int32_t. * * @param pl Pointer-length object * * @return int value */ int32_t pl_i32(const struct pl *pl) { int32_t v = 0; uint32_t mul = 1; const char *p; bool neg = false; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const char ch = *--p; if ('0' <= ch && ch <= '9') { v -= mul * (ch - '0'); mul *= 10; } else if (ch == '-' && p == pl->p) { neg = true; break; } else if (ch == '+' && p == pl->p) { break; } else { return 0; } } if (!neg && v == INT32_MIN) return INT32_MIN; return neg ? v : -v; } /** * Convert a pointer-length object to an int64_t. * * @param pl Pointer-length object * * @return int value */ int64_t pl_i64(const struct pl *pl) { int64_t v = 0; uint64_t mul = 1; const char *p; bool neg = false; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const char ch = *--p; if ('0' <= ch && ch <= '9') { v -= mul * (ch - '0'); mul *= 10; } else if (ch == '-' && p == pl->p) { neg = true; break; } else if (ch == '+' && p == pl->p) { break; } else { return 0; } } if (!neg && v == INT64_MIN) return INT64_MIN; return neg ? v : -v; } /** * Convert a pointer-length object to a numeric 32-bit value * * @param pl Pointer-length object * * @return 32-bit value */ uint32_t pl_u32(const struct pl *pl) { uint32_t v=0, mul=1; const char *p; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const uint8_t c = *--p - '0'; if (c > 9) return 0; v += mul * c; mul *= 10; } return v; } /** * Convert a hex pointer-length object to a numeric 32-bit value * * @param pl Pointer-length object * * @return 32-bit value */ uint32_t pl_x32(const struct pl *pl) { uint32_t v=0, mul=1; const char *p; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const char ch = *--p; uint8_t c; if ('0' <= ch && ch <= '9') c = ch - '0'; else if ('A' <= ch && ch <= 'F') c = ch - 'A' + 10; else if ('a' <= ch && ch <= 'f') c = ch - 'a' + 10; else return 0; v += mul * c; mul *= 16; } return v; } /** * Convert a pointer-length object to a numeric 64-bit value * * @param pl Pointer-length object * * @return 64-bit value */ uint64_t pl_u64(const struct pl *pl) { uint64_t v=0, mul=1; const char *p; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const uint8_t c = *--p - '0'; if (c > 9) return 0; v += mul * c; mul *= 10; } return v; } /** * Convert a hex pointer-length object to a numeric 64-bit value * * @param pl Pointer-length object * * @return 64-bit value */ uint64_t pl_x64(const struct pl *pl) { uint64_t v=0, mul=1; const char *p; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const char ch = *--p; uint8_t c; if ('0' <= ch && ch <= '9') c = ch - '0'; else if ('A' <= ch && ch <= 'F') c = ch - 'A' + 10; else if ('a' <= ch && ch <= 'f') c = ch - 'a' + 10; else return 0; v += mul * c; mul *= 16; } return v; } /** * Convert a pointer-length object to floating point representation. * Both positive and negative numbers are supported, a string with a * minus sign ('-') is treated as a negative number. * * @param pl Pointer-length object * * @return Double value */ double pl_float(const struct pl *pl) { double v=0, mul=1; const char *p; bool neg = false; if (!pl || !pl->p) return 0; p = &pl->p[pl->l]; while (p > pl->p) { const char ch = *--p; if ('0' <= ch && ch <= '9') { v += mul * (ch - '0'); mul *= 10; } else if (ch == '.') { v /= mul; mul = 1; } else if (ch == '-' && p == pl->p) { neg = true; } else { return 0; } } return neg ? -v : v; } /** * Decode a bool from a pointer-length object * * @param val Pointer to bool for returned value * @param pl Pointer-length object * * @return int 0 if success, otherwise errorcode */ int pl_bool(bool *val, const struct pl *pl) { const char *tval[] = {"1", "true", "enable", "yes", "on"}; const char *fval[] = {"0", "false", "disable", "no", "off"}; size_t i; if (!val || !pl) return EINVAL; for (i = 0; i < RE_ARRAY_SIZE(tval); ++i) { if (!pl_strcasecmp(pl, tval[i])) { *val = true; return 0; } } for (i = 0; i < RE_ARRAY_SIZE(fval); ++i) { if (!pl_strcasecmp(pl, fval[i])) { *val = false; return 0; } } return EINVAL; } /** * Convert an ASCII hex string as a pointer-length object to binary format * * @param pl Pointer-length object * @param hex Destination binary buffer * @param len Length of binary buffer * * @return 0 if success, otherwise errorcode */ int pl_hex(const struct pl *pl, uint8_t *hex, size_t len) { if (!pl_isset(pl) || !hex || (pl->l != (2 * len))) return EINVAL; for (size_t i = 0; i < pl->l; i += 2) { hex[i/2] = ch_hex(*(pl->p + i)) << 4; hex[i/2] += ch_hex(*(pl->p + i +1)); } return 0; } /** * Check if pointer-length object is set * * @param pl Pointer-length object * * @return true if set, false if not set */ bool pl_isset(const struct pl *pl) { return pl ? pl->p && pl->l : false; } /** * Copy a pointer-length object to a NULL-terminated string * * @param pl Pointer-length object * @param str Buffer for NULL-terminated string * @param size Size of buffer * * @return 0 if success, otherwise errorcode */ int pl_strcpy(const struct pl *pl, char *str, size_t size) { size_t len; if (!pl || !pl->p || !str || !size) return EINVAL; len = min(pl->l, size-1); memcpy(str, pl->p, len); str[len] = '\0'; return 0; } /** * Duplicate a pointer-length object to a NULL-terminated string * * @param dst Pointer to destination string (set on return) * @param src Source pointer-length object * * @return 0 if success, otherwise errorcode */ int pl_strdup(char **dst, const struct pl *src) { char *p; if (!dst || !src || !src->p) return EINVAL; p = mem_alloc(src->l+1, NULL); if (!p) return ENOMEM; memcpy(p, src->p, src->l); p[src->l] = '\0'; *dst = p; return 0; } /** * Duplicate a pointer-length object to a new pointer-length object * * @param dst Destination pointer-length object (set on return) * @param src Source pointer-length object * * @return 0 if success, otherwise errorcode */ int pl_dup(struct pl *dst, const struct pl *src) { char *p; if (!dst || !src || !src->p) return EINVAL; p = mem_alloc(src->l, NULL); if (!p) return ENOMEM; memcpy(p, src->p, src->l); dst->p = p; dst->l = src->l; return 0; } /** * Compare a pointer-length object with a NULL-terminated string * (case-sensitive) * * @param pl Pointer-length object * @param str NULL-terminated string * * @return 0 if match, otherwise errorcode */ int pl_strcmp(const struct pl *pl, const char *str) { struct pl s; if (!pl || !str) return EINVAL; pl_set_str(&s, str); return pl_cmp(pl, &s); } /** * Compare n characters of a pointer-length object with a NULL-terminated * string (case-sensitive) * * @param pl Pointer-length object * @param str NULL-terminated string * @param n number of characters that should be compared * * @return 0 if match, otherwise errorcode */ int pl_strncmp(const struct pl *pl, const char *str, size_t n) { if (!pl_isset(pl) || !str || !n) return EINVAL; if (pl->l < n) return EINVAL; return strncmp(pl->p, str, n) == 0 ? 0 : EINVAL; } /** * Compare n characters of a pointer-length object with a NULL-terminated * string (case-insensitive) * * @param pl Pointer-length object * @param str NULL-terminated string * @param n number of characters that should be compared * * @return 0 if match, otherwise errorcode */ int pl_strncasecmp(const struct pl *pl, const char *str, size_t n) { if (!pl_isset(pl) || !str || !n) return EINVAL; if (pl->l < n) return EINVAL; #ifdef WIN32 return _strnicmp(pl->p, str, n) == 0 ? 0 : EINVAL; #else return strncasecmp(pl->p, str, n) == 0 ? 0 : EINVAL; #endif } /** * Compare a pointer-length object with a NULL-terminated string * (case-insensitive) * * @param pl Pointer-length object * @param str NULL-terminated string * * @return 0 if match, otherwise errorcode */ int pl_strcasecmp(const struct pl *pl, const char *str) { struct pl s; if (!pl || !str) return EINVAL; pl_set_str(&s, str); return pl_casecmp(pl, &s); } /** * Compare two pointer-length objects (case-sensitive) * * @param pl1 First pointer-length object * @param pl2 Second pointer-length object * * @return 0 if match, otherwise errorcode */ int pl_cmp(const struct pl *pl1, const struct pl *pl2) { if (!pl1 || !pl2) return EINVAL; /* Different length -> no match */ if (pl1->l != pl2->l) return EINVAL; /* Zero-length strings are always identical */ if (pl1->l == 0) return 0; /* * ~35% speed increase for fmt/pl test */ /* The two pl's are the same */ if (pl1 == pl2) return 0; /* Two different pl's pointing to same string */ if (pl1->p == pl2->p) return 0; return 0 == memcmp(pl1->p, pl2->p, pl1->l) ? 0 : EINVAL; } #ifndef HAVE_STRINGS_H static int casecmp(const struct pl *pl, const char *str) { size_t i = 0; #define LOWER(d) ((d) | 0x20202020) const uint32_t *p1 = (uint32_t *)pl->p; const uint32_t *p2 = (uint32_t *)str; const size_t len = pl->l & ~0x3; /* Skip any unaligned pointers */ if (((size_t)pl->p) & (sizeof(void *) - 1)) goto next; if (((size_t)str) & (sizeof(void *) - 1)) goto next; /* Compare word-wise */ for (; il; i++) { if (tolower(pl->p[i]) != tolower(str[i])) return EINVAL; } return 0; } #endif /** * Compare two pointer-length objects (case-insensitive) * * @param pl1 First pointer-length object * @param pl2 Second pointer-length object * * @return 0 if match, otherwise errorcode */ int pl_casecmp(const struct pl *pl1, const struct pl *pl2) { if (!pl1 || !pl2) return EINVAL; /* Different length -> no match */ if (pl1->l != pl2->l) return EINVAL; /* Zero-length strings are always identical */ if (pl1->l == 0) return 0; /* * ~35% speed increase for fmt/pl test */ /* The two pl's are the same */ if (pl1 == pl2) return 0; /* Two different pl's pointing to same string */ if (pl1->p == pl2->p) return 0; #ifdef HAVE_STRINGS_H return 0 == strncasecmp(pl1->p, pl2->p, pl1->l) ? 0 : EINVAL; #else return casecmp(pl1, pl2->p); #endif } /** * Locate character in pointer-length string * * @param pl Pointer-length string * @param c Character to locate * * @return Pointer to first char if found, otherwise NULL */ const char *pl_strchr(const struct pl *pl, char c) { const char *p, *end; if (!pl) return NULL; end = pl->p + pl->l; for (p = pl->p; p < end; p++) { if (*p == c) return p; } return NULL; } /** * Locate the last occurrence of character in pointer-length string * * @param pl Pointer-length string * @param c Character to locate * * @return Pointer to last char if found, otherwise NULL */ const char *pl_strrchr(const struct pl *pl, char c) { const char *p, *end; if (!pl_isset(pl)) return NULL; end = pl->p + pl->l - 1; for (p = end; p >= pl->p; p--) { if (*p == c) return p; } return NULL; } /** * Locate the first substring in a pointer-length string * * @param pl Pointer-length string * @param str Substring to locate * * @return Pointer to first char if substring is found, otherwise NULL */ const char *pl_strstr(const struct pl *pl, const char *str) { size_t len = str_len(str); /*case pl not set & pl is not long enough*/ if (!pl_isset(pl) || pl->l < len) return NULL; /*case str is empty or just '\0'*/ if (!len) return pl->p; for (size_t i = 0; i < pl->l; ++i) { /*case rest of pl is not long enough*/ if (pl->l - i < len) return NULL; if (!memcmp(pl->p + i, str, len)) return pl->p + i; } return NULL; } /** * Trim white space characters at start of pointer-length string * * @param pl Pointer-length string * * @return int 0 if success, otherwise errorcode */ int pl_ltrim(struct pl *pl) { if (!pl) return EINVAL; if (!pl_isset(pl)) return 0; size_t i = 0; while (i < pl->l && isspace((unsigned char)pl->p[i])) { ++i; } if (i == pl->l) pl->l = 0; else pl_advance(pl, i); return 0; } /** * Trim white space characters at end of pointer-length string * * @param pl Pointer-length string * * @return int 0 if success, otherwise errorcode */ int pl_rtrim(struct pl *pl) { if (!pl) return EINVAL; if (!pl_isset(pl)) return 0; while (pl->l > 0 && isspace((unsigned char)pl->p[pl->l - 1])) { --pl->l; } return 0; } /** * Trim a pointer-length string on both ends * * @param pl Pointer-length string * * @return int 0 if success, otherwise errorcode */ int pl_trim(struct pl *pl) { int err; err = pl_ltrim(pl); err |= pl_rtrim(pl); return err; } /** * Strip HTML tags from a pointer-length string in-place * * @param pl Pointer-length string */ void pl_strip_html(struct pl *pl) { if (!pl) return; const char *r = pl->p; bool in_tag = false; /* lookup first possible html tag */ r = memchr(r, '<', pl->l); if (!r) return; char *w = (char *)r; /* Reference: https://html.spec.whatwg.org/multipage/parsing.html */ for (size_t len = pl->l - (size_t)(r - pl->p); len--; r++) { if (in_tag) { if (*r == '>') in_tag = false; continue; } /* 13.2.5.1 Data state */ if (*r == '<' && len >= 1) { /* 13.2.5.6 Tag open state */ unsigned char n = *(r + 1); if (isalpha(n) || n == '/' || n == '!' || n == '?') { in_tag = true; continue; } } unsigned char c = (unsigned char)*r; if (c >= 0x20 || c == '\n' || c == '\r' || c == '\t') { *w++ = *r; } } pl->l = (size_t)(w - pl->p); } ================================================ FILE: src/fmt/print.c ================================================ /** * @file fmt/print.c Formatted printing * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #ifdef _MSC_VER #include #ifndef isinf #define isinf(d) (!_finite(d)) #endif #ifndef isnan #define isnan(d) _isnan(d) #endif #endif #ifdef SOLARIS #include #undef isinf #define isinf(a) (fpclass((a)) == FP_NINF || fpclass((a)) == FP_PINF) #undef isnan #define isnan(a) isnand((a)) #endif #define DEBUG_MODULE "print" #define DEBUG_LEVEL 5 #include enum length_modifier { LENMOD_NONE = 0, LENMOD_LONG = 1, LENMOD_LONG_LONG = 2, LENMOD_INT64 = 3, LENMOD_SIZE = 42, }; enum { DEC_SIZE = 42, NUM_SIZE = 64 }; static const char prfx_neg[] = "-"; static const char prfx_hex[] = "0x"; static const char str_nil[] = "(nil)"; static int write_padded(const char *p, size_t sz, size_t pad, char pch, bool plr, const char *prfx, re_vprintf_h *vph, void *arg) { const size_t prfx_len = str_len(prfx); int err = 0; pad -= MIN(pad, prfx_len); if (prfx && pch == '0') err |= vph(prfx, prfx_len, arg); while (!plr && (pad-- > sz)) err |= vph(&pch, 1, arg); if (prfx && pch != '0') err |= vph(prfx, prfx_len, arg); if (p && sz) err |= vph(p, sz, arg); while (plr && pad-- > sz) err |= vph(&pch, 1, arg); return err; } static uint32_t local_itoa(char *buf, uint64_t n, uint8_t base, bool uc) { char c, *p = buf + (NUM_SIZE - 1); uint32_t len = 1; const char a = uc ? 'A' : 'a'; *p = '\0'; do { const uint64_t dv = n / base; const uint64_t mul = dv * base; c = (char)(n - mul); if (c < 10) *--p = '0' + c; else *--p = a + (c - 10); n = dv; ++len; } while (n != 0); memmove(buf, p, len); return len - 1; } static size_t local_ftoa(char *buf, double n, size_t dp) { char *p = buf; long long a = (long long)n; double b = n - (double)a; b = (b < 0) ? -b : b; /* integral part */ p += local_itoa(p, (a < 0) ? -a : a, 10, false); *p++ = '.'; /* decimal digits */ while (dp--) { char v; b *= 10; v = (char)b; b -= v; *p++ = '0' + (char)v; } *p = '\0'; return p - buf; } static int vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg, bool safe) { uint8_t base, *bptr; char pch = 0, ch, num[NUM_SIZE], addr[64], msg[256]; enum length_modifier lenmod = LENMOD_NONE; struct re_printf pf; bool fm = false, plr = false; const struct pl *pl; size_t pad = 0, fpad = -1, len, i; const char *str, *p = fmt, *p0 = fmt; const struct sa *sa; re_printf_h *ph; void *ph_arg; va_list *apl; int err = 0; void *ptr; uint64_t n; int64_t sn; bool uc = false; double dbl; int errnum; #ifndef RELEASE struct btrace bt; #endif if (!fmt || !vph) return EINVAL; pf.vph = vph; pf.arg = arg; for (;*p && !err; p++) { if (!fm) { if (*p != '%') continue; pch = ' '; plr = false; pad = 0; fpad = -1; lenmod = LENMOD_NONE; uc = false; if (p > p0) err |= vph(p0, p - p0, arg); fm = true; continue; } fm = false; base = 10; switch (*p) { case '-': plr = true; fm = true; break; case '.': fpad = pad; pad = 0; fm = true; break; case '%': ch = '%'; err |= vph(&ch, 1, arg); break; case 'b': RE_VA_ARG(ap, str, const char *, safe); RE_VA_ARG(ap, len, size_t, safe); err |= write_padded(str, str ? len : 0, pad, ' ', plr, NULL, vph, arg); break; case 'c': RE_VA_ARG(ap, ch, int, safe); err |= write_padded(&ch, 1, pad, ' ', plr, NULL, vph, arg); break; case 'd': case 'i': switch (lenmod) { case LENMOD_INT64: RE_VA_ARG(ap, sn, int64_t, safe); break; case LENMOD_SIZE: RE_VA_ARG(ap, sn, ssize_t, safe); break; default: case LENMOD_LONG_LONG: RE_VA_ARG(ap, sn, signed long long, safe); break; case LENMOD_LONG: RE_VA_ARG(ap, sn, signed long, safe); break; case LENMOD_NONE: RE_VA_ARG(ap, sn, signed, safe); break; } len = local_itoa(num, (sn < 0) ? -sn : sn, base, false); err |= write_padded(num, len, pad, plr ? ' ' : pch, plr, (sn < 0) ? prfx_neg : NULL, vph, arg); break; case 'f': case 'F': RE_VA_ARG(ap, dbl, double, safe); if (fpad == (size_t)-1) { fpad = pad; pad = 0; } if (isinf(dbl)) { err |= write_padded("inf", 3, fpad, ' ', plr, NULL, vph, arg); } else if (isnan(dbl)) { err |= write_padded("nan", 3, fpad, ' ', plr, NULL, vph, arg); } else { len = local_ftoa(num, dbl, pad ? min(pad, DEC_SIZE) : 6); err |= write_padded(num, len, fpad, plr ? ' ' : pch, plr, (dbl<0) ? prfx_neg : NULL, vph, arg); } break; case 'H': RE_VA_ARG(ap, ph, re_printf_h *, safe); RE_VA_ARG(ap, ph_arg, void *, safe); if (ph) err |= ph(&pf, ph_arg); break; case 'l': ++lenmod; fm = true; break; case 'm': RE_VA_ARG(ap, errnum, int, safe); str = str_error(errnum, msg, sizeof(msg)); err |= write_padded(str, str_len(str), pad, ' ', plr, NULL, vph, arg); break; case 'p': RE_VA_ARG(ap, ptr, void *, safe); if (ptr) { len = local_itoa(num, (size_t)ptr, 16, false); err |= write_padded(num, len, pad, plr ? ' ' : pch, plr, prfx_hex, vph, arg); } else { err |= write_padded(str_nil, sizeof(str_nil) - 1, pad, ' ', plr, NULL, vph, arg); } break; case 'r': RE_VA_ARG(ap, pl, const struct pl *, safe); err |= write_padded(pl ? pl->p : NULL, (pl && pl->p) ? pl->l : 0, pad, ' ', plr, NULL, vph, arg); break; case 's': RE_VA_ARG(ap, str, char *, safe); err |= write_padded(str, str_len(str), pad, ' ', plr, NULL, vph, arg); break; case 'X': uc = true; /*@fallthrough@*/ case 'x': base = 16; /*@fallthrough@*/ case 'u': switch (lenmod) { case LENMOD_INT64: RE_VA_ARG(ap, n, uint64_t, safe); break; case LENMOD_SIZE: RE_VA_ARG(ap, n, size_t, safe); break; default: case LENMOD_LONG_LONG: RE_VA_ARG(ap, n, unsigned long long, safe); break; case LENMOD_LONG: RE_VA_ARG(ap, n, unsigned long, safe); break; case LENMOD_NONE: RE_VA_ARG(ap, n, unsigned, safe); break; } len = local_itoa(num, n, base, uc); err |= write_padded(num, len, pad, plr ? ' ' : pch, plr, NULL, vph, arg); break; case 'v': RE_VA_ARG(ap, str, char *, safe); RE_VA_ARG(ap, apl, void *, safe); if (!str || !apl) break; err |= re_vhprintf(str, *apl, vph, arg); break; case 'W': uc = true; /*@fallthrough@*/ case 'w': RE_VA_ARG(ap, bptr, void *, safe); RE_VA_ARG(ap, len, size_t, safe); len = bptr ? len : 0; pch = plr ? ' ' : pch; while (!plr && pad-- > (len * 2)) err |= vph(&pch, 1, arg); for (i=0; i (len * 2)) err |= vph(&pch, 1, arg); break; case 'z': lenmod = LENMOD_SIZE; fm = true; break; case 'L': lenmod = LENMOD_INT64; fm = true; break; case 'j': RE_VA_ARG(ap, sa, struct sa *, safe); if (!sa) break; if (sa_ntop(sa, addr, sizeof(addr))) { err |= write_padded("?", 1, pad, ' ', plr, NULL, vph, arg); break; } err |= write_padded(addr, strlen(addr), pad, ' ', plr, NULL, vph, arg); break; case 'J': RE_VA_ARG(ap, sa, struct sa *, safe); if (!sa) break; if (sa_ntop(sa, addr, sizeof(addr))) { err |= write_padded("?", 1, pad, ' ', plr, NULL, vph, arg); break; } if (AF_INET6 == sa_af(sa)) { ch = '['; err |= vph(&ch, 1, arg); } err |= write_padded(addr, strlen(addr), pad, ' ', plr, NULL, vph, arg); if (AF_INET6 == sa_af(sa)) { ch = ']'; err |= vph(&ch, 1, arg); } ch = ':'; err |= vph(&ch, 1, arg); len = local_itoa(num, sa_port(sa), 10, false); err |= write_padded(num, len, pad, plr ? ' ' : pch, plr, NULL, vph, arg); break; default: if (('0' <= *p) && (*p <= '9')) { if (!pad && ('0' == *p)) { pch = '0'; } else { pad *= 10; pad += *p - '0'; } fm = true; break; } ch = '?'; err |= vph(&ch, 1, arg); break; } if (!fm) p0 = p + 1; } if (!fm && p > p0) err |= vph(p0, p - p0, arg); out: #ifndef RELEASE if (err == ENODATA) { btrace(&bt); re_fprintf(stderr, "Format: \"%b<-- NO ARG\n%H\n", fmt, p - fmt + 1, btrace_println, &bt); re_assert(0 && "RE_VA_ARG: no more arguments"); } else if (err == EOVERFLOW) { btrace(&bt); re_fprintf(stderr, "Format: \"%b<-- SIZE ERROR\n%H\n", fmt, p - fmt + 1, btrace_println, &bt); re_assert(0 && "RE_VA_ARG: arg is not compatible"); } #endif return err; } /** * Print a formatted string * * @param fmt Formatted string * @param ap Variable argument * @param vph Print handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode * * Extensions: * *
 *   %b  (char *, size_t)        Buffer string with pointer and length
 *   %r  (struct pl *)           Pointer-length object
 *   %w  (uint8_t *, size_t)     Binary buffer to hexadecimal format
 *   %j  (struct sa *)           Socket address - address part only
 *   %J  (struct sa *)           Socket address and port - like 1.2.3.4:1234
 *   %H  (re_printf_h *, void *) Print handler with argument
 *   %v  (char *fmt, va_list *)  Variable argument list
 *   %m  (int)                   Describe an error code
 *   %L  (uint64_t/int64_t)      64-bit length modifier for %i, %d, %x and %u
 * 
* * Reserved for the future: * * %k * %y * */ int re_vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg) { return vhprintf(fmt, ap, vph, arg, false); } /** * Print a safe formatted string * * @param fmt Formatted string * @param ap Variable argument * @param vph Print handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode * * Extensions: * *
 *   %b  (char *, size_t)        Buffer string with pointer and length
 *   %r  (struct pl *)           Pointer-length object
 *   %w  (uint8_t *, size_t)     Binary buffer to hexadecimal format
 *   %j  (struct sa *)           Socket address - address part only
 *   %J  (struct sa *)           Socket address and port - like 1.2.3.4:1234
 *   %H  (re_printf_h *, void *) Print handler with argument
 *   %v  (char *fmt, va_list *)  Variable argument list
 *   %m  (int)                   Describe an error code
 * 
* * Reserved for the future: * * %k * %y * */ int re_vhprintf_s(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg) { return vhprintf(fmt, ap, vph, arg, true); } static int print_handler(const char *p, size_t size, void *arg) { struct pl *pl = arg; if (size > pl->l) return ENOMEM; memcpy((void *)pl->p, p, size); pl_advance(pl, size); return 0; } struct dyn_print { char *str; char *p; size_t l; size_t size; }; static int print_handler_dyn(const char *p, size_t size, void *arg) { struct dyn_print *dp = arg; if (size > dp->l - 1) { const size_t new_size = MAX(dp->size + size, dp->size * 2); char *str = mem_realloc(dp->str, new_size); if (!str) return ENOMEM; dp->str = str; dp->l += new_size - dp->size; dp->p = dp->str + new_size - dp->l; dp->size = new_size; } memcpy(dp->p, p, size); dp->p += size; dp->l -= size; return 0; } struct strm_print { FILE *f; size_t n; }; static int print_handler_stream(const char *p, size_t size, void *arg) { struct strm_print *sp = arg; if (1 != fwrite(p, size, 1, sp->f)) return ENOMEM; sp->n += size; return 0; } /** * Print a formatted string to a file stream, using va_list * * @param stream File stream for the output * @param fmt Formatted string * @param ap Variable-arguments list * * @return The number of characters printed, or -1 if error */ int re_vfprintf(FILE *stream, const char *fmt, va_list ap) { struct strm_print sp; if (!stream) return -1; sp.f = stream; sp.n = 0; if (0 != vhprintf(fmt, ap, print_handler_stream, &sp, false)) return -1; return (int)sp.n; } /** * Print a safe formatted string to a file stream, using va_list * * @param stream File stream for the output * @param fmt Formatted string * @param ap Variable-arguments list * * @return The number of characters printed, or -1 if error */ int re_vfprintf_s(FILE *stream, const char *fmt, va_list ap) { struct strm_print sp; if (!stream) return -1; sp.f = stream; sp.n = 0; if (0 != vhprintf(fmt, ap, print_handler_stream, &sp, true)) return -1; return (int)sp.n; } /** * Print a formatted string to stdout, using va_list * * @param fmt Formatted string * @param ap Variable-arguments list * * @return The number of characters printed, or -1 if error */ int re_vprintf(const char *fmt, va_list ap) { return re_vfprintf(stdout, fmt, ap); } /** * Print a safe formatted string to stdout, using va_list * * @param fmt Formatted string * @param ap Variable-arguments list * * @return The number of characters printed, or -1 if error */ int re_vprintf_s(const char *fmt, va_list ap) { return re_vfprintf_s(stdout, fmt, ap); } /** * Print a formatted string to a buffer, using va_list * * @param str Buffer for output string * @param size Size of buffer * @param fmt Formatted string * @param ap Variable-arguments list * * @return The number of characters printed, or -1 if error */ int re_vsnprintf(char *re_restrict str, size_t size, const char *re_restrict fmt, va_list ap) { struct pl pl; int err; if (!str || !size) return -1; pl.p = str; pl.l = size - 1; err = vhprintf(fmt, ap, print_handler, &pl, false); str[size - pl.l - 1] = '\0'; return err ? -1 : (int)(size - pl.l - 1); } /** * Print a safe formatted string to a buffer, using va_list * * @param str Buffer for output string * @param size Size of buffer * @param fmt Formatted string * @param ap Variable-arguments list * * @return The number of characters printed, or -1 if error */ int re_vsnprintf_s(char *re_restrict str, size_t size, const char *re_restrict fmt, va_list ap) { struct pl pl; int err; if (!str || !size) return -1; pl.p = str; pl.l = size - 1; err = vhprintf(fmt, ap, print_handler, &pl, true); str[size - pl.l - 1] = '\0'; return err ? -1 : (int)(size - pl.l - 1); } static int vsdprintf(char **strp, const char *fmt, va_list ap, bool safe) { struct dyn_print dp; int err; if (!strp) return EINVAL; dp.size = 16; dp.str = mem_alloc(dp.size, NULL); if (!dp.str) return ENOMEM; dp.p = dp.str; dp.l = dp.size; err = vhprintf(fmt, ap, print_handler_dyn, &dp, safe); if (err) goto out; *dp.p = '\0'; out: if (err) mem_deref(dp.str); else *strp = dp.str; return err; } /** * Print a formatted string to a dynamically allocated buffer, using va_list * * @param strp Pointer for output string * @param fmt Formatted string * @param ap Variable-arguments list * * @return 0 if success, otherwise errorcode */ int re_vsdprintf(char **strp, const char *fmt, va_list ap) { return vsdprintf(strp, fmt, ap, false); } /** * Print a safe formatted string to a dynamically allocated buffer, using * va_list * * @param strp Pointer for output string * @param fmt Formatted string * @param ap Variable-arguments list * * @return 0 if success, otherwise errorcode */ int re_vsdprintf_s(char **strp, const char *fmt, va_list ap) { return vsdprintf(strp, fmt, ap, true); } /** * Print a formatted string * * @param pf Print backend * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int _re_hprintf(struct re_printf *pf, const char *fmt, ...) { va_list ap; int err; if (!pf) return EINVAL; va_start(ap, fmt); err = re_vhprintf(fmt, ap, pf->vph, pf->arg); va_end(ap); return err; } /** * Print a safe formatted string * * @param pf Print backend * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int _re_hprintf_s(struct re_printf *pf, const char *fmt, ...) { va_list ap; int err; if (!pf) return EINVAL; va_start(ap, fmt); err = re_vhprintf_s(fmt, ap, pf->vph, pf->arg); va_end(ap); return err; } /** * Print a formatted string to a file stream * * @param stream File stream for output * @param fmt Formatted string * * @return The number of characters printed, or -1 if error */ int _re_fprintf(FILE *stream, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = re_vfprintf(stream, fmt, ap); va_end(ap); return n; } /** * Print a safe formatted string to a file stream * * @param stream File stream for output * @param fmt Formatted string * * @return The number of characters printed, or -1 if error */ int _re_fprintf_s(FILE *stream, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = re_vfprintf_s(stream, fmt, ap); va_end(ap); return n; } /** * Print a formatted string to stdout * * @param fmt Formatted string * * @return The number of characters printed, or -1 if error */ int _re_printf(const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = re_vprintf(fmt, ap); va_end(ap); return n; } /** * Print a safe formatted string to stdout * * @param fmt Formatted string * * @return The number of characters printed, or -1 if error */ int _re_printf_s(const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = re_vprintf_s(fmt, ap); va_end(ap); return n; } /** * Print a formatted string to a buffer * * @param str Buffer for output string * @param size Size of buffer * @param fmt Formatted string * * @return The number of characters printed, or -1 if error */ int _re_snprintf(char *re_restrict str, size_t size, const char *re_restrict fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = re_vsnprintf(str, size, fmt, ap); va_end(ap); return n; } /** * Print a safe formatted string to a buffer * * @param str Buffer for output string * @param size Size of buffer * @param fmt Formatted string * * @return The number of characters printed, or -1 if error */ int _re_snprintf_s(char *re_restrict str, size_t size, const char *re_restrict fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = re_vsnprintf_s(str, size, fmt, ap); va_end(ap); return n; } /** * Print a formatted string to a buffer * * @param strp Buffer pointer for output string * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int _re_sdprintf(char **strp, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = re_vsdprintf(strp, fmt, ap); va_end(ap); return err; } /** * Print a safe formatted string to a buffer * * @param strp Buffer pointer for output string * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int _re_sdprintf_s(char **strp, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = re_vsdprintf_s(strp, fmt, ap); va_end(ap); return err; } ================================================ FILE: src/fmt/prm.c ================================================ /** * @file prm.c Generic parameter decoding * * Copyright (C) 2010 Creytiv.com */ #include #include /** * Check if a semicolon separated parameter is present * * @param pl PL string to search * @param pname Parameter name * * @return true if found, false if not found */ bool fmt_param_exists(const struct pl *pl, const char *pname) { struct pl semi, eop; char expr[128]; if (!pl || !pname) return false; (void)re_snprintf(expr, sizeof(expr), "[;]*[ \t\r\n]*%s[ \t\r\n;=]*", pname); if (re_regex(pl->p, pl->l, expr, &semi, NULL, &eop)) return false; if (!eop.l && eop.p < pl->p + pl->l) return false; return semi.l > 0 || pl->p == semi.p; } /** * Fetch parameter from a PL string. The separator can be specified * * @param pl PL string to search * @param pname Parameter name * @param sep Separator * @param val Parameter value, set on return * * @return true if found, false if not found */ bool fmt_param_sep_get(const struct pl *pl, const char *pname, char sep, struct pl *val) { struct pl semi; char expr[128]; if (!pl || !pname) return false; (void)re_snprintf(expr, sizeof(expr), "[%c]*[ \t\r\n]*%s[ \t\r\n]*=[ \t\r\n]*[~ \t\r\n%c]+", sep, pname, sep); if (re_regex(pl->p, pl->l, expr, &semi, NULL, NULL, NULL, val)) return false; return semi.l > 0 || pl->p == semi.p; } /** * Fetch a semicolon separated parameter from a PL string * * @param pl PL string to search * @param pname Parameter name * @param val Parameter value, set on return * * @return true if found, false if not found */ bool fmt_param_get(const struct pl *pl, const char *pname, struct pl *val) { return fmt_param_sep_get(pl, pname, ';', val); } /** * Apply a function handler for each semicolon separated parameter * * @param pl PL string to search * @param ph Parameter handler * @param arg Handler argument */ void fmt_param_apply(const struct pl *pl, fmt_param_h *ph, void *arg) { struct pl prmv, prm, semi, name, val; if (!pl || !ph) return; prmv = *pl; while (!re_regex(prmv.p, prmv.l, "[ \t\r\n]*[~;]+[;]*", NULL, &prm, &semi)) { pl_advance(&prmv, semi.p + semi.l - prmv.p); if (re_regex(prm.p, prm.l, "[^ \t\r\n=]+[ \t\r\n]*[=]*[ \t\r\n]*[~ \t\r\n]*", &name, NULL, NULL, NULL, &val)) break; ph(&name, &val, arg); } } ================================================ FILE: src/fmt/regex.c ================================================ /** * @file regex.c Implements basic regular expressions * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** Defines a character range */ struct chr { uint8_t min; /**< Minimum value */ uint8_t max; /**< Maximum value */ }; static bool expr_match(const struct chr *chrv, uint32_t n, uint8_t c, bool neg) { uint32_t i; for (i=0; i chrv[i].max) continue; break; } return neg ? (i == n) : (i != n); } /** * Parse a string using basic regular expressions. Any number of matching * expressions can be given, and each match will be stored in a "struct pl" * pointer-length type. * * @param ptr String to parse * @param len Length of string * @param expr Regular expressions string * * @return 0 if success, otherwise errorcode * * Example: * * We parse the buffer for any numerical values, to get a match we must have * 1 or more occurrences of the digits 0-9. The result is stored in 'num', * which is of pointer-length type and will point to the first location in * the buffer that contains "42". * *
 const char buf[] = "foo 42 bar";
 struct pl num;
 int err = re_regex(buf, strlen(buf), "[0-9]+", &num);

 here num contains a pointer to '42'
 * 
*/ int re_regex(const char *ptr, size_t len, const char *expr, ...) { struct chr chrv[64]; const char *p, *ep; bool fm, range = false, ec = false, neg = false, qesc = false; uint32_t n = 0; va_list ap; bool eesc; size_t l; if (!ptr || !expr) return EINVAL; again: eesc = false; fm = false; l = len--; p = ptr++; ep = expr; va_start(ap, expr); if (!l) goto out; for (; *ep; ep++) { if ('\\' == *ep && !eesc) { eesc = true; continue; } if (!fm) { /* Start of character class */ if ('[' == *ep && !eesc) { n = 0; fm = true; ec = false; neg = false; range = false; qesc = false; continue; } if (!l) break; if (tolower(*ep) != tolower(*p)) { va_end(ap); goto again; } eesc = false; ++p; --l; continue; } /* End of character class */ else if (ec) { uint32_t nm, nmin, nmax; struct pl lpl, *pl = va_arg(ap, struct pl *); bool quote = false, esc = false; /* Match 0 or more times */ if ('*' == *ep) { nmin = 0; nmax = -1; } /* Match 1 or more times */ else if ('+' == *ep) { nmin = 1; nmax = -1; } /* Match exactly n times */ else if ('1' <= *ep && *ep <= '9') { nmin = *ep - '0'; nmax = *ep - '0'; } else break; fm = false; lpl.p = p; lpl.l = 0; for (nm = 0; l && nm < nmax; nm++, p++, l--, lpl.l++) { if (qesc) { if (esc) { esc = false; continue; } switch (*p) { case '\\': esc = true; continue; case '"': quote = !quote; continue; } if (quote) continue; } if (!expr_match(chrv, n, tolower(*p), neg)) break; } /* Strip quotes */ if (qesc && lpl.l > 1 && lpl.p[0] == '"' && lpl.p[lpl.l - 1] == '"') { lpl.p += 1; lpl.l -= 2; nm -= 2; } if ((nm < nmin) || (nm > nmax)) { va_end(ap); goto again; } if (pl) *pl = lpl; eesc = false; continue; } if (eesc) { eesc = false; goto chr; } switch (*ep) { /* End of character class */ case ']': ec = true; continue; /* Negate with quote escape */ case '~': if (n) break; qesc = true; neg = true; continue; /* Negate */ case '^': if (n) break; neg = true; continue; /* Range */ case '-': if (!n || range) break; range = true; --n; continue; } chr: if (n >= RE_ARRAY_SIZE(chrv)) break; chrv[n].max = tolower(*ep); if (range) range = false; else chrv[n].min = tolower(*ep); ++n; } out: va_end(ap); if (fm) return EINVAL; return *ep ? ENOENT : 0; } ================================================ FILE: src/fmt/str.c ================================================ /** * @file fmt/str.c String format functions * * Copyright (C) 2010 Creytiv.com */ #undef __STRICT_ANSI__ /* for mingw32 */ #include #include #ifdef HAVE_STRINGS_H #include #endif #include #include #include enum { X64_STRSIZE = 17, }; /** * Convert a ascii hex string to binary format * * @param hex Destination binary buffer * @param len Length of binary buffer * @param str Source ascii string * * @return 0 if success, otherwise errorcode */ int str_hex(uint8_t *hex, size_t len, const char *str) { size_t i; if (!hex || !str || (strlen(str) != (2 * len))) return EINVAL; for (i=0; i equal */ if (s1 == s2) return 0; if (!s1 || !s2) return 1; #ifdef WIN32 return _stricmp(s1, s2); #else return strcasecmp(s1, s2); #endif } /** * Calculate the length of a string, safe version. * * @param s String * * @return Length of the string */ size_t str_len(const char *s) { return s ? strlen(s) : 0; } /** * Convert various possible boolean strings to a bool * * @param val Pointer to bool for returned value * @param str String to be converted * * @return int 0 if success, otherwise errorcode */ int str_bool(bool *val, const char *str) { if (!val || !str_isset(str)) return EINVAL; struct pl pl = pl_null; pl_set_str(&pl, str); return pl_bool(val, &pl); } /** * Converts unsigned integer to string * * @param val Number to be converted * @param buf Buffer[ITOA_BUFSZ] that holds the result of the conversion * @param base Base to use for conversion * * @return Pointer to buffer */ char *str_itoa(uint32_t val, char *buf, int base) { int i = ITOA_BUFSZ - 2; buf[ITOA_BUFSZ - 1] = '\0'; if (!val) { buf[i] = '0'; return &buf[i]; } for (; val && i; --i, val /= base) buf[i] = "0123456789abcdef"[val % base]; return &buf[i + 1]; } /** * Converts multibyte characters to wide characters * * @param str String for conversion * * @return Pointer to new allocated wide string */ wchar_t *str_wchar(const char *str) { wchar_t *w; size_t n; if (!str) return NULL; n = strlen(str) + 1; w = mem_zalloc(n * sizeof(wchar_t), NULL); if (!w) return NULL; if (mbstowcs(w, str, n) == (size_t)-1) { mem_deref(w); return NULL; } return w; } ================================================ FILE: src/fmt/str_error.c ================================================ /** * @file str_error.c System error messages * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Look up an error message string corresponding to an error number. * * @param errnum Error Code * @param buf Buffer for storing error message * @param sz Buffer size * * @return Error message string */ const char *str_error(int errnum, char *buf, size_t sz) { const char *s; char msg[128] = {0}; if (!buf || !sz) return NULL; #ifdef HAVE_STRERROR_R #ifdef __GLIBC__ s = strerror_r(errnum, msg, sizeof(msg)); #else (void)strerror_r(errnum, msg, sizeof(msg)); s = msg; #endif #elif defined (WIN32) (void)strerror_s(msg, sizeof(msg), errnum); s = msg; #else /* fallback */ (void)errnum; s = "unknown error"; #endif re_snprintf(buf, sz, "%s [%d]", s, errnum); return buf; } ================================================ FILE: src/fmt/text2pcap.c ================================================ #include #include #include #include #include int re_text2pcap(struct re_printf *pf, struct re_text2pcap *pcap) { if (!pcap) return EINVAL; uint8_t *buf = mbuf_buf(pcap->mb); if (!buf) return EINVAL; re_hprintf(pf, "%s %H 000000", pcap->in ? "I" : "O", fmt_timestamp_us, NULL); size_t sz = mbuf_get_left(pcap->mb); for (size_t i = 0; i < sz; i++) { re_hprintf(pf, " %02x", buf[i]); } re_hprintf(pf, " %s", pcap->id); return 0; } void re_text2pcap_trace(const char *name, const char *id, bool in, const struct mbuf *mb) { struct re_text2pcap pcap = {.in = in, .mb = mb, .id = id}; size_t pcap_buf_sz = (mbuf_get_left(mb) * 3) + 64; char *pcap_buf = mem_alloc(pcap_buf_sz, NULL); if (!pcap_buf) return; (void)re_snprintf(pcap_buf, pcap_buf_sz, "%H", re_text2pcap, &pcap); re_trace_event("pcap", name, 'I', NULL, RE_TRACE_ARG_STRING_COPY, "pcap", pcap_buf); mem_deref(pcap_buf); } ================================================ FILE: src/fmt/time.c ================================================ /** * @file time.c Time formatting * * Copyright (C) 2010 Creytiv.com */ #ifdef __MINGW32__ #define _POSIX_C_SOURCE 200809L #endif #include #ifdef WIN32 #include #endif #include #include static const char *dayv[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char *monv[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; /** * Print Greenwich Mean Time * * @param pf Print function for output * @param ts Time in seconds since the Epoch or NULL for current time * * @return 0 if success, otherwise errorcode */ int fmt_gmtime(struct re_printf *pf, void *ts) { struct tm tm; time_t t; if (!ts) { t = time(NULL); ts = &t; } #ifdef WIN32 if (gmtime_s(&tm, ts)) return EINVAL; #else if (!gmtime_r(ts, &tm)) return EINVAL; #endif return re_hprintf(pf, "%s, %02u %s %u %02u:%02u:%02u GMT", dayv[min((unsigned)tm.tm_wday, RE_ARRAY_SIZE(dayv)-1)], tm.tm_mday, monv[min((unsigned)tm.tm_mon, RE_ARRAY_SIZE(monv)-1)], tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); } /** * Print the human readable time * * @param pf Print function for output * @param seconds Pointer to number of seconds * * @return 0 if success, otherwise errorcode */ int fmt_human_time(struct re_printf *pf, const uint32_t *seconds) { /* max 136 years */ const uint32_t sec = *seconds%60; const uint32_t min = *seconds/60%60; const uint32_t hrs = *seconds/60/60%24; const uint32_t days = *seconds/60/60/24; int err = 0; if (days) err |= re_hprintf(pf, "%u day%s ", days, 1==days?"":"s"); if (hrs) { err |= re_hprintf(pf, "%u hour%s ", hrs, 1==hrs?"":"s"); } if (min) { err |= re_hprintf(pf, "%u min%s ", min, 1==min?"":"s"); } if (sec) { err |= re_hprintf(pf, "%u sec%s", sec, 1==sec?"":"s"); } return err; } /** * Print local time stamp including milli seconds relative to user's timezone * * @param pf Print function for output * @param arg Not used * * @return 0 if success, otherwise errorcode */ int fmt_timestamp(struct re_printf *pf, void *arg) { int h, m, s; uint64_t ms; #ifdef WIN32 SYSTEMTIME st; GetSystemTime(&st); h = st.wHour; m = st.wMinute; s = st.wSecond; ms = st.wMilliseconds; #else struct timespec tspec; struct tm tm; (void)clock_gettime(CLOCK_REALTIME, &tspec); if (!localtime_r(&tspec.tv_sec, &tm)) return EINVAL; h = tm.tm_hour; m = tm.tm_min; s = tm.tm_sec; ms = tspec.tv_nsec / 1000000; #endif (void)arg; return re_hprintf(pf, "%02u:%02u:%02u.%03llu", h, m, s, ms); } /** * Print local time stamp including microseconds relative to user's timezone * * @param pf Print function for output * @param arg Not used * * @return 0 if success, otherwise errorcode */ int fmt_timestamp_us(struct re_printf *pf, void *arg) { int h, m, s; uint64_t us; struct timespec tspec; struct tm tm = {0}; #if defined(WIN32) && !defined(__MINGW32__) timespec_get(&tspec, TIME_UTC); int err = localtime_s(&tm, &tspec.tv_sec); if (err) return err; #else (void)clock_gettime(CLOCK_REALTIME, &tspec); if (!localtime_r(&tspec.tv_sec, &tm)) return EINVAL; #endif h = tm.tm_hour; m = tm.tm_min; s = tm.tm_sec; us = tspec.tv_nsec / 1000; (void)arg; return re_hprintf(pf, "%02u:%02u:%02u.%06llu", h, m, s, us); } ================================================ FILE: src/fmt/unicode.c ================================================ /** * @file unicode.c Unicode character coding * * Copyright (C) 2010 Creytiv.com */ #include #include #include static const char *hex_chars = "0123456789ABCDEF"; /** * UTF-8 encode * * @param pf Print function for output * @param str Input string to encode * * @return 0 if success, otherwise errorcode */ int utf8_encode(struct re_printf *pf, const char *str) { char ubuf[6] = "\\u00", ebuf[2] = "\\"; if (!pf) return EINVAL; if (!str) return 0; while (*str) { const uint8_t c = *str++; /* NOTE: must be unsigned 8-bit */ bool unicode = false; char ec = 0; int err; switch (c) { case '"': ec = '"'; break; case '\\': ec = '\\'; break; case '/': ec = '/'; break; case '\b': ec = 'b'; break; case '\f': ec = 'f'; break; case '\n': ec = 'n'; break; case '\r': ec = 'r'; break; case '\t': ec = 't'; break; default: if (c < ' ') { unicode = true; } /* chars in range 0x80-0xff are not escaped */ break; } if (unicode) { ubuf[4] = hex_chars[(c>>4) & 0xf]; ubuf[5] = hex_chars[c & 0xf]; err = pf->vph(ubuf, sizeof(ubuf), pf->arg); } else if (ec) { ebuf[1] = ec; err = pf->vph(ebuf, sizeof(ebuf), pf->arg); } else { err = pf->vph((char *)&c, 1, pf->arg); } if (err) return err; } return 0; } /** * UTF-8 decode * * @param pf Print function for output * @param pl Input buffer to decode * * @return 0 if success, otherwise errorcode */ int utf8_decode(struct re_printf *pf, const struct pl *pl) { int uhi = -1; size_t i; if (!pf) return EINVAL; if (!pl) return 0; for (i=0; il; i++) { char ch = pl->p[i]; int err; if (ch == '\\') { unsigned u = 0; char ubuf[4]; size_t ulen; ++i; if (i >= pl->l) return EBADMSG; ch = pl->p[i]; switch (ch) { case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'u': if (i+4 >= pl->l) return EBADMSG; if (!isxdigit(pl->p[i+1]) || !isxdigit(pl->p[i+2]) || !isxdigit(pl->p[i+3]) || !isxdigit(pl->p[i+4])) return EBADMSG; u |= ((uint16_t)ch_hex(pl->p[++i])) << 12; u |= ((uint16_t)ch_hex(pl->p[++i])) << 8; u |= ((uint16_t)ch_hex(pl->p[++i])) << 4; u |= ((uint16_t)ch_hex(pl->p[++i])) << 0; /* UTF-16 surrogate pair */ if (u >= 0xd800 && u <= 0xdbff) { uhi = (u - 0xd800) * 0x400; continue; } else if (u >= 0xdc00 && u <= 0xdfff) { if (uhi < 0) continue; u = uhi + u - 0xdc00 + 0x10000; } uhi = -1; ulen = utf8_byteseq(ubuf, u); err = pf->vph(ubuf, ulen, pf->arg); if (err) return err; continue; } } uhi = -1; err = pf->vph(&ch, 1, pf->arg); if (err) return err; } return 0; } /** * Encode Unicode code point into binary UTF-8 * * @param u Binary UTF-8 buffer * @param cp Unicode code point * * @return length of UTF-8 byte sequence */ size_t utf8_byteseq(char u[4], unsigned cp) { if (!u) return 0; if (cp <= 0x7f) { u[0] = cp; return 1; } else if (cp <= 0x7ff) { u[0] = 0xc0 | (cp>>6 & 0x1f); u[1] = 0x80 | (cp & 0x3f); return 2; } else if (cp <= 0xffff) { u[0] = 0xe0 | (cp>>12 & 0x0f); u[1] = 0x80 | (cp>>6 & 0x3f); u[2] = 0x80 | (cp & 0x3f); return 3; } else if (cp <= 0x10ffff) { u[0] = 0xf0 | (cp>>18 & 0x07); u[1] = 0x80 | (cp>>12 & 0x3f); u[2] = 0x80 | (cp>>6 & 0x3f); u[3] = 0x80 | (cp & 0x3f); return 4; } else { /* The replacement character (U+FFFD) */ u[0] = (char)0xef; u[1] = (char)0xbf; u[2] = (char)0xbd; return 3; } } ================================================ FILE: src/h264/getbit.c ================================================ /** * @file h264/getbit.c Generic bit parser * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "h264.h" #define DEBUG_MODULE "getbit" #define DEBUG_LEVEL 5 #include void getbit_init(struct getbit *gb, const uint8_t *buf, size_t size) { if (!gb) return; gb->buf = buf; gb->pos = 0; gb->end = size; } size_t getbit_get_left(const struct getbit *gb) { if (!gb) return 0; if (gb->end > gb->pos) return gb->end - gb->pos; else return 0; } unsigned get_bit(struct getbit *gb) { const uint8_t *p; register unsigned tmp; if (!gb) return 0; if (gb->pos >= gb->end) { re_fprintf(stderr, "get_bit: read past end" " (%zu >= %zu)\n", gb->pos, gb->end); return 0; } p = gb->buf; tmp = ((*(p + (gb->pos >> 0x3))) >> (0x7 - (gb->pos & 0x7))) & 0x1; ++gb->pos; return tmp; } int get_ue_golomb(struct getbit *gb, unsigned *valp) { unsigned zeros = 0; unsigned info; int i; if (!gb) return EINVAL; while (1) { if (getbit_get_left(gb) < 1) return EBADMSG; if (0 == get_bit(gb)) ++zeros; else break; } info = 1 << zeros; for (i = zeros - 1; i >= 0; i--) { if (getbit_get_left(gb) < 1) return EBADMSG; info |= get_bit(gb) << i; } if (valp) *valp = info - 1; return 0; } /* x = 0 for ( i = 0; i < n; i++ ) { x = 2 * x + read_bit() } TotalConsumedBits += n return x */ unsigned get_bits(struct getbit *gb, unsigned n) { unsigned x = 0; if (getbit_get_left(gb) < n) { DEBUG_WARNING("get_bits: read past end" " (n=%zu, left=%zu)\n", n, getbit_get_left(gb)); return 0; } for (unsigned i=0; i> 1; ++w; } unsigned m = (1u << w) - n; unsigned v = dd_f(w - 1); if (v < m) return v; unsigned extra_bit = dd_f(1); return (v << 1) - m + extra_bit; } ================================================ FILE: src/h264/h264.h ================================================ /** * @file h264/h264.h Internal interface * * Copyright (C) 2010 Creytiv.com */ ================================================ FILE: src/h264/nal.c ================================================ /** * @file h264/nal.c H.264 header parser * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include enum { H264_HEADER_LENGTH = 1, H264_STAP_MIN_LENGTH = sizeof(uint16_t) }; static int nal_header_encode_val(struct mbuf *mb, uint8_t nri, enum h264_nalu type) { uint8_t v = nri<<5 | type; return mbuf_write_u8(mb, v); } void h264_nal_header_decode_buf(struct h264_nal_header *hdr, const uint8_t *buf) { if (!hdr || !buf) return; uint8_t v = buf[0]; hdr->f = v>>7 & 0x1; hdr->nri = v>>5 & 0x3; hdr->type = v & 0x1f; } /** * Encode H.264 NAL header * * @param mb Buffer to encode into * @param hdr H.264 NAL header to encode * * @return 0 if success, otherwise errorcode */ int h264_nal_header_encode(struct mbuf *mb, const struct h264_nal_header *hdr) { uint8_t v; if (!mb || !hdr) return EINVAL; v = hdr->f<<7 | hdr->nri<<5 | hdr->type; return mbuf_write_u8(mb, v); } /** * Decode H.264 NAL header * * @param hdr H.264 NAL header to decode into * @param mb Buffer to decode * * @return 0 if success, otherwise errorcode */ int h264_nal_header_decode(struct h264_nal_header *hdr, struct mbuf *mb) { uint8_t v; if (!hdr || !mb) return EINVAL; if (mbuf_get_left(mb) < 1) return EBADMSG; v = mbuf_read_u8(mb); hdr->f = v>>7 & 0x1; hdr->nri = v>>5 & 0x3; hdr->type = v & 0x1f; return 0; } /** * Get the name of an H.264 NAL unit * * @param nal_type NAL unit type * * @return A string containing the NAL unit name */ const char *h264_nal_unit_name(enum h264_nalu nal_type) { switch (nal_type) { case H264_NALU_SLICE: return "SLICE"; case H264_NALU_DPA: return "DPA"; case H264_NALU_DPB: return "DPB"; case H264_NALU_DPC: return "DPC"; case H264_NALU_IDR_SLICE: return "IDR_SLICE"; case H264_NALU_SEI: return "SEI"; case H264_NALU_SPS: return "SPS"; case H264_NALU_PPS: return "PPS"; case H264_NALU_AUD: return "AUD"; case H264_NALU_END_SEQUENCE: return "END_SEQUENCE"; case H264_NALU_END_STREAM: return "END_STREAM"; case H264_NALU_FILLER_DATA: return "FILLER_DATA"; case H264_NALU_SPS_EXT: return "SPS_EXT"; case H264_NALU_AUX_SLICE: return "AUX_SLICE"; case H264_NALU_STAP_A: return "STAP-A"; case H264_NALU_STAP_B: return "STAP-B"; case H264_NALU_MTAP16: return "MTAP16"; case H264_NALU_MTAP24: return "MTAP24"; case H264_NALU_FU_A: return "FU-A"; case H264_NALU_FU_B: return "FU-B"; } return "???"; } int h264_fu_hdr_encode(const struct h264_fu *fu, struct mbuf *mb) { uint8_t v = fu->s<<7 | fu->s<<6 | fu->r<<5 | fu->type; return mbuf_write_u8(mb, v); } int h264_fu_hdr_decode(struct h264_fu *fu, struct mbuf *mb) { uint8_t v; if (mbuf_get_left(mb) < 1) return ENOENT; v = mbuf_read_u8(mb); fu->s = v>>7 & 0x1; fu->e = v>>6 & 0x1; fu->r = v>>5 & 0x1; fu->type = v>>0 & 0x1f; return 0; } /* * Find the NAL start sequence in a H.264 byte stream * * @note: copied from ffmpeg source */ static const uint8_t *h264_find_startcode_int(const uint8_t *p, const uint8_t *end) { const uint8_t *a = p + 4 - ((size_t)p & 3); for (end -= 3; p < a && p < end; p++ ) { if (p[0] == 0 && p[1] == 0 && p[2] == 1) return p; } for (end -= 3; p < end; p += 4) { uint32_t x = *(const uint32_t*)(void *)p; if ( (x - 0x01010101) & (~x) & 0x80808080 ) { if (p[1] == 0 ) { if ( p[0] == 0 && p[2] == 1 ) return p; if ( p[2] == 0 && p[3] == 1 ) return p+1; } if ( p[3] == 0 ) { if ( p[2] == 0 && p[4] == 1 ) return p+2; if ( p[4] == 0 && p[5] == 1 ) return p+3; } } } for (end += 3; p < end; p++) { if (p[0] == 0 && p[1] == 0 && p[2] == 1) return p; } return end + 3; } const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end) { const uint8_t *out = h264_find_startcode_int(p, end); /* check for 4-byte startcode */ if (p sz) { err |= rtp_send_data(fu_hdr, 2, buf, sz, false, rtp_ts, pkth, arg); buf += sz; size -= sz; fu_hdr[1] &= ~(1 << 7); } if (last) fu_hdr[1] |= 1<<6; /* end bit */ err |= rtp_send_data(fu_hdr, 2, buf, size, marker && last, rtp_ts, pkth, arg); } return err; } /** * Packetize an H.264 bitstream with one or more NAL units * with Annex-B startcode (3-byte or 4-byte startcode) * * @param rtp_ts RTP timestamp * @param buf Input buffer * @param len Buffer length * @param pktsize Maximum RTP packet size * @param pkth Packet handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int h264_packetize(uint64_t rtp_ts, const uint8_t *buf, size_t len, size_t pktsize, h264_packet_h *pkth, void *arg) { const uint8_t *start = buf; const uint8_t *end = buf + len; const uint8_t *r; int err = 0; r = h264_find_startcode(start, end); while (r < end) { const uint8_t *r1; /* skip zeros */ while (!*(r++)) ; r1 = h264_find_startcode(r, end); err |= h264_nal_send(true, true, (r1 >= end), r[0], rtp_ts, r+1, r1-r-1, pktsize, pkth, arg); r = r1; } return err; } bool h264_is_keyframe(int type) { return type == H264_NALU_IDR_SLICE; } /** * Encode STAP-A header and payload * * @param mb Target mbuffer for STAP-A NAL unit * @param frame Input frame in Annex-B format * @param frame_sz Number of bytes in input frame * * @return 0 if success, otherwise errorcode * * NOTE: The value of NRI MUST be the maximum of all the * NAL units carried in the aggregation packet. * * NOTE: The input must be in Annex-B format (3-4 byte Startcode) */ int h264_stap_encode(struct mbuf *mb, const uint8_t *frame, size_t frame_sz) { uint8_t nri_max = 0; if (!mb || !frame || !frame_sz) return EINVAL; size_t start = mb->pos; const uint8_t *end = frame + frame_sz; int err = nal_header_encode_val(mb, 0, H264_NALU_STAP_A); if (err) return err; const uint8_t *r = h264_find_startcode(frame, end); while (r < end) { struct h264_nal_header hdr; while (!*(r++)) ; const uint8_t *r1 = h264_find_startcode(r, end); size_t len = r1 - r; if (len > UINT16_MAX) return ERANGE; err = mbuf_write_u16(mb, htons((uint16_t)len)); err |= mbuf_write_mem(mb, r, len); if (err) return err; h264_nal_header_decode_buf(&hdr, r); nri_max = max(hdr.nri, nri_max); r = r1; } /* update NAL header */ mb->buf[start] |= nri_max<<5; return 0; } static int stap_decode_annexb_int(struct mbuf *mb_frame, struct mbuf *mb_pkt, bool long_startcode) { if (!mb_frame || !mb_pkt) return EINVAL; while (mbuf_get_left(mb_pkt) >= H264_STAP_MIN_LENGTH) { uint16_t len = ntohs(mbuf_read_u16(mb_pkt)); if (len < H264_HEADER_LENGTH || len > mbuf_get_left(mb_pkt)) return EBADMSG; #if 0 struct h264_nal_header hdr; h264_nal_header_decode_buf(&hdr, mbuf_buf(mb_pkt)); re_printf("STAP-A decode: len=%u nri=%u type=%u(%s)\n", len, hdr.nri, hdr.type, h264_nal_unit_name(hdr.type)); #endif static const uint8_t sc3[] = {0, 0, 1}; static const uint8_t sc4[] = {0, 0, 0, 1}; int err; if (long_startcode) err = mbuf_write_mem(mb_frame, sc4, sizeof(sc4)); else err = mbuf_write_mem(mb_frame, sc3, sizeof(sc3)); err |= mbuf_write_mem(mb_frame, mbuf_buf(mb_pkt), len); if (err) return err; mbuf_advance(mb_pkt, len); } return 0; } /** * Decode STAP-A payload and convert to Annex-B NAL units * * @param mb_frame Target mbuffer for frame with multiple NAL units * @param mb_pkt Input packet with STAP-A payload * * @return 0 if success, otherwise errorcode * * NOTE: The NAL header must be decoded outside */ int h264_stap_decode_annexb(struct mbuf *mb_frame, struct mbuf *mb_pkt) { return stap_decode_annexb_int(mb_frame, mb_pkt, false); } /** * Decode STAP-A payload and convert to Annex-B NAL units * with long startcode (0x00 0x00 0x00 0x01). * * @param mb_frame Target mbuffer for frame with multiple NAL units * @param mb_pkt Input packet with STAP-A payload * * @return 0 if success, otherwise errorcode * * NOTE: The NAL header must be decoded outside */ int h264_stap_decode_annexb_long(struct mbuf *mb_frame, struct mbuf *mb_pkt) { return stap_decode_annexb_int(mb_frame, mb_pkt, true); } ================================================ FILE: src/h264/sps.c ================================================ /** * @file h264/sps.c H.264 SPS parser * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include "h264.h" enum { MAX_SPS_COUNT = 32, MAX_LOG2_MAX_FRAME_NUM = 16, MACROBLOCK_SIZE = 16, }; #define MAX_MACROBLOCKS 1048576u static int scaling_list(struct getbit *gb, unsigned *scalingl, size_t sizeofscalinglist, bool *usedefaultscalingmatrix) { unsigned lastscale = 8; unsigned nextscale = 8; size_t j; int err; for (j = 0; j < sizeofscalinglist; j++) { if (nextscale != 0) { unsigned delta_scale; err = get_ue_golomb(gb, &delta_scale); if (err) return err; nextscale = (lastscale + delta_scale + 256) % 256; *usedefaultscalingmatrix = (j==0 && nextscale==0); } scalingl[j] = (nextscale==0) ? lastscale : nextscale; lastscale = scalingl[j]; } return 0; } static int decode_scaling_matrix(struct getbit *gb, unsigned chroma_format_idc) { unsigned scalinglist4x4[16]; unsigned scalinglist8x8[64]; bool usedefaultscalingmatrix[12]; unsigned i; int err; for (i = 0; i < ((chroma_format_idc != 3) ? 8u : 12u); i++) { unsigned seq_scaling_list_present_flag; if (getbit_get_left(gb) < 1) return EBADMSG; seq_scaling_list_present_flag = get_bit(gb); if (seq_scaling_list_present_flag) { if (i < 6) { err = scaling_list(gb, scalinglist4x4, 16, &usedefaultscalingmatrix[i]); } else { err = scaling_list(gb, scalinglist8x8, 64, &usedefaultscalingmatrix[i]); } if (err) return err; } } return 0; } /** * Decode a Sequence Parameter Set (SPS) bitstream * * @param sps Decoded H.264 SPS * @param p SPS bitstream to decode, excluding NAL header * @param len Number of bytes * * @return 0 if success, otherwise errorcode */ int h264_sps_decode(struct h264_sps *sps, const uint8_t *p, size_t len) { struct getbit gb; uint8_t profile_idc; unsigned seq_parameter_set_id; unsigned frame_mbs_only_flag; unsigned chroma_format_idc = 1; bool frame_cropping_flag; unsigned mb_w_m1; unsigned mb_h_m1; int err; if (!sps || !p || len < 3) return EINVAL; memset(sps, 0, sizeof(*sps)); profile_idc = p[0]; sps->level_idc = p[2]; getbit_init(&gb, p+3, (len-3)*8); err = get_ue_golomb(&gb, &seq_parameter_set_id); if (err) return err; if (seq_parameter_set_id >= MAX_SPS_COUNT) { re_fprintf(stderr, "h264: sps: sps_id %u out of range\n", seq_parameter_set_id); return EBADMSG; } if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || profile_idc == 86 || profile_idc == 118 || profile_idc == 128 || profile_idc == 138 || profile_idc == 144) { unsigned seq_scaling_matrix_present_flag; err = get_ue_golomb(&gb, &chroma_format_idc); if (err) return err; if (chroma_format_idc > 3U) { return EBADMSG; } else if (chroma_format_idc == 3) { if (getbit_get_left(&gb) < 1) return EBADMSG; /* separate_colour_plane_flag */ (void)get_bit(&gb); } /* bit_depth_luma/chroma */ err = get_ue_golomb(&gb, NULL); err |= get_ue_golomb(&gb, NULL); if (err) return err; if (getbit_get_left(&gb) < 2) return EBADMSG; /* qpprime_y_zero_transform_bypass_flag */ get_bit(&gb); seq_scaling_matrix_present_flag = get_bit(&gb); if (seq_scaling_matrix_present_flag) { err = decode_scaling_matrix(&gb, chroma_format_idc); if (err) return err; } } err = get_ue_golomb(&gb, &sps->log2_max_frame_num); if (err) return err; sps->log2_max_frame_num += 4; if (sps->log2_max_frame_num > MAX_LOG2_MAX_FRAME_NUM) { re_fprintf(stderr, "h264: sps: log2_max_frame_num" " out of range: %u\n", sps->log2_max_frame_num); return EBADMSG; } err = get_ue_golomb(&gb, &sps->pic_order_cnt_type); if (err) return err; if (sps->pic_order_cnt_type == 0) { /* log2_max_pic_order_cnt_lsb */ err = get_ue_golomb(&gb, NULL); if (err) return err; } else if (sps->pic_order_cnt_type == 2) { } else { re_fprintf(stderr, "h264: sps: WARNING:" " unknown pic_order_cnt_type (%u)\n", sps->pic_order_cnt_type); return ENOTSUP; } err = get_ue_golomb(&gb, &sps->max_num_ref_frames); if (err) return err; if (getbit_get_left(&gb) < 1) return EBADMSG; /* gaps_in_frame_num_value_allowed_flag */ (void)get_bit(&gb); err = get_ue_golomb(&gb, &mb_w_m1); err |= get_ue_golomb(&gb, &mb_h_m1); if (err) return err; if (getbit_get_left(&gb) < 1) return EBADMSG; frame_mbs_only_flag = get_bit(&gb); sps->pic_width_in_mbs = mb_w_m1 + 1; sps->pic_height_in_map_units = mb_h_m1 + 1; sps->pic_height_in_map_units *= 2 - frame_mbs_only_flag; if (sps->pic_width_in_mbs >= MAX_MACROBLOCKS || sps->pic_height_in_map_units >= MAX_MACROBLOCKS) { re_fprintf(stderr, "h264: sps: width/height overflow\n"); return EBADMSG; } if (!frame_mbs_only_flag) { if (getbit_get_left(&gb) < 1) return EBADMSG; /* mb_adaptive_frame_field_flag */ (void)get_bit(&gb); } if (getbit_get_left(&gb) < 1) return EBADMSG; /* direct_8x8_inference_flag */ (void)get_bit(&gb); if (getbit_get_left(&gb) < 1) return EBADMSG; frame_cropping_flag = get_bit(&gb); if (frame_cropping_flag) { unsigned crop_left; unsigned crop_right; unsigned crop_top; unsigned crop_bottom; unsigned vsub = (chroma_format_idc == 1) ? 1 : 0; unsigned hsub = (chroma_format_idc == 1 || chroma_format_idc == 2) ? 1 : 0; unsigned sx = 1u << hsub; unsigned sy = (2u - frame_mbs_only_flag) << vsub; unsigned w = MACROBLOCK_SIZE * sps->pic_width_in_mbs; unsigned h = MACROBLOCK_SIZE * sps->pic_height_in_map_units; err = get_ue_golomb(&gb, &crop_left); err |= get_ue_golomb(&gb, &crop_right); err |= get_ue_golomb(&gb, &crop_top); err |= get_ue_golomb(&gb, &crop_bottom); if (err) return err; if ((crop_left + crop_right ) * sx >= w || (crop_top + crop_bottom) * sy >= h) { re_fprintf(stderr, "h264: sps: crop values invalid" " %u %u %u %u / %u %u\n", crop_left, crop_right, crop_top, crop_bottom, w, h); return EBADMSG; } sps->frame_crop_left_offset = sx * crop_left; sps->frame_crop_right_offset = sx * crop_right; sps->frame_crop_top_offset = sy * crop_top; sps->frame_crop_bottom_offset = sy * crop_bottom; } /* success */ sps->profile_idc = profile_idc; sps->seq_parameter_set_id = (uint8_t)seq_parameter_set_id; sps->chroma_format_idc = chroma_format_idc; return 0; } void h264_sps_resolution(const struct h264_sps *sps, unsigned *width, unsigned *height) { if (!sps || !width || !height) return; *width = MACROBLOCK_SIZE * sps->pic_width_in_mbs - sps->frame_crop_left_offset - sps->frame_crop_right_offset; *height = MACROBLOCK_SIZE * sps->pic_height_in_map_units - sps->frame_crop_top_offset - sps->frame_crop_bottom_offset; } const char *h264_sps_chroma_format_name(uint8_t chroma_format_idc) { switch (chroma_format_idc) { case 0: return "monochrome"; case 1: return "YUV420"; case 2: return "YUV422"; case 3: return "YUV444"; default: return "???"; } } ================================================ FILE: src/h265/nal.c ================================================ /** * @file h265/nal.c H.265 header parser * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include #include void h265_nal_encode(uint8_t buf[2], unsigned nal_unit_type, unsigned nuh_temporal_id_plus1) { if (!buf) return; buf[0] = (nal_unit_type & 0x3f) << 1; buf[1] = nuh_temporal_id_plus1 & 0x07; } int h265_nal_encode_mbuf(struct mbuf *mb, const struct h265_nal *nal) { uint8_t buf[2]; h265_nal_encode(buf, nal->nal_unit_type, nal->nuh_temporal_id_plus1); return mbuf_write_mem(mb, buf, sizeof(buf)); } int h265_nal_decode(struct h265_nal *nal, const uint8_t *p) { bool forbidden_zero_bit; if (!nal || !p) return EINVAL; forbidden_zero_bit = p[0] >> 7; nal->nal_unit_type = (p[0] >> 1) & 0x3f; nal->nuh_layer_id = (p[0]&1)<<5 | p[1] >> 3; nal->nuh_temporal_id_plus1 = p[1] & 0x07; if (forbidden_zero_bit) { re_fprintf(stderr, "h265: nal_decode: FORBIDDEN bit set\n"); return EBADMSG; } return 0; } int h265_nal_print(struct re_printf *pf, const struct h265_nal *nal) { if (!nal) return 0; return re_hprintf(pf, "type=%u(%s), TID=%u\n", nal->nal_unit_type, h265_nalunit_name(nal->nal_unit_type), nal->nuh_temporal_id_plus1); } const char *h265_nalunit_name(enum h265_naltype type) { switch (type) { /* VCL class */ case H265_NAL_TRAIL_N: return "TRAIL_N"; case H265_NAL_TRAIL_R: return "TRAIL_R"; case H265_NAL_TSA_N: return "TSA_N"; case H265_NAL_TSA_R: return "TSA_R"; case H265_NAL_STSA_N: return "STSA_N"; case H265_NAL_STSA_R: return "STSA_R"; case H265_NAL_RADL_N: return "RADL_N"; case H265_NAL_RADL_R: return "RADL_R"; case H265_NAL_RASL_N: return "RASL_N"; case H265_NAL_RASL_R: return "RASL_R"; case H265_NAL_BLA_W_LP: return "BLA_W_LP"; case H265_NAL_BLA_W_RADL: return "BLA_W_RADL"; case H265_NAL_BLA_N_LP: return "BLA_N_LP"; case H265_NAL_IDR_W_RADL: return "IDR_W_RADL"; case H265_NAL_IDR_N_LP: return "IDR_N_LP"; case H265_NAL_CRA_NUT: return "CRA_NUT"; case H265_NAL_RSV_IRAP_VCL22: return "H265_NAL_RSV_IRAP_VCL22"; case H265_NAL_RSV_IRAP_VCL23: return "H265_NAL_RSV_IRAP_VCL23"; /* non-VCL class */ case H265_NAL_VPS_NUT: return "VPS_NUT"; case H265_NAL_SPS_NUT: return "SPS_NUT"; case H265_NAL_PPS_NUT: return "PPS_NUT"; case H265_NAL_AUD: return "AUD"; case H265_NAL_EOS_NUT: return "EOS_NUT"; case H265_NAL_EOB_NUT: return "EOB_NUT"; case H265_NAL_FD_NUT: return "FD_NUT"; case H265_NAL_PREFIX_SEI_NUT: return "PREFIX_SEI_NUT"; case H265_NAL_SUFFIX_SEI_NUT: return "SUFFIX_SEI_NUT"; /* RFC 7798 */ case H265_NAL_AP: return "H265_NAL_AP"; case H265_NAL_FU: return "H265_NAL_FU"; } return "???"; } bool h265_is_keyframe(enum h265_naltype type) { /* between 16 and 23 (inclusive) */ switch (type) { case H265_NAL_BLA_W_LP: case H265_NAL_BLA_W_RADL: case H265_NAL_BLA_N_LP: case H265_NAL_IDR_W_RADL: case H265_NAL_IDR_N_LP: case H265_NAL_CRA_NUT: case H265_NAL_RSV_IRAP_VCL22: case H265_NAL_RSV_IRAP_VCL23: return true; default: return false; } } static inline int packetize(bool marker, const uint8_t *buf, size_t len, size_t maxlen, uint64_t rtp_ts, h265_packet_h *pkth, void *arg) { int err = 0; if (len <= maxlen) { err = pkth(marker, rtp_ts, NULL, 0, buf, len, arg); } else { struct h265_nal nal; uint8_t fu_hdr[3]; const size_t flen = maxlen - sizeof(fu_hdr); err = h265_nal_decode(&nal, buf); if (err) { re_fprintf(stderr, "h265: encode: could not decode" " NAL of %zu bytes (%m)\n", len, err); return err; } h265_nal_encode(fu_hdr, H265_NAL_FU, nal.nuh_temporal_id_plus1); fu_hdr[2] = 1<<7 | nal.nal_unit_type; buf+=2; len-=2; while (len > flen) { err |= pkth(false, rtp_ts, fu_hdr, 3, buf, flen, arg); buf += flen; len -= flen; fu_hdr[2] &= ~(1 << 7); /* clear Start bit */ } fu_hdr[2] |= 1<<6; /* set END bit */ err |= pkth(marker, rtp_ts, fu_hdr, 3, buf, len, arg); } return err; } int h265_packetize(uint64_t rtp_ts, const uint8_t *buf, size_t len, size_t pktsize, h265_packet_h *pkth, void *arg) { const uint8_t *start = buf; const uint8_t *end = buf + len; const uint8_t *r; int err = 0; r = h264_find_startcode(start, end); while (r < end) { const uint8_t *r1; bool marker; /* skip zeros */ while (!*(r++)) ; r1 = h264_find_startcode(r, end); marker = (r1 >= end); err |= packetize(marker, r, r1-r, pktsize, rtp_ts, pkth, arg); r = r1; } return err; } ================================================ FILE: src/hash/func.c ================================================ /** * @file func.c Hashmap functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #define FNV1_32A_INIT UINT32_C(0x811c9dc5) #define FNV_32_PRIME UINT32_C(0x01000193) /** * Calculate hash-value using "Jenkins One-at-a-time" hash algorithm. * * @param key Pointer to key * @param len Key length * * @return Calculated hash-value */ uint32_t hash_joaat(const uint8_t *key, size_t len) { uint32_t hash = 0; size_t i; for (i = 0; i < len; i++) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /** * Calculate hash-value for a case-insensitive string * * @param str String * @param len Length of string * * @return Calculated hash-value */ uint32_t hash_joaat_ci(const char *str, size_t len) { uint32_t hash = 0; size_t i; if (!str) return 0; for (i = 0; i < len; i++) { hash += tolower(str[i]); hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /** * Calculate hash-value for a NULL-terminated string * * @param str String * * @return Calculated hash-value */ uint32_t hash_joaat_str(const char *str) { uint32_t hash = 0; while (*str) { hash += *str++; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /** * Calculate hash-value for a case-insensitive NULL-terminated string * * @param str String * * @return Calculated hash-value */ uint32_t hash_joaat_str_ci(const char *str) { uint32_t hash = 0; while (*str) { hash += tolower(*str++); hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /** * Calculate hash-value for a pointer-length object * * @param pl Pointer-length object * * @return Calculated hash-value */ uint32_t hash_joaat_pl(const struct pl *pl) { return pl ? hash_joaat((const uint8_t *)pl->p, pl->l) : 0; } /** * Calculate hash-value for a case-insensitive pointer-length object * * @param pl Pointer-length object * * @return Calculated hash-value */ uint32_t hash_joaat_pl_ci(const struct pl *pl) { return pl ? hash_joaat_ci(pl->p, pl->l) : 0; } /** * Calculate hash-value using fast hash algorithm. * * @param k Pointer to key * @param len Key length * * @return Calculated hash-value */ uint32_t hash_fast(const char *k, size_t len) { uint32_t h = FNV1_32A_INIT; if (!k) return 0; while (len--) { h ^= (uint32_t)*k++; h *= FNV_32_PRIME; } return h; } /** * Calculate hash-value for a NULL-terminated string * * @param str String * * @return Calculated hash-value */ uint32_t hash_fast_str(const char *str) { uint32_t h = FNV1_32A_INIT; if (!str) return 0; while (*str) { h ^= (uint32_t)*str++; h *= FNV_32_PRIME; } return h; } ================================================ FILE: src/hash/hash.c ================================================ /** * @file hash.c Hashmap table * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include /** Defines a hashmap table */ struct hash { struct list *bucket; /**< Bucket with linked lists */ uint32_t bsize; /**< Bucket size */ }; static void hash_destructor(void *data) { struct hash *h = data; mem_deref(h->bucket); } /** * Allocate a new hashmap table * * @param hp Address of hashmap pointer * @param bsize Bucket size * * @return 0 if success, otherwise errorcode */ int hash_alloc(struct hash **hp, uint32_t bsize) { struct hash *h; int err = 0; if (!hp || !bsize) return EINVAL; /* Validate bucket size */ if (bsize & (bsize-1)) return EINVAL; h = mem_zalloc(sizeof(*h), hash_destructor); if (!h) return ENOMEM; h->bsize = bsize; h->bucket = mem_zalloc(bsize*sizeof(*h->bucket), NULL); if (!h->bucket) { err = ENOMEM; goto out; } out: if (err) mem_deref(h); else *hp = h; return err; } /** * Add an element to the hashmap table * * @param h Hashmap table * @param key Hash key * @param le List element * @param data Element data */ void hash_append(struct hash *h, uint32_t key, struct le *le, void *data) { if (!h || !le) return; list_append(&h->bucket[key & (h->bsize-1)], le, data); } /** * Unlink an element from the hashmap table * * @param le List element */ void hash_unlink(struct le *le) { list_unlink(le); } /** * Apply a handler function to all elements in the hashmap with a matching key * * @param h Hashmap table * @param key Hash key * @param ah Apply handler * @param arg Handler argument * * @return List element if traversing stopped, otherwise NULL */ struct le *hash_lookup(const struct hash *h, uint32_t key, list_apply_h *ah, void *arg) { if (!h || !ah) return NULL; return list_apply(&h->bucket[key & (h->bsize-1)], true, ah, arg); } /** * Apply a handler function to all elements in the hashmap * * @param h Hashmap table * @param ah Apply handler * @param arg Handler argument * * @return List element if traversing stopped, otherwise NULL */ struct le *hash_apply(const struct hash *h, list_apply_h *ah, void *arg) { struct le *le = NULL; uint32_t i; if (!h || !ah) return NULL; for (i=0; (ibsize) && !le; i++) le = list_apply(&h->bucket[i], true, ah, arg); return le; } /** * Return bucket list for a given bucket index * * @param h Hashmap table * @param i Bucket index * * @return Bucket list if valid input, otherwise NULL */ struct list *hash_list_idx(const struct hash *h, uint32_t i) { if (!h || i >= h->bsize) return NULL; return &h->bucket[i]; } /** * Return bucket list for a given key * * @param h Hashmap table * @param key Hash key * * @return Bucket list if valid input, otherwise NULL */ struct list *hash_list(const struct hash *h, uint32_t key) { return h ? &h->bucket[key & (h->bsize - 1)] : NULL; } /** * Get hash bucket size * * @param h Hashmap table * * @return hash bucket size */ uint32_t hash_bsize(const struct hash *h) { return h ? h->bsize : 0; } /** * Flush a hashmap and free all elements * * @param h Hashmap table */ void hash_flush(struct hash *h) { uint32_t i; if (!h) return; for (i=0; ibsize; i++) list_flush(&h->bucket[i]); } /** * Clear a hashmap without dereferencing the elements * * @param h Hashmap table */ void hash_clear(struct hash *h) { uint32_t i; if (!h) return; for (i=0; ibsize; i++) list_clear(&h->bucket[i]); } /** * Calculate a valid hash size from a random size * * @param size Requested size * * @return Valid hash size */ uint32_t hash_valid_size(uint32_t size) { uint32_t x; for (x=0; (uint32_t)1<bsize); for (uint32_t i = 0; i < h->bsize; i++) { struct le *he = hash_list_idx(h, i)->head; if (!he) continue; uint32_t c = list_count(he->list); if (!c) continue; err |= re_hprintf(pf, " [%u]: %u\n", i, c); } return err; } ================================================ FILE: src/hmac/apple/hmac.c ================================================ /** * @file apple/hmac.c HMAC using Apple API * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include #include #include #include enum { KEY_SIZE = 256 }; struct hmac { CCHmacContext ctx; uint8_t key[KEY_SIZE]; size_t key_len; CCHmacAlgorithm algo; }; static void destructor(void *arg) { struct hmac *hmac = arg; memset(&hmac->ctx, 0, sizeof(hmac->ctx)); } int hmac_create(struct hmac **hmacp, enum hmac_hash hash, const uint8_t *key, size_t key_len) { struct hmac *hmac; CCHmacAlgorithm algo; if (!hmacp || !key || !key_len || key_len > KEY_SIZE) return EINVAL; switch (hash) { case HMAC_HASH_SHA1: algo = kCCHmacAlgSHA1; break; case HMAC_HASH_SHA256: algo = kCCHmacAlgSHA256; break; default: return ENOTSUP; } hmac = mem_zalloc(sizeof(*hmac), destructor); if (!hmac) return ENOMEM; memcpy(hmac->key, key, key_len); hmac->key_len = key_len; hmac->algo = algo; *hmacp = hmac; return 0; } int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len, const uint8_t *data, size_t data_len) { if (!hmac || !md || !md_len || !data || !data_len) return EINVAL; /* reset state */ CCHmacInit(&hmac->ctx, hmac->algo, hmac->key, hmac->key_len); CCHmacUpdate(&hmac->ctx, data, data_len); CCHmacFinal(&hmac->ctx, md); return 0; } ================================================ FILE: src/hmac/hmac.c ================================================ /** * @file hmac/hmac.c HMAC-SHA1 * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include struct hmac { uint8_t key[SHA_DIGEST_LENGTH]; size_t key_len; }; static void destructor(void *arg) { struct hmac *hmac = arg; memset(hmac, 0, sizeof(*hmac)); } int hmac_create(struct hmac **hmacp, enum hmac_hash hash, const uint8_t *key, size_t key_len) { struct hmac *hmac; if (!hmacp || !key || !key_len) return EINVAL; if (hash != HMAC_HASH_SHA1) return ENOTSUP; if (key_len > SHA_DIGEST_LENGTH) return EINVAL; hmac = mem_zalloc(sizeof(*hmac), destructor); if (!hmac) return ENOMEM; memcpy(hmac->key, key, key_len); hmac->key_len = key_len; *hmacp = hmac; return 0; } int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len, const uint8_t *data, size_t data_len) { if (!hmac || !md || !md_len || !data || !data_len) return EINVAL; hmac_sha1(hmac->key, hmac->key_len, data, data_len, md, md_len); return 0; } ================================================ FILE: src/hmac/hmac_sha1.c ================================================ /** * @file hmac_sha1.c Implements HMAC-SHA1 as of RFC 2202 * * Copyright (C) 2010 Creytiv.com */ #include #include #ifdef USE_OPENSSL #include #include #include #elif defined (__APPLE__) #include #elif defined (WIN32) #include #include #elif defined (USE_MBEDTLS) #include #include #endif #include #define DEBUG_MODULE "hmac" #define DEBUG_LEVEL 5 #include #if !defined (USE_OPENSSL) && defined (WIN32) static void compute_hash(ALG_ID alg_id, const void* data, size_t dataSize, uint8_t hashBuf[64], DWORD hashSize, const void *hmacSecret, size_t hmacSecretSize) { DWORD hashSizeSize = sizeof(hashSize); HCRYPTPROV context; HCRYPTKEY hmackey = 0; CryptAcquireContext(&context, 0, 0, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT); struct HmacSecretBlob { BLOBHEADER header; DWORD hmacSecretSize; BYTE hmacSecret[1]; }; size_t hmacSecretBlobSize = MAX(offsetof(struct HmacSecretBlob, hmacSecret) + hmacSecretSize, sizeof(struct HmacSecretBlob)); uint8_t blobData[256]; struct HmacSecretBlob* hmacSecretBlob = (struct HmacSecretBlob*) ( blobData ); hmacSecretBlob->header.bType = PLAINTEXTKEYBLOB; hmacSecretBlob->header.bVersion = CUR_BLOB_VERSION; hmacSecretBlob->header.reserved = 0; hmacSecretBlob->header.aiKeyAlg = CALG_RC2; hmacSecretBlob->hmacSecretSize = (DWORD)hmacSecretSize; memcpy(hmacSecretBlob->hmacSecret, hmacSecret, hmacSecretSize); CryptImportKey(context, blobData, (DWORD)hmacSecretBlobSize, 0, CRYPT_IPSEC_HMAC_KEY, &hmackey); HCRYPTHASH hash; if (CryptCreateHash(context, CALG_HMAC, hmackey, 0, &hash)) { HMAC_INFO info = { 0 }; info.HashAlgid = alg_id; CryptSetHashParam(hash, HP_HMAC_INFO, (BYTE *)&info, 0); CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *)&hashSize, &hashSizeSize, 0); if (hashSize == 0) { DEBUG_WARNING("INVALID HASHSIZE\n"); } CryptHashData(hash, (BYTE*)data, (DWORD)dataSize, 0); CryptGetHashParam(hash, HP_HASHVAL, hashBuf, &hashSize, 0); CryptDestroyHash(hash); } CryptDestroyKey(hmackey); CryptReleaseContext(context, 0); } #endif /** * Function to compute the digest * * @param k Secret key * @param lk Length of the key in bytes * @param d Data * @param ld Length of data in bytes * @param out Digest output * @param t Size of digest output */ void hmac_sha1(const uint8_t *k, /* secret key */ size_t lk, /* length of the key in bytes */ const uint8_t *d, /* data */ size_t ld, /* length of data in bytes */ uint8_t *out, /* output buffer, at least "t" bytes */ size_t t) { #ifdef USE_OPENSSL (void)t; if (!HMAC(EVP_sha1(), k, (int)lk, d, ld, out, NULL)) ERR_clear_error(); #elif defined (__APPLE__) (void)t; CCHmac(kCCHmacAlgSHA1, k, lk, d, ld, out); #elif defined (WIN32) compute_hash(CALG_SHA1, d, ld, out, (DWORD)t, k, lk); #elif defined (MBEDTLS_MD_C) int err; (void)t; err = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), k, lk, d, ld, out); if (err) DEBUG_WARNING("mbedtls_md_hmac: %s\n", mbedtls_high_level_strerr(err)); #else (void)k; (void)lk; (void)d; (void)ld; (void)out; (void)t; #error missing HMAC-SHA1 backend #endif } void hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len, uint8_t *out, size_t out_len) { #ifdef USE_OPENSSL (void)out_len; if (!HMAC(EVP_sha256(), key, (int)key_len, data, data_len, out, NULL)) ERR_clear_error(); #elif defined (__APPLE__) (void)out_len; CCHmac(kCCHmacAlgSHA256, key, key_len, data, data_len, out); #elif defined (WIN32) compute_hash(CALG_SHA_256, data, data_len, out, (DWORD)out_len, key, key_len); #elif defined (MBEDTLS_MD_C) int err; (void)out_len; err = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, key_len, data, data_len, out); if (err) DEBUG_WARNING("mbedtls_md_hmac: %s\n", mbedtls_high_level_strerr(err)); #else (void)key; (void)key_len; (void)data; (void)data_len; (void)out; (void)out_len; #error missing HMAC-SHA256 backend #endif } ================================================ FILE: src/hmac/openssl/hmac.c ================================================ /** * @file openssl/hmac.c HMAC using OpenSSL * * Copyright (C) 2010 Creytiv.com * Copyright (C) 2022 Sebastian Reimers */ #include #include #include #include #include #include struct hmac { const EVP_MD *evp; uint8_t *key; int key_len; }; static void destructor(void *arg) { struct hmac *hmac = arg; mem_deref(hmac->key); } int hmac_create(struct hmac **hmacp, enum hmac_hash hash, const uint8_t *key, size_t key_len) { struct hmac *hmac; int err = 0; if (!hmacp || !key || !key_len) return EINVAL; hmac = mem_zalloc(sizeof(*hmac), destructor); if (!hmac) return ENOMEM; hmac->key = mem_zalloc(key_len, NULL); if (!hmac->key) { err = ENOMEM; goto error; } memcpy(hmac->key, key, key_len); hmac->key_len = (int)key_len; switch (hash) { case HMAC_HASH_SHA1: hmac->evp = EVP_sha1(); break; case HMAC_HASH_SHA256: hmac->evp = EVP_sha256(); break; default: err = ENOTSUP; goto error; } *hmacp = hmac; return 0; error: mem_deref(hmac); return err; } int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len, const uint8_t *data, size_t data_len) { unsigned int len = (unsigned int)md_len; unsigned char *rval; if (!hmac || !md || !md_len || !data || !data_len) return EINVAL; rval = HMAC(hmac->evp, hmac->key, hmac->key_len, data, data_len, md, &len); if (!rval) { ERR_clear_error(); return EPROTO; } return 0; } ================================================ FILE: src/http/auth.c ================================================ /** * @file http/auth.c HTTP Authentication * * Copyright (C) 2011 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include enum { NONCE_EXPIRES = 300, NONCE_MIN_SIZE = 33, }; static uint64_t secret; static bool secret_set; /** * Print HTTP digest authentication challenge * * @param pf Print function for output * @param auth Authentication parameters * * @return 0 if success, otherwise errorcode */ int http_auth_print_challenge(struct re_printf *pf, const struct http_auth *auth) { uint8_t key[MD5_SIZE]; uint64_t nv[2]; if (!auth) return 0; if (!secret_set) { secret = rand_u64(); secret_set = true; } nv[0] = time(NULL); nv[1] = secret; md5((uint8_t *)nv, sizeof(nv), key); return re_hprintf(pf, "Digest realm=\"%s\", nonce=\"%w%llx\", " "qop=\"auth\"%s", auth->realm, key, sizeof(key), nv[0], auth->stale ? ", stale=true" : ""); } static int chk_nonce(const struct pl *nonce, uint32_t expires) { uint8_t nkey[MD5_SIZE], ckey[MD5_SIZE]; uint64_t nv[2]; struct pl pl; int64_t age; unsigned i; if (!nonce || !nonce->p || nonce->l < NONCE_MIN_SIZE) return EINVAL; pl = *nonce; for (i=0; i expires) return ETIMEDOUT; return 0; } /** * Check HTTP digest authorization * * @param hval Authorization header value * @param method Request method * @param auth Authentication parameters * @param authh Authentication handler * @param arg Authentication handler argument * * @return true if check is passed, otherwise false */ bool http_auth_check(const struct pl *hval, const struct pl *method, struct http_auth *auth, http_auth_h *authh, void *arg) { struct httpauth_digest_resp resp; uint8_t ha1[MD5_SIZE]; if (!hval || !method || !auth || !authh) return false; if (httpauth_digest_response_decode(&resp, hval)) return false; if (pl_strcasecmp(&resp.realm, auth->realm)) return false; if (chk_nonce(&resp.nonce, NONCE_EXPIRES)) { auth->stale = true; return false; } if (authh(&resp.username, ha1, arg)) return false; if (httpauth_digest_response_auth(&resp, method, ha1)) return false; return true; } /** * Check HTTP digest authorization of an HTTP request * * @param msg HTTP message * @param auth Authentication parameters * @param authh Authentication handler * @param arg Authentication handler argument * * @return true if check is passed, otherwise false */ bool http_auth_check_request(const struct http_msg *msg, struct http_auth *auth, http_auth_h *authh, void *arg) { const struct http_hdr *hdr; if (!msg) return false; hdr = http_msg_hdr(msg, HTTP_HDR_AUTHORIZATION); if (!hdr) return false; return http_auth_check(&hdr->val, &msg->met, auth, authh, arg); } ================================================ FILE: src/http/chunk.c ================================================ /** * @file http/chunk.c Chunked Transfer Encoding * * Copyright (C) 2011 Creytiv.com */ #include #include #include #include "http.h" static int decode_chunk_size(struct http_chunk *chunk, struct mbuf *mb) { while (mbuf_get_left(mb)) { char ch = (char)mbuf_read_u8(mb); uint8_t c; if (ch == '\n') { if (chunk->digit) { chunk->digit = false; chunk->param = false; return 0; } else continue; } if (chunk->param) continue; if ('0' <= ch && ch <= '9') c = ch - '0'; else if ('A' <= ch && ch <= 'F') c = ch - 'A' + 10; else if ('a' <= ch && ch <= 'f') c = ch - 'a' + 10; else if (ch == '\r' || ch == ' ' || ch == '\t') continue; else if (ch == ';' && chunk->digit) { chunk->param = true; continue; } else return EPROTO; chunk->digit = true; chunk->size <<= 4; chunk->size += c; } return ENODATA; } static int decode_trailer(struct http_chunk *chunk, struct mbuf *mb) { while (mbuf_get_left(mb)) { char ch = (char)mbuf_read_u8(mb); if (ch == '\n') { if (++chunk->lf >= 2) return 0; } else if (ch != '\r') chunk->lf = 0; } return ENODATA; } int http_chunk_decode(struct http_chunk *chunk, struct mbuf *mb, size_t *size) { int err; if (!chunk || !mb || !size) return EINVAL; if (chunk->trailer) { err = decode_trailer(chunk, mb); if (err) return err; *size = 0; return 0; } err = decode_chunk_size(chunk, mb); if (err) return err; if (chunk->size == 0) { chunk->trailer = true; chunk->lf = 1; err = decode_trailer(chunk, mb); if (err) return err; } *size = chunk->size; chunk->size = 0; return 0; } ================================================ FILE: src/http/client.c ================================================ /** * @file http/client.c HTTP Client * * Copyright (C) 2011 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "http.h" #define DEBUG_MODULE "http_client" #define DEBUG_LEVEL 5 #include enum { CONN_TIMEOUT = 30000, RECV_TIMEOUT = 60000, IDLE_TIMEOUT = 900000, BUFSIZE_MAX = 524288, CONN_BSIZE = 256, QUERY_HASH_SIZE = 16, TCP_HASH_SIZE = 2, }; struct http_cli { struct http_conf conf; struct list reql; struct hash *ht_conn; struct dnsc *dnsc; struct tls *tls; char *tlshn; struct mbuf *cert; struct mbuf *key; struct sa laddr; struct sa laddr6; size_t bufsize_max; }; struct conn; struct http_req { struct http_chunk chunk; struct sa srvv[16]; struct le le; struct http_req **reqp; struct http_cli *cli; struct http_msg *msg; struct dns_query *dq; struct dns_query *dq6; struct conn *conn; struct mbuf *mbreq; struct mbuf *mb; char *host; http_resp_h *resph; http_data_h *datah; http_conn_h *connh; void *arg; size_t rx_len; unsigned srvc; uint16_t port; bool chunked; bool secure; bool close; http_bodyh *bodyh; }; static const struct http_conf default_conf = { CONN_TIMEOUT, RECV_TIMEOUT, IDLE_TIMEOUT, }; struct conn { struct tmr tmr; struct sa addr; struct le he; struct http_req *req; struct tls_conn *sc; struct tcp_conn *tc; uint64_t usec; }; static void req_close(struct http_req *req, int err, const struct http_msg *msg); static int req_connect(struct http_req *req); static void timeout_handler(void *arg); static void cli_destructor(void *arg) { struct http_cli *cli = arg; struct le *le = cli->reql.head; while (le) { struct http_req *req = le->data; le = le->next; req_close(req, ECONNABORTED, NULL); } hash_flush(cli->ht_conn); mem_deref(cli->ht_conn); mem_deref(cli->cert); mem_deref(cli->key); mem_deref(cli->dnsc); mem_deref(cli->tls); mem_deref(cli->tlshn); } static void req_destructor(void *arg) { struct http_req *req = arg; list_unlink(&req->le); mem_deref(req->msg); mem_deref(req->dq); mem_deref(req->dq6); mem_deref(req->conn); mem_deref(req->mbreq); mem_deref(req->mb); mem_deref(req->host); } static void conn_destructor(void *arg) { struct conn *conn = arg; tmr_cancel(&conn->tmr); hash_unlink(&conn->he); mem_deref(conn->sc); mem_deref(conn->tc); } static void conn_idle(struct conn *conn) { struct http_req *req; struct http_cli *cli; if (!conn) return; req = conn->req; if (!req) goto out; cli = req->cli; if (!cli) goto out; tmr_start(&conn->tmr, cli->conf.idle_timeout, timeout_handler, conn); out: conn->req = NULL; } static void req_close(struct http_req *req, int err, const struct http_msg *msg) { list_unlink(&req->le); req->dq = mem_deref(req->dq); req->dq6 = mem_deref(req->dq6); req->datah = NULL; if (req->conn) { if (req->connh) req->connh(req->conn->tc, req->conn->sc, req->arg); if (err || req->close || req->connh) mem_deref(req->conn); else conn_idle(req->conn); req->conn = NULL; } req->connh = NULL; if (req->reqp) { *req->reqp = NULL; req->reqp = NULL; } if (req->resph) { if (msg) msg->mb->pos = 0; req->resph(err, msg, req->arg); req->resph = NULL; } mem_deref(req); } static void try_next(struct conn *conn, int err) { struct http_req *req = conn->req; bool retry = conn->usec > 1; mem_deref(conn); if (!req) return; req->conn = NULL; if (retry) ++req->srvc; if (req->srvc > 0 && !req->msg) { err = req_connect(req); if (!err) return; } req_close(req, err, NULL); } static int write_body_buf(struct http_msg *msg, const uint8_t *buf, size_t sz, size_t max_size) { if ((msg->mb->pos + sz) > max_size) return EOVERFLOW; return mbuf_write_mem(msg->mb, buf, sz); } static int write_body(struct http_req *req, struct mbuf *mb) { const size_t size = min(mbuf_get_left(mb), req->rx_len); int err; if (size == 0) return 0; if (req->datah) err = req->datah(mbuf_buf(mb), size, req->msg, req->arg); else err = write_body_buf(req->msg, mbuf_buf(mb), size, req->cli->bufsize_max); if (err) return err; req->rx_len -= size; mb->pos += size; return 0; } static int req_recv(struct http_req *req, struct mbuf *mb, bool *last) { int err; *last = false; if (!req->chunked) { err = write_body(req, mb); if (err) return err; if (req->rx_len == 0) *last = true; return 0; } tmr_start(&req->conn->tmr, req->cli->conf.recv_timeout, timeout_handler, req->conn); while (mbuf_get_left(mb)) { if (req->rx_len == 0) { err = http_chunk_decode(&req->chunk, mb, &req->rx_len); if (err == ENODATA) return 0; else if (err) return err; else if (req->rx_len == 0) { *last = true; return 0; } } err = write_body(req, mb); if (err) return err; } return 0; } static void timeout_handler(void *arg) { struct conn *conn = arg; try_next(conn, ETIMEDOUT); } static int read_req_data(struct http_req *req) { int err = 0; struct mbuf *mb; size_t rlen = 0; if (!req->bodyh) return 0; mb = mbuf_alloc(1); if (!mb) return ENOMEM; rlen = req->bodyh(mb, req->arg); if (!rlen) goto out; err = mbuf_write_mem(req->mbreq, mb->buf, mb->end); if (err) goto out; out: mem_deref(mb); return err; } static int send_buf(struct tcp_conn *tc, struct mbuf *large, size_t max_size) { struct mbuf *mb = NULL; size_t len; int err = 0; if (!tc || !large) return EINVAL; if (!mbuf_get_left(large)) return 0; mb = mbuf_alloc_ref(large); if (!mb) { err = ENOMEM; goto out; } len = min(mbuf_get_left(large), max_size); mb->end = large->pos + len; err = tcp_send(tc, mb); if (err) goto out; mbuf_advance(large, len); out: mem_deref(mb); return err; } static int send_req_buf(struct conn *conn) { int err; struct http_req *req; if (!conn) return EINVAL; req = conn->req; if (!req || !req->cli) return EINVAL; if (!mbuf_get_left(req->mbreq)) return 0; err = send_buf(conn->tc, req->mbreq, req->cli->bufsize_max); if (err) goto out; if (!mbuf_get_left(req->mbreq)) { if (req->mbreq) mbuf_rewind(req->mbreq); err = read_req_data(req); if (err) goto out; if (req->mbreq) mbuf_set_pos(req->mbreq, 0); if (mbuf_get_left(req->mbreq) && !tcp_sendq_used(conn->tc)) (void) send_req_buf(conn); } tmr_start(&conn->tmr, req->cli->conf.recv_timeout, timeout_handler, conn); out: return err; } static void send_req_handler(void *arg) { (void) send_req_buf((struct conn *)arg); } static void estab_handler(void *arg) { struct conn *conn = arg; struct http_req *req = conn->req; int err; if (!req || !req->cli) return; err = send_req_buf(conn); if (err) { try_next(conn, err); return; } if (mbuf_get_left(req->mbreq)) tcp_set_send(conn->tc, send_req_handler); tmr_start(&conn->tmr, req->cli->conf.recv_timeout, timeout_handler, conn); } static void recv_handler(struct mbuf *mb, void *arg) { const struct http_hdr *hdr; struct conn *conn = arg; struct http_req *req = conn->req; size_t pos; bool last; int err; if (!req) return; if (req->msg) { err = req_recv(req, mb, &last); if (err || last) goto out; return; } if (req->mb) { const size_t len = mbuf_get_left(mb); if ((mbuf_get_left(req->mb) + len) > req->cli->bufsize_max) { err = EOVERFLOW; goto out; } pos = req->mb->pos; req->mb->pos = req->mb->end; err = mbuf_write_mem(req->mb, mbuf_buf(mb), len); if (err) goto out; req->mb->pos = pos; } else { req->mb = mem_ref(mb); } pos = req->mb->pos; err = http_msg_decode(&req->msg, req->mb, false); if (err) { if (err == ENODATA) { req->mb->pos = pos; return; } goto out; } if (req->datah) tmr_cancel(&conn->tmr); hdr = http_msg_hdr(req->msg, HTTP_HDR_CONNECTION); if (hdr && !pl_strcasecmp(&hdr->val, "close")) req->close = true; if (http_msg_hdr_has_value(req->msg, HTTP_HDR_TRANSFER_ENCODING, "chunked")) req->chunked = true; else req->rx_len = req->msg->clen; err = req_recv(req, req->mb, &last); if (err || last) goto out; return; out: req_close(req, err, req->msg); } static void close_handler(int err, void *arg) { struct conn *conn = arg; try_next(conn, err ? err : ECONNRESET); } static bool conn_cmp(struct le *le, void *arg) { const struct conn *conn = le->data; const struct http_req *req = arg; if (!sa_cmp(&req->srvv[req->srvc], &conn->addr, SA_ALL)) return false; if (req->secure != !!conn->sc) return false; return conn->req == NULL; } static int conn_connect(struct http_req *req) { struct sa *laddr = NULL; int err = 0; if (!req || !req->cli) return EINVAL; const struct sa *addr = &req->srvv[req->srvc]; struct conn *conn = list_ledata(hash_lookup(req->cli->ht_conn, sa_hash(addr, SA_ALL), conn_cmp, req)); if (conn) { err = send_req_buf(conn); if (!err) { tmr_start(&conn->tmr, req->cli->conf.recv_timeout, timeout_handler, conn); req->conn = conn; conn->req = req; ++conn->usec; if (mbuf_get_left(req->mbreq)) tcp_set_send(conn->tc, send_req_handler); return 0; } mem_deref(conn); } conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return ENOMEM; hash_append(req->cli->ht_conn, sa_hash(addr, SA_ALL), &conn->he, conn); conn->addr = *addr; conn->usec = 1; if (sa_af(&conn->addr) == AF_INET) laddr = &req->cli->laddr; else if (sa_af(&conn->addr) == AF_INET6) laddr = &req->cli->laddr6; if (sa_isset(laddr, SA_ADDR)) { sa_set_scopeid(&conn->addr, sa_scopeid(laddr)); err = tcp_connect_bind(&conn->tc, addr, estab_handler, recv_handler,close_handler, laddr, conn); } else { err = tcp_connect(&conn->tc, addr, estab_handler, recv_handler, close_handler, conn); } if (err) goto out; #ifdef USE_TLS if (req->secure) { err = tls_start_tcp(&conn->sc, req->cli->tls, conn->tc, 0); if (err) goto out; if (req->cli->tlshn) err = tls_set_verify_server(conn->sc, req->cli->tlshn); else err = tls_set_verify_server(conn->sc, req->host); if (err) goto out; } #endif tmr_start(&conn->tmr, req->cli->conf.conn_timeout, timeout_handler, conn); if (!err) { req->conn = conn; conn->req = req; } out: if (err) mem_deref(conn); return err; } static int req_connect(struct http_req *req) { int err = EINVAL; while (req->srvc > 0) { --req->srvc; req->mb = mem_deref(req->mb); err = conn_connect(req); if (!err) break; } return err; } static bool rr_handler(struct dnsrr *rr, void *arg) { struct http_req *req = arg; if (req->srvc >= RE_ARRAY_SIZE(req->srvv)) return true; switch (rr->type) { case DNS_TYPE_A: sa_set_in(&req->srvv[req->srvc++], rr->rdata.a.addr, req->port); break; case DNS_TYPE_AAAA: sa_set_in6(&req->srvv[req->srvc++], rr->rdata.aaaa.addr, req->port); break; } return false; } static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct http_req *req = arg; (void)hdr; (void)authl; (void)addl; dns_rrlist_apply2(ansl, req->host, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN, true, rr_handler, req); /* wait for other (A/AAAA) query to complete */ if (req->dq || req->dq6) return; if (req->srvc == 0) { err = err ? err : EDESTADDRREQ; goto fail; } err = req_connect(req); if (err) goto fail; return; fail: req_close(req, err, NULL); } int http_uri_decode(struct http_uri *hu, const struct pl *uri) { int err = 0; if (!hu) return EINVAL; memset(hu, 0, sizeof(*hu)); /* Try IPv6 first */ err = re_regex(uri->p, uri->l, "[a-z]+://\\[[^\\]]+\\][:]*[0-9]*[^]+", &hu->scheme, &hu->host, NULL, &hu->port, &hu->path) || hu->scheme.p != uri->p; if (!err) goto out; /* Then non-IPv6 host */ err = re_regex(uri->p, uri->l, "[a-z]+://[^:/]+[:]*[0-9]*[^]+", &hu->scheme, &hu->host, NULL, &hu->port, &hu->path) || hu->scheme.p != uri->p; out: if (!err && !pl_isset(&hu->path)) pl_set_str(&hu->path, "/"); return err; } static int http_request_addr_v(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, const struct sa *addr, http_resp_h *resph, http_data_h *datah, http_bodyh *bodyh, void *arg, const char *fmt, va_list ap) { struct http_uri http_uri; struct pl pl; struct http_req *req; uint16_t defport; struct sa sa; bool ipv6; bool secure; int err; if (!cli || !met || !uri) return EINVAL; pl_set_str(&pl, uri); if (http_uri_decode(&http_uri, &pl)) return EINVAL; if (!pl_strcasecmp(&http_uri.scheme, "http") || !pl_strcasecmp(&http_uri.scheme, "ws")) { secure = false; defport = 80; } #ifdef USE_TLS else if (!pl_strcasecmp(&http_uri.scheme, "https") || !pl_strcasecmp(&http_uri.scheme, "wss")) { secure = true; defport = 443; } #endif else return ENOTSUP; req = mem_zalloc(sizeof(*req), req_destructor); if (!req) return ENOMEM; list_append(&cli->reql, &req->le, req); req->cli = cli; req->secure = secure; req->port = (addr && sa_port(addr)) ? sa_port(addr) : (pl_isset(&http_uri.port) ? pl_u32(&http_uri.port) : defport); req->resph = resph; req->datah = datah; req->bodyh = bodyh; req->arg = arg; err = pl_strdup(&req->host, &http_uri.host); if (err) goto out; req->mbreq = mbuf_alloc(1024); if (!req->mbreq) { err = ENOMEM; goto out; } ipv6 = sa_set(&sa, &http_uri.host, 0) == 0 && sa_af(&sa) == AF_INET6; err = mbuf_printf(req->mbreq, "%s %r HTTP/1.1\r\n" "Host: %s%r%s\r\n", met, &http_uri.path, ipv6 ? "[" : "", &http_uri.host, ipv6 ? "]" : ""); if (fmt) err |= mbuf_vprintf(req->mbreq, fmt, ap); else err |= mbuf_write_str(req->mbreq, "\r\n"); if (err) goto out; err = read_req_data(req); if (err) goto out; mbuf_set_pos(req->mbreq, 0); #ifdef USE_TLS if (cli->cert && cli->key) { err = tls_set_certificate_pem(cli->tls, (char *)cli->cert->buf, cli->cert->end, (char *)cli->key->buf, cli->key->end); } else if (cli->cert) { err = tls_set_certificate(cli->tls, (char *)cli->cert->buf, cli->cert->end); } if (err) goto out; #endif if (addr) { /* Use explicit addr instead of DNS resolution */ req->srvv[0] = *addr; sa_set_port(&req->srvv[0], req->port); req->srvc = 1; err = req_connect(req); } else if (!sa_set_str(&req->srvv[0], req->host, req->port)) { req->srvc = 1; err = req_connect(req); if (err) goto out; } else { struct sa tmp; err = dnsc_query(&req->dq, cli->dnsc, req->host, DNS_TYPE_A, DNS_CLASS_IN, true, query_handler, req); if (err) goto out; if (0 == net_default_source_addr_get(AF_INET6, &tmp)) { err = dnsc_query(&req->dq6, cli->dnsc, req->host, DNS_TYPE_AAAA, DNS_CLASS_IN, true, query_handler, req); if (err) goto out; } } out: if (err) mem_deref(req); else if (reqp) { req->reqp = reqp; *reqp = req; } return err; } /** * Send an HTTP request * * @param reqp Pointer to allocated HTTP request object * @param cli HTTP Client * @param met Request method * @param uri Request URI * @param resph Response handler * @param datah Content handler (optional) * @param bodyh Body handler (optional) * @param arg Handler argument * @param fmt Formatted HTTP headers and body (optional) * * @return 0 if success, otherwise errorcode */ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, http_resp_h *resph, http_data_h *datah, http_bodyh *bodyh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = http_request_addr_v(reqp, cli, met, uri, NULL, resph, datah, bodyh, arg, fmt, ap); va_end(ap); return err; } /** * Send an HTTP request with explicit server address (bypasses DNS) * * Like http_request but connects to addr instead of resolving the URI * host via DNS. The Host header still uses the hostname from uri. * If addr is NULL, falls back to DNS resolution like http_request(). * * @param reqp Pointer to allocated HTTP request object * @param cli HTTP Client * @param met Request method * @param uri Request URI (host used for Host header only) * @param addr Explicit server address for TCP connect, or NULL * @param resph Response handler * @param datah Content handler (optional) * @param bodyh Body handler (optional) * @param arg Handler argument * @param fmt Formatted HTTP headers and body (optional) * * @return 0 if success, otherwise errorcode */ int http_request_addr(struct http_req **reqp, struct http_cli *cli, const char *met, const char *uri, const struct sa *addr, http_resp_h *resph, http_data_h *datah, http_bodyh *bodyh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = http_request_addr_v(reqp, cli, met, uri, addr, resph, datah, bodyh, arg, fmt, ap); va_end(ap); return err; } /** * Set HTTP request connection handler * * @param req HTTP request object * @param connh Connection handler */ void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh) { if (!req) return; req->connh = connh; } int http_client_set_config(struct http_cli *cli, struct http_conf *conf) { if (!cli || !conf) return EINVAL; cli->conf = *conf; dnsc_conf_set_timeout(cli->dnsc, conf->conn_timeout, conf->idle_timeout); return 0; } /** * Allocate an HTTP Client instance * * @param clip Pointer to allocated HTTP Client * @param dnsc DNS Client * * @return 0 if success, otherwise errorcode */ int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc) { struct http_cli *cli; int err; if (!clip || !dnsc) return EINVAL; cli = mem_zalloc(sizeof(*cli), cli_destructor); if (!cli) return ENOMEM; err = hash_alloc(&cli->ht_conn, CONN_BSIZE); if (err) goto out; #ifdef USE_TLS err = tls_alloc(&cli->tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; err = tls_set_verify_purpose(cli->tls, "sslserver"); if (err) goto out; #else err = 0; #endif cli->dnsc = mem_ref(dnsc); cli->conf = default_conf; cli->bufsize_max = BUFSIZE_MAX; out: if (err) mem_deref(cli); else *clip = cli; return err; } #ifdef USE_TLS /** * Replace HTTP Client TLS Context * * @param cli HTTP Client * @param tls TLS Context * * @return 0 if success, otherwise errorcode */ int http_client_set_tls(struct http_cli *cli, struct tls *tls) { if (!cli || !tls) return EINVAL; mem_deref(cli->tls); cli->tls = mem_ref(tls); return 0; } /** * Get HTTP Client TLS Context * * @param cli HTTP Client * @param tls TLS Context * * @return 0 if success, otherwise errorcode */ int http_client_get_tls(struct http_cli *cli, struct tls **tls) { if (!cli || !tls) return EINVAL; *tls = cli->tls; return 0; } /** * Add trusted CA certificates * * @param cli HTTP Client * @param capath Path to CA certificates * * @return 0 if success, otherwise errorcode */ int http_client_add_ca(struct http_cli *cli, const char *tls_ca) { if (!cli || !tls_ca) return EINVAL; return tls_add_ca(cli->tls, tls_ca); } /** * Add trusted CA certificates given as string * * @param cli HTTP Client * @param capem The trusted CA as 0-terminated string given in PEM format * * @return 0 if success, otherwise errorcode */ int http_client_add_capem(struct http_cli *cli, const char *capem) { if (!cli) return EINVAL; return tls_add_capem(cli->tls, capem); } /** * Add trusted CRL certificates given as string * * @param cli HTTP Client * @param pem The trusted CRL as 0-terminated string given in PEM format * * @return 0 if success, otherwise errorcode */ int http_client_add_crlpem(struct http_cli *cli, const char *pem) { if (!cli) return EINVAL; return tls_add_crlpem(cli->tls, pem); } /** * Set client certificate * @param cli HTTP Client * @param path File path to client certificate * * @return 0 for success, error code otherwise. */ int http_client_set_cert(struct http_cli *cli, const char *path) { int err = 0; if (!cli || !path) return EINVAL; cli->cert = mem_deref(cli->cert); err = fs_fread(&cli->cert, path); if (err) { cli->cert = mem_deref(cli->cert); return err; } return 0; } /** * Set client certificate in PEM format * * @param cli HTTP Client * @param pem Client certificate as 0-terminated string in PEM format * * @return 0 for success, error code otherwise. */ /* ------------------------------------------------------------------------- */ int http_client_set_certpem(struct http_cli *cli, const char *pem) { if (!cli || !str_isset(pem)) return EINVAL; cli->cert = mem_deref(cli->cert); cli->cert = mbuf_alloc(strlen(pem)); return mbuf_write_str(cli->cert, pem); } /** * Set client key * * @param cli HTTP Client * @param path File path to client key * * @return 0 for success, error code otherwise. */ int http_client_set_key(struct http_cli *cli, const char *path) { int err = 0; if (!cli || !path) return EINVAL; cli->key = mem_deref(cli->key); err = fs_fread(&cli->key, path); if (err) { cli->key = mem_deref(cli->key); return err; } return 0; } /** * Set client key in PEM format * * @param cli HTTP Client * @param pem Client key as 0-terminated string in PEM format * * @return 0 for success, error code otherwise. */ int http_client_set_keypem(struct http_cli *cli, const char *pem) { if (!cli || !str_isset(pem)) return EINVAL; cli->key = mem_deref(cli->key); cli->key = mbuf_alloc(strlen(pem)); return mbuf_write_str(cli->key, pem); } /** * Set verify host name * * @param cli HTTP Client * @param hostname String for alternative name validation. * * @return 0 if success, otherwise errorcode */ int http_client_set_tls_hostname(struct http_cli *cli, const struct pl *hostname) { if (!cli) return EINVAL; cli->tlshn = mem_deref(cli->tlshn); if (!hostname) return 0; return pl_strdup(&cli->tlshn, hostname); } /** * Enabled / disable tls session reuse. * Note: disabled per default * * @param cli HTTP Client * @param enabled Pass 1 for enabled, 0 for disabled * * @return 0 if success, otherwise errorcode */ int http_client_set_session_reuse(struct http_cli *cli, bool enabled) { if (!cli || !cli->tls) return EINVAL; return tls_set_session_reuse(cli->tls, enabled); } /** * Set minimum TLS version * * @param cli HTTP Client * @param version Minimum version, e.g.: TLS1_2_VERSION * * @return 0 if success, otherwise errorcode */ int http_client_set_tls_min_version(struct http_cli *cli, int version) { if (!cli || !cli->tls) return EINVAL; return tls_set_min_proto_version(cli->tls, version); } /** * Set maximum TLS version * * @param cli HTTP Client * @param version Maximum version, e.g.: TLS1_2_VERSION * * @return 0 if success, otherwise errorcode */ int http_client_set_tls_max_version(struct http_cli *cli, int version) { if (!cli || !cli->tls) return EINVAL; return tls_set_max_proto_version(cli->tls, version); } /** * Disable TLS server certificate verification * * @param cli HTTP Client * * @return 0 if success, otherwise errorcode */ int http_client_disable_verify_server(struct http_cli *cli) { if (!cli) return EINVAL; tls_disable_verify_server(cli->tls); return 0; } /** * Change used certificate+key of TLS connection * * @param cli HTTP Client * @param chain Cert (chain) + Key (PEM format) * @param len_chain Length of certificate + key PEM string * * @return int 0 if success, otherwise errorcode */ int http_client_use_chainpem(struct http_cli *cli, const char *chain, int len_chain) { if (!cli || !cli->tls) return EINVAL; int err = tls_set_certificate_chain_pem(cli->tls, chain, len_chain); if (err) return err; cli->cert = mem_deref(cli->cert); cli->key = mem_deref(cli->key); return 0; } /** * Change used certificate+key of TLS connection * * @param cli HTTP Client * @param path Path to Cert (chain) + Key file (PEM format) * * @return int 0 if success, otherwise errorcode */ int http_client_use_chain(struct http_cli *cli, const char *path) { int err; if (!cli || !cli->tls) return EINVAL; err = tls_set_certificate_chain(cli->tls, path); if (err) return err; cli->cert = mem_deref(cli->cert); cli->key = mem_deref(cli->key); return 0; } #endif /* USE_TLS */ /** * Send an HTTP request * * @param cli HTTP Client * @param addr Bind to local v4 address * */ void http_client_set_laddr(struct http_cli *cli, const struct sa *addr) { if (cli && addr) sa_cpy(&cli->laddr, addr); } /** * Send an HTTP request * * @param cli HTTP Client * @param addr Bind to local v6 address * */ void http_client_set_laddr6(struct http_cli *cli, const struct sa *addr) { if (cli && addr) sa_cpy(&cli->laddr6, addr); } void http_client_set_bufsize_max(struct http_cli *cli, size_t max_size) { if (!cli) return; cli->bufsize_max = max_size; } size_t http_client_get_bufsize_max(struct http_cli *cli) { if (!cli) return BUFSIZE_MAX; return cli->bufsize_max; } ================================================ FILE: src/http/http.h ================================================ /** * @file http.h HTTP Private Interface * * Copyright (C) 2010 Creytiv.com */ struct http_chunk { size_t size; unsigned lf; bool trailer; bool digit; bool param; }; int http_chunk_decode(struct http_chunk *chunk, struct mbuf *mb, size_t *size); ================================================ FILE: src/http/msg.c ================================================ /** * @file http/msg.c HTTP Message decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include enum { STARTLINE_MAX = 8192, }; static void hdr_destructor(void *arg) { struct http_hdr *hdr = arg; list_unlink(&hdr->le); } static void destructor(void *arg) { struct http_msg *msg = arg; list_flush(&msg->hdrl); mem_deref(msg->_mb); mem_deref(msg->mb); } static enum http_hdrid hdr_hash(const struct pl *name) { if (!name->l) return HTTP_HDR_NONE; switch (name->p[0]) { case 'x': case 'X': if (name->l > 1 && name->p[1] == '-') return HTTP_HDR_NONE; break; } return (enum http_hdrid)(hash_joaat_ci(name->p, name->l) & 0xfff); } static inline bool hdr_comma_separated(enum http_hdrid id) { switch (id) { case HTTP_HDR_ACCEPT: case HTTP_HDR_ACCEPT_CHARSET: case HTTP_HDR_ACCEPT_ENCODING: case HTTP_HDR_ACCEPT_LANGUAGE: case HTTP_HDR_ACCEPT_RANGES: case HTTP_HDR_ALLOW: case HTTP_HDR_CACHE_CONTROL: case HTTP_HDR_CONNECTION: case HTTP_HDR_CONTENT_ENCODING: case HTTP_HDR_CONTENT_LANGUAGE: case HTTP_HDR_EXPECT: case HTTP_HDR_IF_MATCH: case HTTP_HDR_IF_NONE_MATCH: case HTTP_HDR_PRAGMA: case HTTP_HDR_SEC_WEBSOCKET_EXTENSIONS: case HTTP_HDR_SEC_WEBSOCKET_PROTOCOL: case HTTP_HDR_SEC_WEBSOCKET_VERSION: case HTTP_HDR_TE: case HTTP_HDR_TRAILER: case HTTP_HDR_TRANSFER_ENCODING: case HTTP_HDR_UPGRADE: case HTTP_HDR_VARY: case HTTP_HDR_VIA: case HTTP_HDR_WARNING: return true; default: return false; } } static inline int hdr_add(struct http_msg *msg, const struct pl *name, enum http_hdrid id, const char *p, ssize_t l) { struct http_hdr *hdr; int err = 0; hdr = mem_zalloc(sizeof(*hdr), hdr_destructor); if (!hdr) return ENOMEM; hdr->name = *name; hdr->val.p = p; hdr->val.l = MAX(l, 0); hdr->id = id; list_append(&msg->hdrl, &hdr->le, hdr); /* parse common headers */ switch (id) { case HTTP_HDR_CONTENT_TYPE: err = msg_ctype_decode(&msg->ctyp, &hdr->val); break; case HTTP_HDR_CONTENT_LENGTH: msg->clen = pl_u32(&hdr->val); break; default: break; } if (err) mem_deref(hdr); return err; } /** * Decode a HTTP message * * @param msgp Pointer to allocated HTTP Message * @param mb Buffer containing HTTP Message * @param req True for request, false for response * * @return 0 if success, otherwise errorcode */ int http_msg_decode(struct http_msg **msgp, struct mbuf *mb, bool req) { struct pl b, s, e, name, scode; const char *p, *cv; struct http_msg *msg; bool comsep, quote; enum http_hdrid id = HTTP_HDR_NONE; uint32_t ws, lf; size_t l; int err; if (!msgp || !mb) return EINVAL; p = (const char *)mbuf_buf(mb); l = mbuf_get_left(mb); if (re_regex(p, l, "[\r\n]*[^\r\n]+[\r]*[\n]1", &b, &s, NULL, &e)) return (l > STARTLINE_MAX) ? EBADMSG : ENODATA; msg = mem_zalloc(sizeof(*msg), destructor); if (!msg) return ENOMEM; msg->_mb = mem_ref(mb); msg->mb = mbuf_alloc(8192); if (!msg->mb) { err = ENOMEM; goto out; } if (req) { if (re_regex(s.p, s.l, "[a-z]+ [^? ]+[^ ]* HTTP/[0-9.]+", &msg->met, &msg->path, &msg->prm, &msg->ver) || msg->met.p != s.p) { err = EBADMSG; goto out; } } else { if (re_regex(s.p, s.l, "HTTP/[0-9.]+ [0-9]+[ ]*[^]*", &msg->ver, &scode, NULL, &msg->reason) || msg->ver.p != s.p + 5) { err = EBADMSG; goto out; } msg->scode = pl_u32(&scode); } l -= e.p + e.l - p; p = e.p + e.l; name.p = cv = NULL; name.l = ws = lf = 0; comsep = false; quote = false; for (; l > 0; p++, l--) { switch (*p) { case ' ': case '\t': lf = 0; /* folding */ ++ws; break; case '\r': ++ws; break; case '\n': ++ws; if (!name.p) { ++p; --l; /* no headers */ err = 0; goto out; } if (!lf++) break; ++p; --l; /* eoh */ /*@fallthrough@*/ default: if (lf || (*p == ',' && comsep && !quote)) { if (!name.l) { err = EBADMSG; goto out; } err = hdr_add(msg, &name, id, cv ? cv : p, cv ? p - cv - ws : 0); if (err) goto out; if (!lf) { /* comma separated */ cv = NULL; break; } if (lf > 1) { /* eoh */ err = 0; goto out; } comsep = false; name.p = NULL; cv = NULL; lf = 0; } if (!name.p) { name.p = p; name.l = 0; ws = 0; } if (!name.l) { if (*p != ':') { ws = 0; break; } name.l = MAX((int)(p - name.p - ws), 0); if (!name.l) { err = EBADMSG; goto out; } id = hdr_hash(&name); comsep = hdr_comma_separated(id); break; } if (!cv) { quote = false; cv = p; } if (*p == '"') quote = !quote; ws = 0; break; } } err = ENODATA; out: if (err) mem_deref(msg); else { *msgp = msg; mb->pos = mb->end - l; } return err; } /** * Get a HTTP Header from a HTTP Message * * @param msg HTTP Message * @param id HTTP Header ID * * @return HTTP Header if found, NULL if not found */ const struct http_hdr *http_msg_hdr(const struct http_msg *msg, enum http_hdrid id) { return http_msg_hdr_apply(msg, true, id, NULL, NULL); } /** * Apply a function handler to certain HTTP Headers * * @param msg HTTP Message * @param fwd True to traverse forwards, false to traverse backwards * @param id HTTP Header ID * @param h Function handler * @param arg Handler argument * * @return HTTP Header if handler returns true, otherwise NULL */ const struct http_hdr *http_msg_hdr_apply(const struct http_msg *msg, bool fwd, enum http_hdrid id, http_hdr_h *h, void *arg) { struct le *le; if (!msg) return NULL; le = fwd ? msg->hdrl.head : msg->hdrl.tail; while (le) { const struct http_hdr *hdr = le->data; le = fwd ? le->next : le->prev; if (hdr->id != id) continue; if (!h || h(hdr, arg)) return hdr; } return NULL; } /** * Get an unknown HTTP Header from a HTTP Message * * @param msg HTTP Message * @param name Header name * * @return HTTP Header if found, NULL if not found */ const struct http_hdr *http_msg_xhdr(const struct http_msg *msg, const char *name) { return http_msg_xhdr_apply(msg, true, name, NULL, NULL); } /** * Apply a function handler to certain unknown HTTP Headers * * @param msg HTTP Message * @param fwd True to traverse forwards, false to traverse backwards * @param name HTTP Header name * @param h Function handler * @param arg Handler argument * * @return HTTP Header if handler returns true, otherwise NULL */ const struct http_hdr *http_msg_xhdr_apply(const struct http_msg *msg, bool fwd, const char *name, http_hdr_h *h, void *arg) { struct le *le; struct pl pl; if (!msg || !name) return NULL; pl_set_str(&pl, name); le = fwd ? msg->hdrl.head : msg->hdrl.tail; while (le) { const struct http_hdr *hdr = le->data; le = fwd ? le->next : le->prev; if (pl_casecmp(&hdr->name, &pl)) continue; if (!h || h(hdr, arg)) return hdr; } return NULL; } static bool count_handler(const struct http_hdr *hdr, void *arg) { uint32_t *n = arg; (void)hdr; ++(*n); return false; } /** * Count the number of HTTP Headers * * @param msg HTTP Message * @param id HTTP Header ID * * @return Number of HTTP Headers */ uint32_t http_msg_hdr_count(const struct http_msg *msg, enum http_hdrid id) { uint32_t n = 0; http_msg_hdr_apply(msg, true, id, count_handler, &n); return n; } /** * Count the number of unknown HTTP Headers * * @param msg HTTP Message * @param name HTTP Header name * * @return Number of HTTP Headers */ uint32_t http_msg_xhdr_count(const struct http_msg *msg, const char *name) { uint32_t n = 0; http_msg_xhdr_apply(msg, true, name, count_handler, &n); return n; } static bool value_handler(const struct http_hdr *hdr, void *arg) { return 0 == pl_strcasecmp(&hdr->val, (const char *)arg); } /** * Check if a HTTP Header matches a certain value * * @param msg HTTP Message * @param id HTTP Header ID * @param value Header value to check * * @return True if value matches, false if not */ bool http_msg_hdr_has_value(const struct http_msg *msg, enum http_hdrid id, const char *value) { return NULL != http_msg_hdr_apply(msg, true, id, value_handler, (void *)value); } /** * Check if an unknown HTTP Header matches a certain value * * @param msg HTTP Message * @param name HTTP Header name * @param value Header value to check * * @return True if value matches, false if not */ bool http_msg_xhdr_has_value(const struct http_msg *msg, const char *name, const char *value) { return NULL != http_msg_xhdr_apply(msg, true, name, value_handler, (void *)value); } /** * Print a HTTP Message * * @param pf Print function for output * @param msg HTTP Message * * @return 0 if success, otherwise errorcode */ int http_msg_print(struct re_printf *pf, const struct http_msg *msg) { struct le *le; int err; if (!msg) return 0; if (pl_isset(&msg->met)) err = re_hprintf(pf, "%r %r%r HTTP/%r\n", &msg->met, &msg->path, &msg->prm, &msg->ver); else err = re_hprintf(pf, "HTTP/%r %u %r\n", &msg->ver, msg->scode, &msg->reason); for (le=msg->hdrl.head; le; le=le->next) { const struct http_hdr *hdr = le->data; err |= re_hprintf(pf, "%r: %r (%i)\n", &hdr->name, &hdr->val, hdr->id); } return err; } ================================================ FILE: src/http/request.c ================================================ /** * @file http/request.c HTTP request connection * * Supports: * - GET, POST and PUT requests * - basic, digest and token authentication (e.g. bearer) * - TLS * * Copyright (C) 2020 Commend.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "http.h" #define DEBUG_MODULE "reqconn" #define DEBUG_LEVEL 5 #include enum { MAX_RETRIES = 3, }; struct http_reqconn { struct le le; struct http_cli *client; /**< HTTP client */ struct sa peer; /**< Peer address */ struct http_req *req; /**< Current HTTP request */ char *uri; /**< Request URI */ char *met; /**< Request Method */ char *path; /**< Request path/resource */ char *ctype; /**< Content-type */ uint32_t timeout; /**< Timeout for DNS and HTTP */ char *user; /**< Auth user */ char *pass; /**< Auth password */ struct mbuf *body; /**< HTTP body for POST/PUT request */ char *token; /**< Auth token (e.g. bearer token) */ char *tokentype; /**< Auth token type */ struct mbuf *custhdr; /**< Custom HTTP headers */ int retries; /**< Auth retry counter */ http_resp_h *resph; /**< HTTP response handler */ http_data_h *datah; /**< HTTP data handler for downloads */ void *arg; /**< User data pointer for resph and datah */ http_bodyh *bodyh; /**< Handler for the request body */ uint64_t bodyl; /**< Size of body if request handler used */ #ifdef USE_TLS char *tlshn; /**< TLS host name */ #endif }; static void destructor(void *arg) { struct http_reqconn *conn = arg; mem_deref(conn->req); mem_deref(conn->uri); mem_deref(conn->met); mem_deref(conn->path); mem_deref(conn->ctype); mem_deref(conn->user); mem_deref(conn->pass); mem_deref(conn->body); mem_deref(conn->token); mem_deref(conn->tokentype); mem_deref(conn->custhdr); #ifdef USE_TLS mem_deref(conn->tlshn); #endif } static int make_digest_mb(struct mbuf *mb, struct httpauth_digest_chall *digest, struct http_reqconn *conn) { struct httpauth_digest_resp *resp = NULL; int err; err = httpauth_digest_make_response(&resp, digest, conn->path, conn->met, conn->user, conn->pass, conn->body); if (err) return err; err = httpauth_digest_response_encode(resp, mb); mem_deref(resp); return err; } static int make_token_mb(struct mbuf *mb, const struct http_reqconn *conn) { int err; const char auth[] = "Authorization: "; const char defaulttype[] = "Bearer"; if (!conn || !mb) return EINVAL; if (!str_isset(conn->token) || !mb) return EINVAL; err = mbuf_resize(mb, strlen(conn->token) + sizeof(auth) + strlen(conn->tokentype ? conn->tokentype : defaulttype) + 1); if (err) return err; err = mbuf_write_str(mb, auth); if (conn->tokentype) err |= mbuf_write_str(mb, conn->tokentype); else err |= mbuf_write_str(mb, defaulttype); err |= mbuf_write_str(mb, " "); err |= mbuf_write_str(mb, conn->token); mbuf_set_pos(mb, 0); return err; } static int make_basic_mb(struct mbuf *mb, struct http_reqconn *conn) { int err; struct httpauth_basic *basic; if (!conn || !mb) return EINVAL; basic = httpauth_basic_alloc(); if (!basic) return ENOMEM; err = httpauth_basic_make_response(basic, conn->user, conn->pass); if (err) goto out; err = httpauth_basic_encode(basic, mb); out: mem_deref(basic); return err; } static int send_req(struct http_reqconn *conn, const struct pl *auth); static void resp_handler(int err, const struct http_msg *msg, void *arg) { struct http_reqconn *conn = arg; const struct http_hdr *hdr; struct httpauth_digest_chall digest; struct httpauth_basic *basic = NULL; struct pl auth; struct mbuf *abuf = NULL; if (!conn) return; if (!msg) { DEBUG_INFO("no http_msg (%m)\n", err); goto disconnect; } else { DEBUG_INFO("scode=%u (%m)\n", msg->scode, err); } if (err || (msg->scode != 401 && msg->scode != 403)) goto disconnect; hdr = http_msg_hdr(msg, HTTP_HDR_WWW_AUTHENTICATE); if (!hdr) goto disconnect; conn->retries++; if (conn->retries > MAX_RETRIES) { err = EAUTH; DEBUG_INFO("not authorized\n"); goto disconnect; } if (httpauth_digest_challenge_decode(&digest, &hdr->val)) { /* It's not digest. Now try basic. */ basic = httpauth_basic_alloc(); if (!basic) { err = ENOMEM; goto disconnect; } if (httpauth_basic_decode(basic, &hdr->val)) { err = EBADMSG; goto disconnect; } } abuf = mbuf_alloc(1); if (!abuf) { err = ENOMEM; goto disconnect; } if (pl_isset(&digest.nonce)) err = make_digest_mb(abuf, &digest, conn); else if (basic && pl_isset(&basic->realm)) err = make_basic_mb(abuf, conn); else err = EBADMSG; if (err) { DEBUG_WARNING("Authentication failed (%m)\n", err); goto disconnect; } pl_set_mbuf(&auth, abuf); mbuf_set_pos(conn->body, 0); err = send_req(conn, &auth); if (err) goto disconnect; goto out; disconnect: if (conn && conn->resph) conn->resph(err, msg, conn->arg); out: mem_deref(abuf); mem_deref(basic); mem_deref(conn); } static int data_handler(const uint8_t *buf, size_t size, const struct http_msg *msg, void *arg) { struct http_reqconn *conn = arg; if (!conn) return EINVAL; if (!conn->datah) return 0; return conn->datah(buf, size, msg, conn->arg); } static size_t req_body_handler(struct mbuf *mb, void *arg) { struct http_reqconn *conn = arg; size_t len = 0; if (!mb) return 0; if (conn->bodyh) { len = conn->bodyh(mb, conn->arg); } else if (conn->body) { len = min(mbuf_get_left(conn->body), http_client_get_bufsize_max(conn->client)); if (!len) return len; mbuf_write_mem(mb, mbuf_buf(conn->body), len); mbuf_advance(conn->body, len); } return len; } static int send_req(struct http_reqconn *conn, const struct pl *auth) { int err; struct mbuf *ctbuf = NULL; struct mbuf *clbuf = NULL; struct pl ct = PL_INIT; struct pl cl = PL_INIT; struct pl custh = PL_INIT; #if (DEBUG_LEVEL >= 7) struct pl dbg; #endif if (!conn) return EINVAL; if (conn->body || conn->bodyh) { clbuf = mbuf_alloc(22); if (!clbuf) return ENOMEM; if (conn->bodyh) mbuf_printf(clbuf, "Content-Length: %llu\r\n", conn->bodyl); else mbuf_printf(clbuf, "Content-Length: %zu\r\n", mbuf_get_left(conn->body)); mbuf_set_pos(clbuf, 0); pl_set_mbuf(&cl, clbuf); } if (conn->ctype) { ctbuf = mbuf_alloc(17 + strlen(conn->ctype)); mbuf_printf(ctbuf, "Content-Type: %s\r\n", conn->ctype); mbuf_set_pos(ctbuf, 0); pl_set_mbuf(&ct, ctbuf); } DEBUG_INFO("send %s uri=%s path=%s len=%zu %s auth.\n", conn->met, conn->uri, conn->path, mbuf_get_left(conn->body), auth ? "with" : "without"); if (auth) { DEBUG_INFO("auth=|%r|\n", auth); } #if (DEBUG_LEVEL >= 7) if (conn->body) { pl_set_mbuf(&dbg, conn->body); DEBUG_PRINTF("postdata:\n%r\n", &dbg); } #endif if (conn->custhdr) pl_set_mbuf(&custh, conn->custhdr); err = http_request_addr(&conn->req, conn->client, conn->met, conn->uri, sa_isset(&conn->peer, SA_ADDR) ? &conn->peer : NULL, resp_handler, conn->datah ? data_handler : NULL, (conn->bodyh || conn->body) ? req_body_handler : NULL, conn, "%r%s" "User-Agent: re " RE_VERSION "\r\n" "%r" "%r" "%r" "\r\n", auth, auth ? "\r\n" : "", &ct, &custh, &cl); mem_deref(clbuf); mem_deref(ctbuf); if (err) { DEBUG_WARNING("Could not send %s request. (%m)\n", conn->met, err); return err; } /* keep internal reference for resp_handler */ mem_ref(conn); return 0; } static int send_auth_token(struct http_reqconn *conn) { struct pl auth; int err = 0; struct mbuf *mb = mbuf_alloc(1); if (!mb) { err = ENOMEM; goto out; } err = make_token_mb(mb, conn); if (err) goto out; pl_set_mbuf(&auth, mb); err = send_req(conn, &auth); out: mem_deref(mb); return err; } /** * Allocates a new http_reqconn instance. Has to be freed after usage with * mem_deref(). * * @param pconn A pointer for returning the new http_reqconn. * @param client The HTTP client. Multiple parallel HTTP request with the same * HTTP client are possible. * @param resph The optional response handler. * @param datah The optional data handler. This is useful for downloading * large files. * @param arg A pointer that will be passed to resph and datah. * * @return 0 if success, otherwise errorcode */ int http_reqconn_alloc(struct http_reqconn **pconn, struct http_cli *client, http_resp_h *resph, http_data_h *datah, void* arg) { struct http_reqconn *conn = NULL; int err; struct pl pl = PL("GET"); if (!pconn || !client) return EINVAL; conn = mem_zalloc(sizeof(*conn), destructor); if (!conn) return ENOMEM; conn->client = client; conn->resph = resph; conn->datah = datah; conn->arg = arg; err = http_reqconn_set_method(conn, &pl); if (err) conn = mem_deref(conn); *pconn = conn; return err; } int http_reqconn_set_auth(struct http_reqconn *conn, const struct pl *user, const struct pl *pass) { int err = 0; if (!conn) return EINVAL; conn->user = mem_deref(conn->user); conn->pass = mem_deref(conn->pass); if (pl_isset(user)) err |= pl_strdup(&conn->user, user); if (pl_isset(pass)) err |= pl_strdup(&conn->pass, pass); return err; } int http_reqconn_set_bearer(struct http_reqconn *conn, const struct pl *bearer) { conn->tokentype = mem_deref(conn->tokentype); return http_reqconn_set_authtoken(conn, bearer); } int http_reqconn_set_authtoken(struct http_reqconn *conn, const struct pl *token) { if (!conn) return EINVAL; conn->token = mem_deref(conn->token); if (!pl_isset(token)) return 0; return pl_strdup(&conn->token, token); } int http_reqconn_set_tokentype(struct http_reqconn *conn, const struct pl *tokentype) { if (!conn) return EINVAL; conn->tokentype = mem_deref(conn->tokentype); if (!pl_isset(tokentype)) return 0; return pl_strdup(&conn->tokentype, tokentype); } int http_reqconn_set_method(struct http_reqconn *conn, const struct pl *met) { if (!conn) return EINVAL; conn->met = mem_deref(conn->met); return pl_strdup(&conn->met, met); } int http_reqconn_set_body(struct http_reqconn *conn, struct mbuf *body) { if (!conn || !body) return EINVAL; conn->body = mbuf_alloc_ref(body); if (!conn->body) return ENOMEM; mbuf_set_pos(conn->body, 0); conn->bodyl = mbuf_get_left(conn->body); return 0; } int http_reqconn_set_ctype(struct http_reqconn *conn, const struct pl *ctype) { if (!conn) return EINVAL; conn->ctype = mem_deref(conn->ctype); if (!pl_isset(ctype)) return 0; return pl_strdup(&conn->ctype, ctype); } int http_reqconn_add_header(struct http_reqconn *conn, const struct pl *header) { int err; if (!conn) return EINVAL; if (!pl_isset(header)) return 0; if (!conn->custhdr) conn->custhdr = mbuf_alloc(8); if (!conn->custhdr) return ENOMEM; err = mbuf_write_pl(conn->custhdr, header); err |= mbuf_write_str(conn->custhdr, "\r\n"); if (err) conn->custhdr = mem_deref(conn->custhdr); return err; } int http_reqconn_clr_header(struct http_reqconn *conn) { if (!conn) return EINVAL; conn->custhdr = mem_deref(conn->custhdr); return 0; } #ifdef USE_TLS int http_reqconn_set_tls_hostname(struct http_reqconn *conn, const struct pl *hostname) { if (!conn) return EINVAL; conn->tlshn = mem_deref(conn->tlshn); if (!pl_isset(hostname)) return 0; return pl_strdup(&conn->tlshn, hostname); } #endif /** * Set explicit peer address for next request (bypasses DNS) * * If set, the TCP connection goes to this address while * the URI's hostname is still used for the Host header and * TLS SNI. Useful for routing through a local proxy. * * @param conn HTTP request connection * @param peer Peer address (NULL to clear) * * @return 0 if success, otherwise errorcode */ int http_reqconn_set_peer(struct http_reqconn *conn, const struct sa *peer) { if (!conn) return EINVAL; if (peer) conn->peer = *peer; else memset(&conn->peer, 0, sizeof(conn->peer)); return 0; } int http_reqconn_send(struct http_reqconn *conn, const struct pl *uri) { int err; struct http_uri hu; char *host = NULL; #ifdef USE_TLS struct pl tlshn; #endif if (!conn || !pl_isset(uri)) return EINVAL; err = http_uri_decode(&hu, uri); if (err) { DEBUG_WARNING("http uri %r decode error (%m)\n", uri, err); return EINVAL; } conn->uri = mem_deref(conn->uri); conn->path = mem_deref(conn->path); err |= pl_strdup(&conn->uri, uri); err |= pl_strdup(&conn->path, &hu.path); err |= pl_strdup(&host, &hu.host); if (err) return err; #ifdef USE_TLS if (conn->tlshn) { pl_set_str(&tlshn, conn->tlshn); err = http_client_set_tls_hostname(conn->client, &tlshn); } if (err) { DEBUG_WARNING("Could not set TLS hostname.\n"); mem_deref(host); return err; } #endif mem_deref(host); if (conn->custhdr) mbuf_set_pos(conn->custhdr, 0); conn->retries = 0; if (conn->token) err = send_auth_token(conn); else err = send_req(conn, NULL); return err; } int http_reqconn_set_req_bodyh(struct http_reqconn *conn, http_bodyh cb, uint64_t len) { int err = 0; if (!conn) return EINVAL; conn->bodyh = cb; conn->bodyl = len; return err; } ================================================ FILE: src/http/server.c ================================================ /** * @file http/server.c HTTP Server * * Copyright (C) 2011 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include enum { TIMEOUT_IDLE = 600000, TIMEOUT_INIT = 10000, BUFSIZE_MAX = 1024 * 1024 * 1, /* 1 MB */ }; struct http_sock { struct list connl; struct tcp_sock *ts; struct tls *tls; http_req_h *reqh; https_verify_msg_h *verifyh; size_t max_body_size; void *arg; }; struct http_conn { struct le le; struct tmr tmr; struct sa peer; struct http_sock *sock; struct tcp_conn *tc; struct tls_conn *sc; struct mbuf *mb; struct http_verify_msg_d *verify_msg_d; }; static void conn_close(struct http_conn *conn); static void sock_destructor(void *arg) { struct http_sock *sock = arg; struct le *le; for (le=sock->connl.head; le;) { struct http_conn *conn = le->data; le = le->next; conn_close(conn); mem_deref(conn); } mem_deref(sock->tls); mem_deref(sock->ts); } static void conn_destructor(void *arg) { struct http_conn *conn = arg; list_unlink(&conn->le); tmr_cancel(&conn->tmr); mem_deref(conn->verify_msg_d); mem_deref(conn->sc); mem_deref(conn->tc); mem_deref(conn->mb); } static void conn_close(struct http_conn *conn) { list_unlink(&conn->le); tmr_cancel(&conn->tmr); conn->verify_msg_d = mem_deref(conn->verify_msg_d); conn->sc = mem_deref(conn->sc); conn->tc = mem_deref(conn->tc); conn->sock = NULL; } static void timeout_handler(void *arg) { struct http_conn *conn = arg; conn_close(conn); mem_deref(conn); } #ifdef HAVE_TLS1_3_POST_HANDSHAKE_AUTH struct http_verify_msg_d { struct http_conn *conn; struct http_msg *msg; int err; int scode; const char *reason; struct tmr tmr; }; static void verify_msg_destructor(void *arg) { struct http_verify_msg_d *d = arg; tmr_cancel(&d->tmr); mem_deref(d->msg); } static void verify_cert_done(void *arg) { struct http_verify_msg_d *d = arg; struct http_conn *conn = d->conn; if (d->err) http_ereply(conn, d->scode, d->reason); else conn->sock->reqh(conn, d->msg, conn->sock->arg); conn->verify_msg_d = mem_deref(d); } static int http_verify_handler(int ok, void *arg) { struct http_verify_msg_d *d = arg; if (ok) { d->err = 0; } else { d->err = EACCES; d->scode = 403; d->reason = "Forbidden"; } tmr_start(&d->tmr, 1, verify_cert_done, d); return ok; } static enum re_https_verify_msg verify_msg(struct http_conn *conn, struct http_msg *msg) { enum re_https_verify_msg res; struct http_verify_msg_d *d; if (!conn->sock) return HTTPS_MSG_IGNORE; else if (!conn->sock->verifyh) return HTTPS_MSG_OK; res = conn->sock->verifyh(conn, msg, conn->sock->arg); if (res == HTTPS_MSG_REQUEST_CERT) { d = mem_zalloc(sizeof(*d), verify_msg_destructor); if (!d) { res = HTTPS_MSG_IGNORE; goto out; } d->conn = conn; d->err = ETIMEDOUT; d->scode = 408; d->reason = "Request Timeout"; d->msg = msg; mem_deref(conn->verify_msg_d); conn->verify_msg_d = d; tmr_start(&d->tmr, TIMEOUT_IDLE, verify_cert_done, d); int err = tls_set_verify_client_handler(http_conn_tls(conn), -1, http_verify_handler, d); if (err) { res = HTTPS_MSG_IGNORE; goto out; } err = tls_verify_client_post_handshake( http_conn_tls(conn)); if (err) { res = HTTPS_MSG_IGNORE; goto out; } } out: return res; } #endif static void recv_handler(struct mbuf *mb, void *arg) { struct http_conn *conn = arg; int err = 0; if (conn->mb) { const size_t len = mbuf_get_left(mb), pos = conn->mb->pos; if ((mbuf_get_left(conn->mb) + len) > conn->sock->max_body_size) { err = EOVERFLOW; goto out; } conn->mb->pos = conn->mb->end; err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len); if (err) goto out; conn->mb->pos = pos; } else { conn->mb = mem_ref(mb); } while (conn->mb) { size_t end, pos = conn->mb->pos; struct http_msg *msg; err = http_msg_decode(&msg, conn->mb, true); if (err) { if (err == ENODATA) { conn->mb->pos = pos; err = 0; break; } goto out; } if (mbuf_get_left(conn->mb) < msg->clen) { conn->mb->pos = pos; mem_deref(msg); break; } mem_deref(msg->mb); msg->mb = mem_ref(msg->_mb); mb = conn->mb; end = mb->end; mb->end = mb->pos + msg->clen; if (end > mb->end) { struct mbuf *mbn = mbuf_alloc(end - mb->end); if (!mbn) { mem_deref(msg); err = ENOMEM; goto out; } (void)mbuf_write_mem(mbn, mb->buf + mb->end, end - mb->end); mbn->pos = 0; mem_deref(conn->mb); conn->mb = mbn; } else { conn->mb = mem_deref(conn->mb); } #ifdef HAVE_TLS1_3_POST_HANDSHAKE_AUTH if (verify_msg(conn, msg) == HTTPS_MSG_OK) { conn->sock->reqh(conn, msg, conn->sock->arg); mem_deref(msg); } #else conn->sock->reqh(conn, msg, conn->sock->arg); mem_deref(msg); #endif if (!conn->tc) { err = ENOTCONN; goto out; } tmr_start(&conn->tmr, TIMEOUT_IDLE, timeout_handler, conn); } out: if (err) { conn_close(conn); mem_deref(conn); } } static void close_handler(int err, void *arg) { struct http_conn *conn = arg; (void)err; conn_close(conn); mem_deref(conn); } static void connect_handler(const struct sa *peer, void *arg) { struct http_sock *sock = arg; struct http_conn *conn; int err; conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) { err = ENOMEM; goto out; } list_append(&sock->connl, &conn->le, conn); conn->peer = *peer; conn->sock = sock; err = tcp_accept(&conn->tc, sock->ts, NULL, recv_handler, close_handler, conn); if (err) goto out; #ifdef USE_TLS if (sock->tls) { err = tls_start_tcp(&conn->sc, sock->tls, conn->tc, 0); if (err) goto out; } #endif tmr_start(&conn->tmr, TIMEOUT_INIT, timeout_handler, conn); out: if (err) { mem_deref(conn); tcp_reject(sock->ts); } } /** * Create an HTTP socket from file descriptor * * @param sockp Pointer to returned HTTP Socket * @param fd File descriptor * @param reqh Request handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int http_listen_fd(struct http_sock **sockp, re_sock_t fd, http_req_h *reqh, void *arg) { struct http_sock *sock; int err; if (!sockp || fd == RE_BAD_SOCK || !reqh) return EINVAL; sock = mem_zalloc(sizeof(*sock), sock_destructor); if (!sock) return ENOMEM; err = tcp_sock_alloc_fd(&sock->ts, fd, connect_handler, sock); if (err) goto out; sock->reqh = reqh; sock->arg = arg; sock->max_body_size = BUFSIZE_MAX; out: if (err) mem_deref(sock); else *sockp = sock; return err; } /** * Create an HTTP socket * * @param sockp Pointer to returned HTTP Socket * @param laddr Network address to listen on * @param reqh Request handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int http_listen(struct http_sock **sockp, const struct sa *laddr, http_req_h *reqh, void *arg) { struct http_sock *sock; int err; if (!sockp || !laddr || !reqh) return EINVAL; sock = mem_zalloc(sizeof(*sock), sock_destructor); if (!sock) return ENOMEM; err = tcp_listen(&sock->ts, laddr, connect_handler, sock); if (err) goto out; sock->reqh = reqh; sock->arg = arg; sock->max_body_size = BUFSIZE_MAX; out: if (err) mem_deref(sock); else *sockp = sock; return err; } /** * Create an HTTP secure socket * * @param sockp Pointer to returned HTTP Socket * @param laddr Network address to listen on * @param cert File path of TLS certificate * @param reqh Request handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int https_listen(struct http_sock **sockp, const struct sa *laddr, const char *cert, http_req_h *reqh, void *arg) { struct http_sock *sock; int err; if (!sockp || !laddr || !cert || !reqh) return EINVAL; err = http_listen(&sock, laddr, reqh, arg); if (err) return err; #ifdef USE_TLS err = tls_alloc(&sock->tls, TLS_METHOD_SSLV23, cert, NULL); #else err = EPROTONOSUPPORT; #endif if (err) goto out; out: if (err) mem_deref(sock); else *sockp = sock; return err; } /** * Set verify http msg handler. (Needs TLS v1.3 post-handshake auth) * * This handler allows to decide whether e.g. a certificate * should be requested from the client or not. * E.g. This decision can done based on the http path contained in * struct http_msg. * * @param sock HTTP socket * @param verifyh Verify handler called before the https request * handler is called to return a http response * to the client. * * @return 0 if success, otherwise errorcode */ int https_set_verify_msgh(struct http_sock *sock, https_verify_msg_h *verifyh) { #ifdef HAVE_TLS1_3_POST_HANDSHAKE_AUTH if (!sock || !verifyh) return EINVAL; sock->verifyh = verifyh; return 0; #else (void)sock; (void)verifyh; return ENOTSUP; #endif } /** * Set Request buffer size limit * * @param sock HTTP socket * @param limit New limit in bytes */ void http_set_max_body_size(struct http_sock *sock, size_t limit) { if (!sock) return; sock->max_body_size = limit; } /** * Get the TCP socket of an HTTP socket * * @param sock HTTP socket * * @return TCP socket */ struct tcp_sock *http_sock_tcp(struct http_sock *sock) { return sock ? sock->ts : NULL; } /** * Get the TLS struct of an HTTP sock * * @param sock HTTP socket * * @return TLS struct */ struct tls *http_sock_tls(const struct http_sock *sock) { return sock ? sock->tls : NULL; } /** * Get the peer address of an HTTP connection * * @param conn HTTP connection * * @return Peer address */ const struct sa *http_conn_peer(const struct http_conn *conn) { return conn ? &conn->peer : NULL; } /** * Get the TCP connection of an HTTP connection * * @param conn HTTP connection * * @return TCP connection */ struct tcp_conn *http_conn_tcp(struct http_conn *conn) { return conn ? conn->tc : NULL; } /** * Get the TLS connection of an HTTP connection * * @param conn HTTP connection * * @return TLS connection */ struct tls_conn *http_conn_tls(struct http_conn *conn) { return conn ? conn->sc : NULL; } /** * Reset IDLE Timeout of an HTTP Connection * * @param conn HTTP connection */ void http_conn_reset_timeout(struct http_conn *conn) { tmr_start(&conn->tmr, TIMEOUT_IDLE, timeout_handler, conn); } /** * Close the HTTP connection * * @param conn HTTP connection */ void http_conn_close(struct http_conn *conn) { if (!conn) return; conn->sc = mem_deref(conn->sc); conn->tc = mem_deref(conn->tc); } static int http_vreply(struct http_conn *conn, uint16_t scode, const char *reason, const char *fmt, va_list ap) { struct mbuf *mb; int err; if (!conn || !scode || !reason) return EINVAL; if (!conn->tc) return ENOTCONN; mb = mbuf_alloc(8192); if (!mb) return ENOMEM; err = mbuf_printf(mb, "HTTP/1.1 %u %s\r\n", scode, reason); if (fmt) err |= mbuf_vprintf(mb, fmt, ap); else err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n"); if (err) goto out; mb->pos = 0; err = tcp_send(conn->tc, mb); if (err) goto out; out: mem_deref(mb); return err; } /** * Send an HTTP response * * @param conn HTTP connection * @param scode Response status code * @param reason Response reason phrase * @param fmt Formatted HTTP message * * @return 0 if success, otherwise errorcode */ int http_reply(struct http_conn *conn, uint16_t scode, const char *reason, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = http_vreply(conn, scode, reason, fmt, ap); va_end(ap); return err; } /** * Send an HTTP response with content formatting * * @param conn HTTP connection * @param scode Response status code * @param reason Response reason phrase * @param ctype Content type * @param fmt Formatted HTTP content * * @return 0 if success, otherwise errorcode */ int http_creply(struct http_conn *conn, uint16_t scode, const char *reason, const char *ctype, const char *fmt, ...) { struct mbuf *mb; va_list ap; int err; if (!ctype || !fmt) return EINVAL; mb = mbuf_alloc(8192); if (!mb) return ENOMEM; va_start(ap, fmt); err = mbuf_vprintf(mb, fmt, ap); va_end(ap); if (err) goto out; err = http_reply(conn, scode, reason, "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "\r\n" "%b", ctype, mb->end, mb->buf, mb->end); if (err) goto out; out: mem_deref(mb); return err; } /** * Send an HTTP error response * * @param conn HTTP connection * @param scode Response status code * @param reason Response reason phrase * * @return 0 if success, otherwise errorcode */ int http_ereply(struct http_conn *conn, uint16_t scode, const char *reason) { return http_creply(conn, scode, reason, "text/html", "\n" "\n" "%u %s\n" "

%u %s

\n" "\n", scode, reason, scode, reason); } ================================================ FILE: src/httpauth/basic.c ================================================ /** * @file basic.c HTTP Basic authentication * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #define DEBUG_MODULE "httpauth_basic" #define DEBUG_LEVEL 5 #include static void httpauth_basic_destr(void *arg) { struct httpauth_basic *basic = arg; mem_deref(basic->mb); } struct httpauth_basic *httpauth_basic_alloc(void) { struct httpauth_basic *basic = mem_zalloc(sizeof(*basic), httpauth_basic_destr); if (!basic) DEBUG_WARNING("could not allocate httpauth_basic\n"); return basic; } /** * Decode a Basic response * * @param basic Basic response object * @param hval Header value to decode from * * @return 0 if successfully decoded, otherwise errorcode */ int httpauth_basic_decode(struct httpauth_basic *basic, const struct pl *hval) { if (!basic || !hval) return EINVAL; if (re_regex(hval->p, hval->l, "[ \t\r\n]*Basic[ \t\r\n]+realm[ \t\r\n]*=[ \t\r\n]*" "[~ \t\r\n,]*", NULL, NULL, NULL, NULL, &basic->realm) || !pl_isset(&basic->realm)) return EBADMSG; return 0; } int httpauth_basic_make_response(struct httpauth_basic *basic, const char *user, const char *pwd) { uint8_t *in; char *out; size_t si, so; size_t poso; int err; if (!basic || !user || !pwd) return EINVAL; si = strlen(user) + strlen(pwd) + 1; so = 4 * (si + 2) / 3; basic->mb = mbuf_alloc(si + so + 1); if (!basic->mb) return ENOMEM; err = mbuf_printf(basic->mb, "%s:%s", user, pwd); poso = basic->mb->pos; err |= mbuf_fill(basic->mb, 0, so + 1); if (err) goto fault; mbuf_set_pos(basic->mb, 0); in = mbuf_buf(basic->mb); mbuf_set_pos(basic->mb, poso); out = (char*) mbuf_buf(basic->mb); err = base64_encode(in, si, out, &so); if (err) goto fault; pl_set_str(&basic->auth, out); return 0; fault: mem_deref(basic->mb); return err; } int httpauth_basic_encode(const struct httpauth_basic *basic, struct mbuf *mb) { int err; if (!basic || !mb || !pl_isset(&basic->auth)) return EINVAL; err = mbuf_resize(mb, basic->auth.l + 21); if (err) return err; err = mbuf_write_str(mb, "Authorization: Basic "); err |= mbuf_write_pl(mb, &basic->auth); if (err) return err; mbuf_set_pos(mb, 0); return 0; } /* HTTPAUTH BASIC REQUESTS*/ static void httpauth_basic_request_destructor(void *arg) { struct httpauth_basic_req *req = arg; mem_deref(req->realm); mem_deref(req->charset); } int httpauth_basic_request_print(struct re_printf *pf, const struct httpauth_basic_req *req) { int err = 0; if (!pf || !req) return EINVAL; err = re_hprintf(pf, "Basic realm=\"%s\"", req->realm); if (str_isset(req->charset)) err |= re_hprintf(pf, ", charset=\"%s\"", req->charset); return err; } /** * Verify received credentials * * @param hval http authentication header value containing the credentials * @param user user name (may be an UTF-8 string) * @param passwd user password (may be an UTF-8 string) * * @return 0 if successfully verified, otherwise errorcode */ int httpauth_basic_verify(const struct pl *hval, const char *user, const char *passwd) { struct pl b64c = PL_INIT; struct mbuf *mb = NULL; char *c = NULL; size_t clen = 0; int err = 0; if (!hval || !user || !passwd) return EINVAL; mb = mbuf_alloc(str_len(user) + str_len(passwd) + 1); if (!mb) return ENOMEM; if (re_regex(hval->p, hval->l, "[ \t\r\n]*Basic[ \t\r\n]+[~ \t\r\n]*", NULL, NULL, &b64c) || !pl_isset(&b64c)) { err = EBADMSG; goto out; } clen = b64c.l; c = mem_zalloc(clen, NULL); if (!c) { err = ENOMEM; goto out; } err = base64_decode(b64c.p, b64c.l, (uint8_t *) c, &clen); if (err) goto out; err = mbuf_printf(mb, "%s:%s", user, passwd); if (err) goto out; if (mem_seccmp(mb->buf, (uint8_t *)c, clen) != 0) err = EACCES; out: if (c) mem_secclean(c, clen); if (mb) mem_secclean(mb->buf, mb->size); mem_deref(c); mem_deref(mb); return err; } /** * Create a Basic Authentication Request * * @param preq httpauth_basic_req object ptr * @param realm realm * @param charset optional charset * * @return 0 if successful, otherwise errorcode */ int httpauth_basic_request(struct httpauth_basic_req **preq, const char *realm, const char *charset) { struct httpauth_basic_req *req = NULL; int err = 0; if (!preq || !realm) return EINVAL; req = mem_zalloc(sizeof(*req), httpauth_basic_request_destructor); if (!req) return ENOMEM; err = str_dup(&req->realm, realm); if (str_isset(charset) && str_casecmp(charset, "UTF-8") == 0) err |= str_dup(&req->charset, charset); if (err) mem_deref(req); else *preq = req; return err; } ================================================ FILE: src/httpauth/digest.c ================================================ /** * @file digest.c HTTP Digest authentication (RFC 2617) - obsolete * HTTP Digest authentication (RFC 7616) - wip * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include typedef void (digest_decode_h)(const struct pl *name, const struct pl *val, void *arg); /* General fields */ static const struct pl param_realm = PL("realm"); static const struct pl param_nonce = PL("nonce"); static const struct pl param_opaque = PL("opaque"); static const struct pl param_algorithm = PL("algorithm"); static const struct pl param_qop = PL("qop"); static const struct pl param_stale = PL("stale"); /* Challenge fields */ static const struct pl param_domain = PL("domain"); /* Response fields */ static const struct pl param_response = PL("response"); static const struct pl param_uri = PL("uri"); static const struct pl param_username = PL("username"); /* static const struct pl param_userstar = PL("username*"); future use */ static const struct pl param_cnonce = PL("cnonce"); static const struct pl param_nc = PL("nc"); /* Optional fields */ static const struct pl param_charset = PL("charset"); static const struct pl param_userhash = PL("userhash"); static void challenge_decode(const struct pl *name, const struct pl *val, void *arg) { struct httpauth_digest_chall *chall = arg; if (!pl_casecmp(name, ¶m_realm)) chall->realm = *val; else if (!pl_casecmp(name, ¶m_domain)) chall->domain = *val; else if (!pl_casecmp(name, ¶m_nonce)) chall->nonce = *val; else if (!pl_casecmp(name, ¶m_opaque)) chall->opaque= *val; else if (!pl_casecmp(name, ¶m_stale)) chall->stale = *val; else if (!pl_casecmp(name, ¶m_algorithm)) chall->algorithm = *val; else if (!pl_casecmp(name, ¶m_qop)) chall->qop = *val; else if (!pl_casecmp(name, ¶m_charset)) chall->charset = *val; else if (!pl_casecmp(name, ¶m_userhash)) chall->userhash = *val; } static void algorithm_decode(struct httpauth_digest_resp *resp, const struct pl *val) { resp->algorithm = *val; if (pl_strstr(val, "SHA-256")) { resp->hashh = &sha256; resp->hash_length = SHA256_DIGEST_LENGTH; } else { resp->hashh = &md5; resp->hash_length = MD5_SIZE; } } static void response_decode(const struct pl *name, const struct pl *val, void *arg) { struct httpauth_digest_resp *resp = arg; if (!pl_casecmp(name, ¶m_realm)) resp->realm = *val; else if (!pl_casecmp(name, ¶m_nonce)) resp->nonce = *val; else if (!pl_casecmp(name, ¶m_response)) resp->response = *val; else if (!pl_casecmp(name, ¶m_username)) resp->username = *val; else if (!pl_casecmp(name, ¶m_uri)) resp->uri = *val; else if (!pl_casecmp(name, ¶m_nc)) resp->nc = *val; else if (!pl_casecmp(name, ¶m_cnonce)) resp->cnonce = *val; else if (!pl_casecmp(name, ¶m_qop)) resp->qop = *val; else if (!pl_casecmp(name, ¶m_algorithm)) algorithm_decode(resp, val); else if (!pl_casecmp(name, ¶m_charset)) resp->charset = *val; else if (!pl_casecmp(name, ¶m_userhash)) resp->userhash = *val; } static int digest_decode(const struct pl *hval, digest_decode_h *dech, void *arg) { struct pl r = *hval, start, end, name, val; if (re_regex(r.p, r.l, "[ \t\r\n]*Digest[ \t\r\n]+", &start, &end) || start.p != r.p) return EBADMSG; pl_advance(&r, end.p - r.p); while (!re_regex(r.p, r.l, "[ \t\r\n,]+[a-z]+[ \t\r\n]*=[ \t\r\n]*[~ \t\r\n,]*", NULL, &name, NULL, NULL, &val)) { pl_advance(&r, val.p + val.l - r.p); dech(&name, &val, arg); } return 0; } static void response_destructor(void *data) { struct httpauth_digest_resp *resp = data; mem_deref(resp->mb); } /** * Decode a Digest challenge * * @param chall Digest challenge object to decode into * @param hval Header value to decode from * * @return 0 if successfully decoded, otherwise errorcode */ int httpauth_digest_challenge_decode(struct httpauth_digest_chall *chall, const struct pl *hval) { int err; if (!chall || !hval) return EINVAL; memset(chall, 0, sizeof(*chall)); err = digest_decode(hval, challenge_decode, chall); if (err) return err; if (!chall->realm.p || !chall->nonce.p) return EBADMSG; return 0; } /** * Decode a Digest response * * @param resp Digest response object to decode into * @param hval Header value to decode from * * @return 0 if successfully decoded, otherwise errorcode */ int httpauth_digest_response_decode(struct httpauth_digest_resp *resp, const struct pl *hval) { int err; if (!resp || !hval) return EINVAL; memset(resp, 0, sizeof(*resp)); err = digest_decode(hval, response_decode, resp); if (err) return err; if (!resp->realm.p || !resp->nonce.p || !resp->response.p || !resp->username.p || !resp->uri.p) return EBADMSG; return 0; } /** * Authenticate a digest response * * @param resp Digest response * @param method Request method * @param ha1 HA1 value from MD5(username:realm:password) * * @return 0 if successfully authenticated, otherwise errorcode */ int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp, const struct pl *method, const uint8_t *ha1) { uint8_t ha2[MD5_SIZE], digest[MD5_SIZE], response[MD5_SIZE]; const char *p; uint32_t i; int err; if (!resp || !method || !ha1) return EINVAL; if (resp->response.l != 32) return EAUTH; err = md5_printf(ha2, "%r:%r", method, &resp->uri); if (err) return err; if (pl_isset(&resp->qop)) err = md5_printf(digest, "%w:%r:%r:%r:%r:%w", ha1, (size_t)MD5_SIZE, &resp->nonce, &resp->nc, &resp->cnonce, &resp->qop, ha2, sizeof(ha2)); else err = md5_printf(digest, "%w:%r:%w", ha1, (size_t)MD5_SIZE, &resp->nonce, ha2, sizeof(ha2)); if (err) return err; for (i=0, p=resp->response.p; irealm = chall->realm; resp->nonce = chall->nonce; pl_set_str(&resp->username, user); pl_set_str(&resp->uri, path); resp->qop = chall->qop; err = mbuf_printf(mb, "%x", re_atomic_rlx(&nc)); err |= mbuf_write_u8(mb, 0); if (err) goto out; /* Client nonce should change, so we use random value. */ cnonce = rand_u32(); p1 = mb->pos; err = mbuf_printf(mb, "%x", cnonce); err |= mbuf_write_u8(mb, 0); if (err) goto out; /* compute response */ /* HA1 = MD5(username:realm:password) */ p2 = mb->pos; err = mbuf_printf(mb, "%r:%r:%s", &resp->username, &resp->realm, pwd); if (err) goto out; mbuf_set_pos(mb, p2); md5(mbuf_buf(mb), mbuf_get_left(mb), ha1); mbuf_skip_to_end(mb); if (0 == pl_strcmp(&chall->algorithm, "MD5-sess")) { /* HA1 = MD5(HA1:nonce:cnonce) */ p2 = mb->pos; err = mbuf_printf(mb, "%w:%r:%x", ha1, sizeof(ha1), &resp->nonce, cnonce); if (err) goto out; mbuf_set_pos(mb, p2); md5(mbuf_buf(mb), mbuf_get_left(mb), ha1); mbuf_skip_to_end(mb); } /* HA2 */ p2 = mb->pos; if (0 == pl_strcmp(&resp->qop, "auth-int") && mbuf_get_left(body)) { /* HA2 = MD5(method:digestURI:MD5(entityBody)) */ err = mbuf_write_mem(mb, mbuf_buf(body), mbuf_get_left(body)); if (err) goto out; mbuf_set_pos(mb, p2); md5(mbuf_buf(mb), mbuf_get_left(mb), ha2); mbuf_skip_to_end(mb); p2 = mb->pos; err = mbuf_printf(mb, "%s:%r:%w", method, &resp->uri, ha2, sizeof(ha2)); } else { /* HA2 = MD5(method:digestURI) */ err = mbuf_printf(mb, "%s:%r", method, &resp->uri); } if (err) goto out; mbuf_set_pos(mb, p2); md5(mbuf_buf(mb), mbuf_get_left(mb), ha2); mbuf_skip_to_end(mb); /* response */ p2 = mb->pos; if (0 == pl_strcmp(&resp->qop, "auth-int") || 0 == pl_strcmp(&resp->qop, "auth")) { /* response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) */ err = mbuf_printf(mb, "%w:%r:%x:%x:%r:%w", ha1, sizeof(ha1), &resp->nonce, re_atomic_rlx(&nc), cnonce, &resp->qop, ha2, sizeof(ha2)); } else { /* response = MD5(HA1:nonce:HA2) */ err = mbuf_printf(mb, "%w:%r:%w", ha1, sizeof(ha1), &resp->nonce, ha2, sizeof(ha2)); } if (err) goto out; mbuf_set_pos(mb, p2); md5(mbuf_buf(mb), mbuf_get_left(mb), response); mbuf_skip_to_end(mb); p2 = mb->pos; err = mbuf_printf(mb, "%w", response, sizeof(response)); err |= mbuf_write_u8(mb, 0); if (err) goto out; re_atomic_rlx_add(&nc, 1); mbuf_set_pos(mb, 0); pl_set_str(&resp->nc, (const char*) mbuf_buf(mb)); mbuf_set_pos(mb, p1); pl_set_str(&resp->cnonce, (const char*) mbuf_buf(mb)); mbuf_set_pos(mb, p2); pl_set_str(&resp->response, (const char*) mbuf_buf(mb)); out: resp->mb = mb; if (err) mem_deref(resp); else *presp = resp; return err; } int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, struct mbuf *mb) { int err; size_t s; if (!resp || !mb) return EINVAL; /* length of string literals */ s = 93; if (pl_isset(&resp->qop)) s += 26; /* length of values */ s += resp->username.l + resp->realm.l + resp->nonce.l + resp->uri.l; s += resp->response.l; if (pl_isset(&resp->qop)) s += resp->qop.l + resp->nc.l + resp->cnonce.l; if (s > mb->size) { err = mbuf_resize(mb, s); if (err) return err; } err = mbuf_write_str(mb, "Authorization: "); err |= mbuf_printf(mb, "Digest username=\"%r\"", &resp->username); err |= mbuf_printf(mb, ", realm=\"%r\"", &resp->realm); err |= mbuf_printf(mb, ", nonce=\"%r\"", &resp->nonce); err |= mbuf_printf(mb, ", uri=\"%r\"", &resp->uri); err |= mbuf_printf(mb, ", response=\"%r\"", &resp->response); if (pl_isset(&resp->qop)) { err |= mbuf_printf(mb, ", qop=%r", &resp->qop); err |= mbuf_printf(mb, ", nc=%r", &resp->nc); err |= mbuf_printf(mb, ", cnonce=\"%r\"", &resp->cnonce); } mbuf_set_pos(mb, 0); return err; } static void httpauth_digest_chall_req_destructor(void *arg) { struct httpauth_digest_chall_req *req = arg; mem_deref(req->realm); mem_deref(req->domain); mem_deref(req->nonce); mem_deref(req->opaque); mem_deref(req->algorithm); mem_deref(req->qop); mem_deref(req->charset); } static int generate_nonce(char **pnonce, const time_t ts, const char *etag, const char *secret) { struct mbuf *mb = NULL; char *nonce = NULL; uint8_t hash [SHA256_DIGEST_LENGTH]; int err = 0; mb = mbuf_alloc(32); if (!mb) return ENOMEM; if (str_isset(secret)) err = mbuf_printf(mb, "%Lu:%s:%s", (uint64_t)ts, etag, secret); else err = mbuf_printf(mb, "%Lu:%s", (uint64_t)ts, etag); if (err) goto out; sha256(mb->buf, mb->end, hash); mbuf_rewind(mb); err = mbuf_printf(mb, "%w%016Lx", hash, sizeof(hash), (uint64_t)ts); if (err) goto out; mbuf_set_pos(mb, 0); err = mbuf_strdup(mb, &nonce, mbuf_get_left(mb)); out: if (err) mem_deref(nonce); else *pnonce = nonce; mem_deref(mb); return err; } static int check_nonce(const char *req_nonce, const struct pl *resp_nonce, const char *etag) { struct pl pl = PL_INIT; time_t ts; char *renonce = NULL; int err = 0; if (!req_nonce || !resp_nonce || !etag) return EINVAL; pl = *resp_nonce; pl.p = pl.p + (pl.l - 16); pl.l = 16; ts = (time_t) pl_x64(&pl); if (time(NULL) - ts > 300) { err = ETIMEDOUT; goto out; } err = generate_nonce(&renonce, ts, etag, NULL); if (err) goto out; if (str_casecmp(req_nonce, renonce)) err = EAUTH; out: mem_deref(renonce); return err; } static int digest_verify(struct httpauth_digest_chall_req *req, struct httpauth_digest_resp *resp, const struct pl *method, const char *user, const char *passwd, const char *entitybody) { uint8_t *hash1 = NULL; uint8_t *hash2 = NULL; struct mbuf *mb = NULL; int err = 0; mb = mbuf_alloc(str_len(user) + str_len(passwd) + str_len(req->realm) + 2); hash1 = mem_zalloc(resp->hash_length, NULL); hash2 = mem_zalloc(resp->hash_length, NULL); if (!mb || !hash1 || !hash2) { err = ENOMEM; goto out; } /* HASH H2 */ if (pl_strstr(&resp->qop, "auth-int")) { if (!str_isset(entitybody)) resp->hashh((uint8_t *)"", str_len(""), hash1); else resp->hashh((uint8_t *)entitybody, str_len(entitybody), hash1); err = mbuf_printf(mb, "%r:%r:%w", method, &resp->uri, hash1, resp->hash_length); } else { err = mbuf_printf(mb, "%r:%r", method, &resp->uri); } if (err) goto out; resp->hashh(mb->buf, mb->end, hash2); mbuf_rewind(mb); /* HASH H1 */ if (pl_strcmp(&resp->username, user) != 0) { err = EACCES; goto out; } err = mbuf_printf(mb, "%s:%r:%s", user, &resp->realm, passwd); if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); mbuf_rewind(mb); if (pl_strstr(&resp->algorithm, "-sess")) { err = mbuf_printf(mb, "%w:%r:%r", hash1, resp->hash_length, &resp->nonce, &resp->cnonce); if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); mbuf_rewind(mb); } /* DIGEST */ if (pl_isset(&resp->qop)) { err = mbuf_printf(mb, "%w:%r:%r:%r:%r:%w", hash1, resp->hash_length, &resp->nonce, &resp->nc, &resp->cnonce, &resp->qop, hash2, resp->hash_length); } else { err = mbuf_printf(mb, "%w:%r:%w", hash1, resp->hash_length, &resp->nonce, hash2, resp->hash_length); } if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); mbuf_rewind(mb); /* VERIFICATION */ err = pl_hex(&resp->response, hash2, resp->hash_length); if (err) goto out; err = mem_seccmp(hash1, hash2, resp->hash_length) == 0 ? 0 : EACCES; out: mem_deref(hash1); mem_deref(hash2); mem_deref(mb); return err; } int httpauth_digest_verify(struct httpauth_digest_chall_req *req, const struct pl *hval, const struct pl *method, const char *etag, const char *user, const char *passwd, const char *entitybody) { struct httpauth_digest_resp resp; int err = 0; if (!req || !hval || !method || !user || !passwd) return EINVAL; err = httpauth_digest_response_decode(&resp, hval); if (err) return err; if (pl_strcasecmp(&resp.realm, req->realm)) return EINVAL; err = check_nonce(req->nonce, &resp.nonce, etag); if (err == ETIMEDOUT || err == EAUTH) { req->stale = true; return EAUTH; } else if (err) { return err; } return digest_verify(req, &resp, method, user, passwd, entitybody); } /** * Prints / encodes an HTTP digest request challenge * * @param pf Re_printf object * @param req Request to print * * @return 0 if success, otherwise errorcode */ int httpauth_digest_chall_req_print(struct re_printf *pf, const struct httpauth_digest_chall_req *req) { int err = 0; if (!req) return EINVAL; /* historical reason quoted strings: */ /* realm, domain, nonce, opaque, qop */ /* historical reason unquoted strings: */ /* stale, algorithm */ err = re_hprintf(pf, "Digest realm=\"%s\", " "qop=\"%s\", nonce=\"%s\", algorithm=%s", req->realm, req->qop, req->nonce, req->algorithm); if (str_isset(req->opaque)) err |= re_hprintf(pf, ", opaque=\"%s\"", req->opaque); if (str_isset(req->domain)) err |= re_hprintf(pf, ", domain=\"%s\"", req->domain); if (req->stale) err |= re_hprintf(pf, ", stale=true"); if (str_isset(req->charset)) err |= re_hprintf(pf, ", charset=\"%s\"", req->charset); if (req->userhash) err |= re_hprintf(pf, ", userhash=true"); return err; } /** * Create a digest authentication request * * @param preq Httpauth_digest_chall_req object ptr * @param realm Realm * @param etag Changing data for nonce creation * (HTTP ETag header / SIP msg src address) * @param qop Quality of protection * * @return 0 if success, otherwise errorcode */ int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, const char *realm, const char *etag, const char *qop) { return httpauth_digest_chall_request_full(preq, realm, NULL, etag, NULL, false, NULL, qop, NULL, false); } /** * Create a full configurable digest authentication request * * @param preq Httpauth_digest_chall_req object ptr * @param realm Realm * @param domain Domain (not used in SIP) * @param etag Changing data for nonce creation * (HTTP ETag header / SIP msg src address) * @param opaque Opaque * @param stale Stale * @param algo Supported algorithm (MD5, SHA1, SHA256 and sess versions) * @param qop Quality of protection * @param charset Character set used (not used in SIP) * @param userhash Userhash support (not used in SIP) * * @return 0 if success, otherwise errorcode */ int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, const char *realm, const char *domain, const char *etag, const char *opaque, const bool stale, const char *algo, const char *qop, const char *charset, const bool userhash) { struct httpauth_digest_chall_req *req = NULL; int err = 0; if (!preq || !realm || !etag || !qop) return EINVAL; req = mem_zalloc(sizeof(*req), httpauth_digest_chall_req_destructor); if (!req) return ENOMEM; req->stale = stale; req->userhash = userhash; err = str_dup(&req->realm, realm); err |= str_dup(&req->qop, qop); if (str_isset(algo)) err |= str_dup(&req->algorithm, algo); else err |= str_dup(&req->algorithm, "MD5"); if (str_isset(domain)) err |= str_dup(&req->domain, domain); if (str_isset(opaque)) err |= str_dup(&req->opaque, opaque); if (str_isset(charset) && str_casecmp(charset, "UTF-8") == 0) err |= str_dup(&req->charset, charset); if (err) goto out; err = generate_nonce(&req->nonce, time(NULL), etag, NULL); out: if (err) mem_deref(req); else *preq = req; return err; } static void httpauth_digest_response_destructor(void *arg) { struct httpauth_digest_enc_resp *resp = arg; mem_deref(resp->realm); mem_deref(resp->nonce); mem_deref(resp->opaque); mem_deref(resp->algorithm); mem_deref(resp->qop); mem_deref(resp->response); mem_deref(resp->username); mem_deref(resp->username_star); mem_deref(resp->uri); mem_deref(resp->charset); } static int digest_response(struct httpauth_digest_enc_resp *resp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *user, const char *passwd, const char *entitybody) { uint8_t *hash1 = NULL; uint8_t *hash2 = NULL; struct mbuf *mb = NULL; int err = 0, n = 0; if (!resp || !resp->hashh) return EINVAL; size_t hashstringl = (resp->hash_length * 2) + 1; mb = mbuf_alloc(str_len(user) + str_len(passwd) + chall->realm.l + 2); if (!mb) return ENOMEM; hash1 = mem_zalloc(resp->hash_length, NULL); hash2 = mem_zalloc(resp->hash_length, NULL); if (!resp->response) resp->response = mem_zalloc(hashstringl, NULL); if (!resp->response || !hash1 || !hash2) { err = ENOMEM; goto out; } /* HASH A2 */ if (str_isset(resp->qop) && str_str(resp->qop, "auth-int")) { if (!entitybody || str_casecmp(entitybody, "") == 0) { resp->hashh((uint8_t *)"", 0, hash1); } else { resp->hashh((uint8_t *)entitybody, str_len(entitybody), hash1); } err = mbuf_printf(mb, "%r:%s:%w", method, resp->uri, hash1, resp->hash_length); } else { err = mbuf_printf(mb, "%r:%s", method, resp->uri); } if (err) goto out; resp->hashh(mb->buf, mb->end, hash2); mbuf_rewind(mb); /* HASH A1 */ if (resp->userhash) { if (!resp->username) resp->username = mem_zalloc(hashstringl, NULL); if (!resp->username) { err = ENOMEM; goto out; } err = mbuf_printf(mb, "%s:%s", user, resp->realm); if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); n = re_snprintf(resp->username, hashstringl, "%w", hash1, hashstringl); if (n == -1 || n != (int)hashstringl -1) { err = ERANGE; goto out; } mbuf_rewind(mb); err = mbuf_printf(mb, "%w:%s:%s", hash1, resp->hash_length, resp->realm, passwd); } else { err = mbuf_printf(mb, "%s:%s:%s", user, resp->realm, passwd); resp->username = mem_deref(resp->username); err |= str_dup(&resp->username, user); } if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); mbuf_rewind(mb); if (str_str(resp->algorithm, "-sess")) { err = mbuf_printf(mb, "%w:%s:%08x", hash1, resp->hash_length, resp->nonce, resp->cnonce); if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); mbuf_rewind(mb); } /* DIGEST */ if (str_isset(resp->qop)) { err = mbuf_printf(mb, "%w:%s:%08x:%08x:%s:%w", hash1, resp->hash_length, resp->nonce, resp->nc, resp->cnonce, resp->qop, hash2, resp->hash_length); } else { err = mbuf_printf(mb, "%w:%s:%w", hash1, resp->hash_length, resp->nonce, hash2, resp->hash_length); } if (err) goto out; resp->hashh(mb->buf, mb->end, hash1); n = re_snprintf(resp->response, hashstringl, "%w", hash1, resp->hash_length); if (n == -1 || n != (int)hashstringl - 1) err = ERANGE; out: mem_deref(mb); mem_deref(hash1); mem_deref(hash2); return err; } /** * Prints / encodes an HTTP digest response * * @param pf Re_printf object * @param resp Response to print * * @return 0 if success, otherwise errorcode */ int httpauth_digest_response_print(struct re_printf *pf, const struct httpauth_digest_enc_resp *resp) { int err = 0; if (!resp) return EINVAL; /* historical reason quoted strings: */ /* username, realm, nonce, uri, */ /* response, cnonce, opaque */ /* historical reason unquoted strings: */ /* qop, algorithm, nc */ err = re_hprintf(pf, "Digest realm=\"%s\"," " nonce=\"%s\", username=\"%s\", uri=\"%s\"," " response=\"%s\"", resp->realm, resp->nonce, resp->username, resp->uri, resp->response); if (str_isset(resp->opaque)) err |= re_hprintf(pf, ", opaque=\"%s\"", resp->opaque); if (str_isset(resp->algorithm)) err |= re_hprintf(pf, ", algorithm=%s", resp->algorithm); if (str_isset(resp->qop)) err |= re_hprintf(pf, ", qop=%s, cnonce=\"%08x\", nc=\"%08x\"", resp->qop, resp->cnonce, resp->nc); if (resp->userhash) err |= re_hprintf(pf, ", userhash=true"); if (str_isset(resp->charset)) err |= re_hprintf(pf, ", charset=\"%s\"", resp->charset); return err; } /** * Set cnonce and nc and recalculate the response value. * This function should be used only for unit tests * * @param resp Httpauth_new_digest_response object pointer * @param chall Received and decoded digest challenge * @param method Used method * @param user Username * @param passwd User password * @param entitybody Entitybody if qop=auth-int * @param cnonce Cnonce * @param nonce_cnt Nonce counter * * @return 0 if success, otherwise errorcode */ int httpauth_digest_response_set_cnonce(struct httpauth_digest_enc_resp *resp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *user, const char *passwd, const char *entitybody, uint32_t cnonce, uint32_t nonce_cnt) { if (!resp || !chall || !method || !passwd) return EINVAL; resp->cnonce = cnonce; resp->nc = nonce_cnt; return digest_response(resp, chall, method, user, passwd, entitybody); } /** * Create a digest authentication response * * @param presp Httpauth_new_digest_response object pointer * @param chall Received and decoded digest challenge * @param method Used method * @param uri Accessed uri * @param user Username * @param passwd User password * @param qop Quality of protection * @param entitybody Entitybody if qop=auth-int * * @return 0 if success, otherwise errorcode */ int httpauth_digest_response(struct httpauth_digest_enc_resp **presp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *uri, const char *user, const char *passwd, const char *qop, const char *entitybody) { return httpauth_digest_response_full(presp, chall, method, uri, user, passwd, qop, entitybody, NULL, false); } /** * Create a full configurable digest authentication response * * @param presp Httpauth_new_digest_response object pointer * @param chall Received and decoded digest challenge * @param method Used method * @param uri Accessed uri * @param user Username * @param passwd User password * @param qop Quality of protection * @param entitybody Entitybody if qop=auth-int * @param charset Used character set (only UTF-8 or NULL allowed) * @param userhash Enable hashed usernames * * @return 0 if success, otherwise errorcode */ int httpauth_digest_response_full(struct httpauth_digest_enc_resp **presp, const struct httpauth_digest_chall *chall, const struct pl *method, const char *uri, const char *user, const char *passwd, const char *qop, const char *entitybody, const char *charset, const bool userhash) { struct httpauth_digest_enc_resp *resp = NULL; int err = 0; if (!presp || !chall || !method || !uri || !user || !passwd) return EINVAL; resp = mem_zalloc(sizeof(*resp), httpauth_digest_response_destructor); if (!resp) { return ENOMEM; } /* create cnonce & nonce count */ resp->cnonce = rand_u32(); resp->nc = (uint32_t) re_atomic_rlx_add(&nc, 1); /* copy fields */ err = pl_strdup(&resp->realm, &chall->realm); err |= pl_strdup(&resp->nonce, &chall->nonce); err |= pl_strdup(&resp->opaque, &chall->opaque); if (err) { goto out; } /* userhash supported by server */ if (userhash && (pl_strcasecmp(&chall->userhash, "true") == 0)) resp->userhash = true; /* only allowed qop Nothing, "auth" or "auth-int" */ if (str_isset(qop) && (str_casecmp(qop, "auth")) && (str_casecmp(qop, "auth-int"))) { err = EPROTONOSUPPORT; goto out; } /* qop supported by server */ if (pl_isset(&chall->qop) && str_isset(qop) && pl_strstr(&chall->qop, qop)) { err = str_dup(&resp->qop, qop); if (err) goto out; } /* only allowed charset Nothing or "UTF-8" */ if (str_isset(charset) && str_casecmp(charset, "UTF-8")) { err = EPROTONOSUPPORT; goto out; } /* charset supported by server */ if (pl_isset(&chall->charset) && str_isset(charset) && pl_strstr(&chall->charset, charset) == 0) { err = str_dup(&resp->charset, charset); if (err) goto out; } err = str_dup(&resp->uri, uri); if (err) goto out; if (pl_strstr(&chall->algorithm, "SHA-256-sess")) { resp->hashh = &sha256; resp->hash_length = SHA256_DIGEST_LENGTH; err = str_dup(&resp->algorithm, "SHA-256-sess"); } else if (pl_strstr(&chall->algorithm, "SHA-256")) { resp->hashh = &sha256; resp->hash_length = SHA256_DIGEST_LENGTH; err = str_dup(&resp->algorithm, "SHA-256"); } else if (pl_strstr(&chall->algorithm, "MD5-sess")) { resp->hashh = &md5; resp->hash_length = MD5_SIZE; err = str_dup(&resp->algorithm, "MD5-sess"); } else if (!pl_isset(&chall->algorithm) || pl_strstr(&chall->algorithm, "MD5")) { resp->hashh = &md5; resp->hash_length = MD5_SIZE; err = str_dup(&resp->algorithm, "MD5"); } else { err = EPROTONOSUPPORT; goto out; } if (err) goto out; err = digest_response(resp, chall, method, user, passwd, entitybody); out: if (err) mem_deref(resp); else *presp = resp; return err; } ================================================ FILE: src/ice/cand.c ================================================ /** * @file cand.c ICE Candidates * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "icecand" #define DEBUG_LEVEL 5 #include static void cand_destructor(void *arg) { struct ice_cand *cand = arg; list_unlink(&cand->le); mem_deref(cand->foundation); mem_deref(cand->ifname); if (cand != cand->base) mem_deref(cand->base); } /** Foundation is a hash of IP address and candidate type */ static int compute_foundation(struct ice_cand *cand) { uint32_t v; v = sa_hash(&cand->addr, SA_ADDR); v ^= cand->type; return re_sdprintf(&cand->foundation, "%08x", v); } static int cand_alloc(struct ice_cand **candp, struct icem *icem, enum ice_cand_type type, unsigned compid, uint32_t prio, const char *ifname, enum ice_transp transp, const struct sa *addr) { struct ice_cand *cand; int err; if (!icem) return EINVAL; cand = mem_zalloc(sizeof(*cand), cand_destructor); if (!cand) return ENOMEM; list_append(&icem->lcandl, &cand->le, cand); cand->type = type; cand->compid = compid; cand->prio = prio; cand->transp = transp; sa_cpy(&cand->addr, addr); err = compute_foundation(cand); if (ifname) err |= str_dup(&cand->ifname, ifname); if (err) mem_deref(cand); else if (candp) *candp = cand; return err; } int icem_lcand_add_base(struct icem *icem, enum ice_cand_type type, unsigned compid, uint16_t lprio, const char *ifname, enum ice_transp transp, const struct sa *addr) { struct icem_comp *comp; struct ice_cand *cand; int err; if (icem->conf.policy == ICE_POLICY_RELAY && type != ICE_CAND_TYPE_RELAY) return 0; if (type != ICE_CAND_TYPE_HOST && type != ICE_CAND_TYPE_RELAY) return EINVAL; comp = icem_comp_find(icem, compid); if (!comp) return ENOENT; err = cand_alloc(&cand, icem, type, compid, ice_cand_calc_prio(type, lprio, compid), ifname, transp, addr); if (err) return err; /* the base is itself */ cand->base = cand; if (type == ICE_CAND_TYPE_RELAY) sa_cpy(&cand->rel, addr); if (type == ICE_CAND_TYPE_HOST) sa_set_port(&cand->addr, comp->lport); return 0; } int icem_lcand_add(struct icem *icem, struct ice_cand *base, enum ice_cand_type type, const struct sa *addr) { struct ice_cand *cand; int err; if (icem->conf.policy == ICE_POLICY_RELAY) return 0; if (!base) return EINVAL; if (type == ICE_CAND_TYPE_HOST || type == ICE_CAND_TYPE_RELAY) return EINVAL; err = cand_alloc(&cand, icem, type, base->compid, ice_cand_calc_prio(type, 0, base->compid), base->ifname, base->transp, addr); if (err) return err; cand->base = mem_ref(base); sa_cpy(&cand->rel, &base->addr); return 0; } int icem_rcand_add(struct icem *icem, enum ice_cand_type type, unsigned compid, uint32_t prio, const struct sa *addr, const struct sa *rel_addr, const struct pl *foundation) { struct ice_cand *rcand; int err; if (!icem || !foundation) return EINVAL; rcand = mem_zalloc(sizeof(*rcand), cand_destructor); if (!rcand) return ENOMEM; list_append(&icem->rcandl, &rcand->le, rcand); rcand->type = type; rcand->compid = compid; rcand->prio = prio; sa_cpy(&rcand->addr, addr); sa_cpy(&rcand->rel, rel_addr); err = pl_strdup(&rcand->foundation, foundation); if (err) mem_deref(rcand); return err; } int icem_rcand_add_prflx(struct ice_cand **rcp, struct icem *icem, unsigned compid, uint32_t prio, const struct sa *addr) { struct ice_cand *rcand; int err; if (!icem || !addr) return EINVAL; rcand = mem_zalloc(sizeof(*rcand), cand_destructor); if (!rcand) return ENOMEM; list_append(&icem->rcandl, &rcand->le, rcand); rcand->type = ICE_CAND_TYPE_PRFLX; rcand->compid = compid; rcand->prio = prio; rcand->addr = *addr; err = re_sdprintf(&rcand->foundation, "%08x", rand_u32()); if (err) goto out; icecomp_printf(icem_comp_find(icem, compid), "added PeerReflexive remote candidate" " with priority %u (%J)\n", prio, addr); out: if (err) mem_deref(rcand); else if (rcp) *rcp = rcand; return err; } struct ice_cand *icem_cand_find(const struct list *lst, unsigned compid, const struct sa *addr) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_cand *cand = le->data; if (compid && cand->compid != compid) continue; if (addr && !sa_cmp(&cand->addr, addr, SA_ALL)) continue; return cand; } return NULL; } /** * Find the highest priority LCAND on the check-list of type HOST/RELAY * * @param icem ICE Media object * @param compid Component ID * * @return Local candidate if found, otherwise NULL */ struct ice_cand *icem_lcand_find_checklist(const struct icem *icem, unsigned compid) { struct le *le; for (le = icem->checkl.head; le; le = le->next) { struct ice_candpair *cp = le->data; if (cp->lcand->compid != compid) continue; switch (cp->lcand->type) { case ICE_CAND_TYPE_HOST: case ICE_CAND_TYPE_RELAY: return cp->lcand; default: break; } } return NULL; } struct ice_cand *icem_lcand_base(struct ice_cand *lcand) { return lcand ? lcand->base : NULL; } const struct sa *icem_lcand_addr(const struct ice_cand *cand) { return cand ? &cand->addr : NULL; } int icem_cands_debug(struct re_printf *pf, const struct list *lst) { struct le *le; int err; err = re_hprintf(pf, " (%u)\n", list_count(lst)); for (le = list_head(lst); le && !err; le = le->next) { const struct ice_cand *cand = le->data; err |= re_hprintf(pf, " {%u} fnd=%-2s prio=%08x %24H", cand->compid, cand->foundation, cand->prio, icem_cand_print, cand); if (sa_isset(&cand->rel, SA_ADDR)) err |= re_hprintf(pf, " (rel-addr=%J)", &cand->rel); err |= re_hprintf(pf, "\n"); } return err; } int icem_cand_print(struct re_printf *pf, const struct ice_cand *cand) { int err = 0; if (!cand) return 0; if (cand->ifname) err |= re_hprintf(pf, "%s:", cand->ifname); err |= re_hprintf(pf, "%s:%J", ice_cand_type2name(cand->type), &cand->addr); return err; } enum ice_cand_type icem_cand_type(const struct ice_cand *cand) { return cand ? cand->type : (enum ice_cand_type)-1; } ================================================ FILE: src/ice/candpair.c ================================================ /** * @file candpair.c ICE Candidate Pairs * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "cndpair" #define DEBUG_LEVEL 5 #include static void candpair_destructor(void *arg) { struct ice_candpair *cp = arg; list_unlink(&cp->le); mem_deref(cp->ct_conn); mem_deref(cp->lcand); mem_deref(cp->rcand); } static bool sort_handler(struct le *le1, struct le *le2, void *arg) { const struct ice_candpair *cp1 = le1->data, *cp2 = le2->data; (void)arg; return cp1->pprio >= cp2->pprio; } static void candpair_set_pprio(struct ice_candpair *cp) { uint32_t g, d; if (ICE_ROLE_CONTROLLING == cp->icem->lrole) { g = cp->lcand->prio; d = cp->rcand->prio; } else { g = cp->rcand->prio; d = cp->lcand->prio; } cp->pprio = ice_calc_pair_prio(g, d); } /** * Add candidate pair to list, sorted by pair priority (highest is first) */ static void list_add_sorted(struct list *list, struct ice_candpair *cp) { struct le *le; /* find our slot */ for (le = list_tail(list); le; le = le->prev) { struct ice_candpair *cp0 = le->data; if (cp->pprio < cp0->pprio) { list_insert_after(list, le, &cp->le, cp); return; } } list_prepend(list, &cp->le, cp); } int icem_candpair_alloc(struct ice_candpair **cpp, struct icem *icem, struct ice_cand *lcand, struct ice_cand *rcand) { struct ice_candpair *cp; struct icem_comp *comp; if (!icem || !lcand || !rcand) return EINVAL; comp = icem_comp_find(icem, lcand->compid); if (!comp) return ENOENT; cp = mem_zalloc(sizeof(*cp), candpair_destructor); if (!cp) return ENOMEM; cp->icem = icem; cp->comp = comp; cp->lcand = mem_ref(lcand); cp->rcand = mem_ref(rcand); cp->state = ICE_CANDPAIR_FROZEN; cp->def = comp->def_lcand == lcand && comp->def_rcand == rcand; candpair_set_pprio(cp); list_add_sorted(&icem->checkl, cp); if (cpp) *cpp = cp; return 0; } int icem_candpair_clone(struct ice_candpair **cpp, struct ice_candpair *cp0, struct ice_cand *lcand, struct ice_cand *rcand) { struct ice_candpair *cp; if (!cp0) return EINVAL; cp = mem_zalloc(sizeof(*cp), candpair_destructor); if (!cp) return ENOMEM; cp->icem = cp0->icem; cp->comp = cp0->comp; cp->lcand = mem_ref(lcand ? lcand : cp0->lcand); cp->rcand = mem_ref(rcand ? rcand : cp0->rcand); cp->def = cp0->def; cp->valid = cp0->valid; cp->nominated = cp0->nominated; cp->state = cp0->state; cp->pprio = cp0->pprio; cp->err = cp0->err; cp->scode = cp0->scode; list_add_sorted(&cp0->icem->checkl, cp); if (cpp) *cpp = cp; return 0; } /** * Computing Pair Priority and Ordering Pairs * * @param lst Checklist (struct ice_candpair) */ void icem_candpair_prio_order(struct list *lst) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; candpair_set_pprio(cp); } list_sort(lst, sort_handler, NULL); } /* cancel transaction */ void icem_candpair_cancel(struct ice_candpair *cp) { if (!cp) return; cp->ct_conn = mem_deref(cp->ct_conn); } void icem_candpair_make_valid(struct ice_candpair *cp) { if (!cp) return; cp->err = 0; cp->scode = 0; cp->valid = true; icem_candpair_set_state(cp, ICE_CANDPAIR_SUCCEEDED); list_unlink(&cp->le); list_add_sorted(&cp->icem->validl, cp); } void icem_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode) { if (!cp) return; cp->err = err; cp->scode = scode; cp->valid = false; icem_candpair_set_state(cp, ICE_CANDPAIR_FAILED); } void icem_candpair_set_state(struct ice_candpair *cp, enum ice_candpair_state state) { if (!cp) return; if (cp->state == state || icem_candpair_iscompleted(cp)) return; icecomp_printf(cp->comp, "%5s <---> %5s FSM: %10s ===> %-10s\n", ice_cand_type2name(cp->lcand->type), ice_cand_type2name(cp->rcand->type), ice_candpair_state2name(cp->state), ice_candpair_state2name(state)); cp->state = state; } /** * Delete all Candidate-Pairs where the Local candidate is of a given type * * @param lst Checklist or Validlist * @param type Candidate type * @param compid Component ID */ void icem_candpairs_flush(struct list *lst, enum ice_cand_type type, unsigned compid) { struct le *le = list_head(lst); while (le) { struct ice_candpair *cp = le->data; le = le->next; if (cp->lcand->compid != compid) continue; if (cp->lcand->type != type) continue; mem_deref(cp); } } bool icem_candpair_iscompleted(const struct ice_candpair *cp) { if (!cp) return false; return cp->state == ICE_CANDPAIR_FAILED || cp->state == ICE_CANDPAIR_SUCCEEDED; } /** * Compare local and remote candidates of two candidate pairs * * @param cp1 First Candidate pair * @param cp2 Second Candidate pair * * @return true if match */ bool icem_candpair_cmp(const struct ice_candpair *cp1, const struct ice_candpair *cp2) { if (!sa_cmp(&cp1->lcand->addr, &cp2->lcand->addr, SA_ALL)) return false; return sa_cmp(&cp1->rcand->addr, &cp2->rcand->addr, SA_ALL); } /** * Find the highest-priority candidate-pair in a given list, with * optional match parameters * * @param lst List of candidate pairs * @param lcand Local candidate (optional) * @param rcand Remote candidate (optional) * * @return Matching candidate pair if found, otherwise NULL * * note: assume list is sorted by priority */ struct ice_candpair *icem_candpair_find(const struct list *lst, const struct ice_cand *lcand, const struct ice_cand *rcand) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; if (!cp->lcand || !cp->rcand) { DEBUG_WARNING("corrupt candpair %p\n", cp); continue; } if (lcand && cp->lcand != lcand) continue; if (rcand && cp->rcand != rcand) continue; return cp; } return NULL; } struct ice_candpair *icem_candpair_find_st(const struct list *lst, unsigned compid, enum ice_candpair_state state) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; if (compid && cp->lcand->compid != compid) continue; if (cp->state != state) continue; return cp; } return NULL; } struct ice_candpair *icem_candpair_find_compid(const struct list *lst, unsigned compid) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; if (cp->lcand->compid != compid) continue; return cp; } return NULL; } /** * Find a remote candidate in the checklist or validlist * * @param icem ICE Media object * @param rcand Remote candidate * * @return Candidate pair if found, otherwise NULL */ struct ice_candpair *icem_candpair_find_rcand(struct icem *icem, const struct ice_cand *rcand) { struct ice_candpair *cp; cp = icem_candpair_find(&icem->checkl, NULL, rcand); if (cp) return cp; cp = icem_candpair_find(&icem->validl, NULL, rcand); if (cp) return cp; return NULL; } bool icem_candpair_cmp_fnd(const struct ice_candpair *cp1, const struct ice_candpair *cp2) { if (!cp1 || !cp2) return false; return 0 == strcmp(cp1->lcand->foundation, cp2->lcand->foundation) && 0 == strcmp(cp1->rcand->foundation, cp2->rcand->foundation); } int icem_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp) { int err; if (!cp) return 0; err = re_hprintf(pf, "{comp=%u} %10s {%c%c%c} %28H <---> %28H", cp->lcand->compid, ice_candpair_state2name(cp->state), cp->def ? 'D' : ' ', cp->valid ? 'V' : ' ', cp->nominated ? 'N' : ' ', icem_cand_print, cp->lcand, icem_cand_print, cp->rcand); if (cp->err) err |= re_hprintf(pf, " (%m)", cp->err); if (cp->scode) err |= re_hprintf(pf, " [%u]", cp->scode); return err; } int icem_candpairs_debug(struct re_printf *pf, const struct list *list) { struct le *le; bool ansi_output = true; int err; if (!list) return 0; err = re_hprintf(pf, " (%u)\n", list_count(list)); for (le = list->head; le && !err; le = le->next) { const struct ice_candpair *cp = le->data; bool is_selected = (cp == cp->comp->cp_sel); bool ansi = false; if (ansi_output) { if (cp->state == ICE_CANDPAIR_SUCCEEDED) { err |= re_hprintf(pf, "\x1b[32m"); ansi = true; } else if (cp->err || cp->scode) { err |= re_hprintf(pf, "\x1b[31m"); ansi = true; } } err |= re_hprintf(pf, " %c %H\n", is_selected ? '*' : ' ', icem_candpair_debug, cp); if (ansi) err |= re_hprintf(pf, "\x1b[;m"); } return err; } ================================================ FILE: src/ice/chklist.c ================================================ /** * @file chklist.c ICE Checklist * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "ice" #define DEBUG_LEVEL 5 #include /** * Forming Candidate Pairs */ static int candpairs_form(struct icem *icem) { struct le *le; int err = 0; if (list_isempty(&icem->lcandl)) return ENOENT; if (list_isempty(&icem->rcandl)) { DEBUG_WARNING("form: '%s' no remote candidates\n", icem->name); return ENOENT; } for (le = icem->lcandl.head; le; le = le->next) { struct ice_cand *lcand = le->data; struct le *rle; for (rle = icem->rcandl.head; rle; rle = rle->next) { struct ice_cand *rcand = rle->data; if (lcand->compid != rcand->compid) continue; if (sa_af(&lcand->addr) != sa_af(&rcand->addr)) continue; if (icem_candpair_find(&icem->checkl, lcand, rcand)) continue; if (icem_candpair_find(&icem->validl, lcand, rcand)) continue; err = icem_candpair_alloc(NULL, icem, lcand, rcand); if (err) return err; } } return err; } /* Replace server reflexive candidates by its base */ static const struct sa *cand_srflx_addr(const struct ice_cand *c) { return (ICE_CAND_TYPE_SRFLX == c->type) ? &c->base->addr : &c->addr; } /* return: NULL to keep, pointer to remove object */ static void *unique_handler(struct le *le1, struct le *le2) { struct ice_candpair *cp1 = le1->data, *cp2 = le2->data; if (cp1->comp->id != cp2->comp->id) return NULL; if (!sa_cmp(cand_srflx_addr(cp1->lcand), cand_srflx_addr(cp2->lcand), SA_ALL) || !sa_cmp(&cp1->rcand->addr, &cp2->rcand->addr, SA_ALL)) return NULL; return cp1->pprio < cp2->pprio ? cp1 : cp2; } /** * Pruning the Pairs */ static void candpair_prune(struct icem *icem) { /* 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. NOTE: This logic assumes the list is sorted by priority */ uint32_t n = ice_list_unique(&icem->checkl, unique_handler); if (n > 0) { DEBUG_INFO("%s: pruned candidate pairs: %u\n", icem->name, n); } } /** * Computing States * * @param icem ICE Media object */ void ice_candpair_set_states(struct icem *icem) { struct le *le, *le2; /* For all pairs with the same foundation, it 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. */ for (le = icem->checkl.head; le; le = le->next) { struct ice_candpair *cp = le->data; for (le2 = icem->checkl.head; le2; le2 = le2->next) { struct ice_candpair *cp2 = le2->data; if (!icem_candpair_cmp_fnd(cp, cp2)) continue; if (cp2->lcand->compid < cp->lcand->compid && cp2->pprio > cp->pprio) cp = cp2; } icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING); } } /** * Forming the Check Lists * * To form the check list for a media stream, * the agent forms candidate pairs, computes a candidate pair priority, * orders the pairs by priority, prunes them, and sets their states. * These steps are described in this section. * * @param icem ICE Media object * * @return 0 if success, otherwise errorcode */ int icem_checklist_form(struct icem *icem) { int err; if (!icem) return EINVAL; /* 1. form candidate pairs */ err = candpairs_form(icem); if (err) return err; /* 2. compute a candidate pair priority */ /* 3. order the pairs by priority */ icem_candpair_prio_order(&icem->checkl); /* 4. prune the pairs */ candpair_prune(icem); return 0; } /* If all of the pairs in the check list are now either in the Failed or Succeeded state: */ static bool iscompleted(const struct icem *icem) { struct le *le; for (le = icem->checkl.head; le; le = le->next) { const struct ice_candpair *cp = le->data; if (!icem_candpair_iscompleted(cp)) return false; } return true; } /* 8. Concluding ICE Processing */ static void concluding_ice(struct icem_comp *comp) { struct ice_candpair *cp; bool use_cand; if (!comp || comp->concluded) return; /* pick the best candidate pair, highest priority */ cp = icem_candpair_find_st(&comp->icem->validl, comp->id, ICE_CANDPAIR_SUCCEEDED); if (!cp) { DEBUG_WARNING("{%s.%u} conclude: no valid candpair found" " (validlist=%u)\n", comp->icem->name, comp->id, list_count(&comp->icem->validl)); return; } icem_comp_set_selected(comp, cp); /* Regular nomination */ use_cand = comp->icem->lrole == ICE_ROLE_CONTROLLING; /* send STUN request with USE_CAND flag via triggered queue */ (void)icem_conncheck_send(cp, use_cand, true); icem_conncheck_schedule_check(comp->icem); comp->concluded = true; } /** * Check List and Timer State Updates * * @param icem ICE Media object */ void icem_checklist_update(struct icem *icem) { struct le *le; bool compl; int err = 0; compl = iscompleted(icem); if (!compl) return; /* * If 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. */ for (le = icem->compl.head; le; le = le->next) { struct icem_comp *comp = le->data; if (!icem_candpair_find_compid(&icem->validl, comp->id)) { DEBUG_WARNING("{%s.%u} checklist update:" " no valid candidate pair" " (validlist=%u)\n", icem->name, comp->id, list_count(&icem->validl)); err = ENOENT; break; } concluding_ice(comp); if (!comp->cp_sel) continue; icem_comp_keepalive(comp, true); } icem->state = err ? ICE_CHECKLIST_FAILED : ICE_CHECKLIST_COMPLETED; if (icem->chkh) { icem->chkh(err, icem->lrole == ICE_ROLE_CONTROLLING, icem->arg); } } /** * Get the Local address of the Selected Candidate pair, if available * * @param icem ICE Media object * @param compid Component ID * * @return Local address if available, otherwise NULL */ const struct sa *icem_selected_laddr(const struct icem *icem, unsigned compid) { const struct ice_cand *cand = icem_selected_lcand(icem, compid); return icem_lcand_addr(cand); } /** * Get the Local candidate of the Selected Candidate pair, if available * * @param icem ICE Media object * @param compid Component ID * * @return Local candidate if available, otherwise NULL */ const struct ice_cand *icem_selected_lcand(const struct icem *icem, unsigned compid) { const struct icem_comp *comp = icem_comp_find(icem, compid); if (!comp || !comp->cp_sel) return NULL; return comp->cp_sel->lcand; } /** * Get the Remote candidate of the Selected Candidate pair, if available * * @param icem ICE Media object * @param compid Component ID * * @return Remote candidate if available, otherwise NULL */ const struct ice_cand *icem_selected_rcand(const struct icem *icem, unsigned compid) { const struct icem_comp *comp = icem_comp_find(icem, compid); if (!comp || !comp->cp_sel) return NULL; return comp->cp_sel->rcand; } ================================================ FILE: src/ice/comp.c ================================================ /** * @file comp.c ICE Media component * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "icecomp" #define DEBUG_LEVEL 5 #include enum {COMPID_MIN = 1, COMPID_MAX = 255}; static bool helper_recv_handler(struct sa *src, struct mbuf *mb, void *arg) { struct icem_comp *comp = arg; struct icem *icem = comp->icem; struct stun_msg *msg = NULL; struct stun_unknown_attr ua; const size_t start = mb->pos; #if 0 re_printf("{%d} UDP recv_helper: %u bytes from %J\n", comp->id, mbuf_get_left(mb), src); #endif if (stun_msg_decode(&msg, mb, &ua)) return false; if (STUN_METHOD_BINDING == stun_msg_method(msg)) { switch (stun_msg_class(msg)) { case STUN_CLASS_REQUEST: (void)icem_stund_recv(comp, src, msg, start); break; default: (void)stun_ctrans_recv(icem->stun, msg, &ua); break; } } mem_deref(msg); return true; /* handled */ } static void destructor(void *arg) { struct icem_comp *comp = arg; tmr_cancel(&comp->tmr_ka); mem_deref(comp->turnc); mem_deref(comp->cp_sel); mem_deref(comp->def_lcand); mem_deref(comp->def_rcand); mem_deref(comp->uh); mem_deref(comp->sock); } static struct ice_cand *cand_default(const struct list *lcandl, unsigned compid) { struct ice_cand *def = NULL; struct le *le; /* NOTE: list must be sorted by priority */ for (le = list_head(lcandl); le; le = le->next) { struct ice_cand *cand = le->data; if (cand->compid != compid) continue; switch (cand->type) { case ICE_CAND_TYPE_RELAY: return cand; case ICE_CAND_TYPE_SRFLX: if (!def || ICE_CAND_TYPE_SRFLX != def->type) def = cand; break; case ICE_CAND_TYPE_HOST: if (!def) def = cand; break; default: break; } } return def; } int icem_comp_alloc(struct icem_comp **cp, struct icem *icem, int id, void *sock) { struct icem_comp *comp; struct sa local; int err; if (!cp || !icem || id<1 || id>255 || !sock) return EINVAL; comp = mem_zalloc(sizeof(*comp), destructor); if (!comp) return ENOMEM; comp->id = id; comp->sock = mem_ref(sock); comp->icem = icem; err = udp_register_helper(&comp->uh, sock, icem->layer, NULL, helper_recv_handler, comp); if (err) goto out; err = udp_local_get(comp->sock, &local); if (err) goto out; comp->lport = sa_port(&local); out: if (err) mem_deref(comp); else *cp = comp; return err; } int icem_comp_set_default_cand(struct icem_comp *comp) { struct ice_cand *cand; if (!comp) return EINVAL; cand = cand_default(&comp->icem->lcandl, comp->id); if (!cand) return ENOENT; mem_deref(comp->def_lcand); comp->def_lcand = mem_ref(cand); return 0; } void icem_comp_set_default_rcand(struct icem_comp *comp, struct ice_cand *rcand) { if (!comp) return; icecomp_printf(comp, "Set default remote candidate: %s:%J\n", ice_cand_type2name(rcand->type), &rcand->addr); mem_deref(comp->def_rcand); comp->def_rcand = mem_ref(rcand); if (comp->turnc) { icecomp_printf(comp, "Add TURN Channel to peer %J\n", &rcand->addr); (void)turnc_add_chan(comp->turnc, &rcand->addr, NULL, NULL); } } void icem_comp_set_selected(struct icem_comp *comp, struct ice_candpair *cp) { if (!comp || !cp) return; if (cp->state != ICE_CANDPAIR_SUCCEEDED) { DEBUG_WARNING("{%s.%u} set_selected: invalid state '%s'" " [%H]\n", comp->icem->name, comp->id, ice_candpair_state2name(cp->state), icem_candpair_debug, cp); } mem_deref(comp->cp_sel); comp->cp_sel = mem_ref(cp); } struct icem_comp *icem_comp_find(const struct icem *icem, unsigned compid) { struct le *le; if (!icem) return NULL; for (le = icem->compl.head; le; le = le->next) { struct icem_comp *comp = le->data; if (comp->id == compid) return comp; } return NULL; } static void timeout(void *arg) { struct icem_comp *comp = arg; struct ice_candpair *cp; tmr_start(&comp->tmr_ka, ICE_DEFAULT_Tr * 1000 + rand_u16() % 1000, timeout, comp); /* find selected candidate-pair */ cp = comp->cp_sel; if (!cp) return; (void)stun_indication(comp->icem->proto, comp->sock, &cp->rcand->addr, (cp->lcand->type == ICE_CAND_TYPE_RELAY) ? 4 : 0, STUN_METHOD_BINDING, NULL, 0, true, 0); } void icem_comp_keepalive(struct icem_comp *comp, bool enable) { if (!comp) return; if (enable) { tmr_start(&comp->tmr_ka, ICE_DEFAULT_Tr * 1000, timeout, comp); } else { tmr_cancel(&comp->tmr_ka); } } void icecomp_printf(struct icem_comp *comp, const char *fmt, ...) { va_list ap; if (!comp || !comp->icem->conf.debug) return; va_start(ap, fmt); (void)re_printf("{%11s.%u} %v", comp->icem->name, comp->id, fmt, &ap); va_end(ap); } int icecomp_debug(struct re_printf *pf, const struct icem_comp *comp) { if (!comp) return 0; return re_hprintf(pf, "id=%u ldef=%J rdef=%J concluded=%d", comp->id, comp->def_lcand ? &comp->def_lcand->addr : NULL, comp->def_rcand ? &comp->def_rcand->addr : NULL, comp->concluded); } int icem_set_turn_client(struct icem *icem, unsigned compid, struct turnc *turnc) { struct icem_comp *comp; comp = icem_comp_find(icem, compid); if (!comp) return ENOENT; comp->turnc = mem_deref(comp->turnc); if (turnc) comp->turnc = mem_ref(turnc); return 0; } ================================================ FILE: src/ice/connchk.c ================================================ /** * @file connchk.c ICE Connectivity Checks * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "connchk" #define DEBUG_LEVEL 5 #include static void pace_next(struct icem *icem) { if (icem->state != ICE_CHECKLIST_RUNNING) return; icem_conncheck_schedule_check(icem); if (icem->state != ICE_CHECKLIST_RUNNING) return; icem_checklist_update(icem); } /** * Constructing a Valid Pair * * @return The valid pair */ static struct ice_candpair *construct_valid_pair(struct icem *icem, struct ice_candpair *cp, const struct sa *mapped, const struct sa *dest) { struct ice_cand *lcand, *rcand; struct ice_candpair *cp2; int err; lcand = icem_cand_find(&icem->lcandl, cp->lcand->compid, mapped); rcand = icem_cand_find(&icem->rcandl, cp->rcand->compid, dest); if (!lcand) { DEBUG_WARNING("no such local candidate: %J\n", mapped); return NULL; } if (!rcand) { DEBUG_WARNING("no such remote candidate: %J\n", dest); return NULL; } /* New candidate? -- implicit success */ if (lcand != cp->lcand || rcand != cp->rcand) { if (lcand != cp->lcand) { icecomp_printf(cp->comp, "New local candidate for mapped %J\n", mapped); } if (rcand != cp->rcand) { icecomp_printf(cp->comp, "New remote candidate for dest %J\n", dest); } /* The original candidate pair is set to 'Failed' because * the implicitly discovered pair is 'better'. * This happens for UAs behind NAT where the original * pair is of type 'host' and the implicit pair is 'srflx' */ icem_candpair_make_valid(cp); cp2 = icem_candpair_find(&icem->validl, lcand, rcand); if (cp2) return cp2; err = icem_candpair_clone(&cp2, cp, lcand, rcand); if (err) return NULL; icem_candpair_make_valid(cp2); /*icem_candpair_failed(cp, EINTR, 0);*/ return cp2; } else { /* Add to VALID LIST, the pair that generated the check */ icem_candpair_make_valid(cp); return cp; } } static void handle_success(struct icem *icem, struct ice_candpair *cp, const struct sa *laddr) { if (!icem_cand_find(&icem->lcandl, cp->lcand->compid, laddr)) { int err; icecomp_printf(cp->comp, "adding local PRFLX Candidate: %J\n", laddr); err = icem_lcand_add(icem, cp->lcand, ICE_CAND_TYPE_PRFLX, laddr); if (err) { DEBUG_WARNING("failed to add PRFLX: %m\n", err); } } cp = construct_valid_pair(icem, cp, laddr, &cp->rcand->addr); if (!cp) { DEBUG_WARNING("{%s} no valid candidate pair for %J\n", icem->name, laddr); return; } icem_candpair_make_valid(cp); icem_comp_set_selected(cp->comp, cp); cp->nominated = true; #if 0 /* stop conncheck now -- conclude */ icem_conncheck_stop(icem, 0); #endif } #if ICE_TRACE static int print_err(struct re_printf *pf, const int *err) { if (err && *err) return re_hprintf(pf, " (%m)", *err); return 0; } #endif static void stunc_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct ice_candpair *cp = arg; struct icem *icem = cp->icem; struct stun_attr *attr; (void)reason; #if ICE_TRACE icecomp_printf(cp->comp, "Rx %H <--- %H '%u %s'%H\n", icem_cand_print, cp->lcand, icem_cand_print, cp->rcand, scode, reason, print_err, &err); #endif if (err) { icem_candpair_failed(cp, err, scode); goto out; } switch (scode) { case 0: /* Success case */ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr) { DEBUG_WARNING("no XOR-MAPPED-ADDR in response\n"); icem_candpair_failed(cp, EBADMSG, 0); break; } handle_success(icem, cp, &attr->v.sa); break; case 487: /* Role Conflict */ ice_switch_local_role(icem); (void)icem_conncheck_send(cp, false, true); break; default: DEBUG_WARNING("{%s.%u} STUN Response: %u %s\n", icem->name, cp->comp->id, scode, reason); icem_candpair_failed(cp, err, scode); break; } out: pace_next(icem); } int icem_conncheck_send(struct ice_candpair *cp, bool use_cand, bool trigged) { struct ice_cand *lcand; struct icem *icem; char username_buf[64]; size_t presz = 0; uint32_t prio_prflx; uint16_t ctrl_attr; int err = 0; if (!cp) return EINVAL; lcand = cp->lcand; icem = cp->icem; if (!str_isset(icem->rufrag)) { DEBUG_WARNING("send: name='%s' no remote ufrag" " [use=%d, trig=%d]\n", icem->name, use_cand, trigged); return EPROTO; } icem_candpair_set_state(cp, ICE_CANDPAIR_INPROGRESS); (void)re_snprintf(username_buf, sizeof(username_buf), "%s:%s", icem->rufrag, icem->lufrag); /* PRIORITY and USE-CANDIDATE */ prio_prflx = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, lcand->compid); switch (icem->lrole) { case ICE_ROLE_CONTROLLING: ctrl_attr = STUN_ATTR_CONTROLLING; break; case ICE_ROLE_CONTROLLED: ctrl_attr = STUN_ATTR_CONTROLLED; if (use_cand) { DEBUG_WARNING("send: use_cand=true, but" " role is controlled (trigged=%d)" " [%H]\n", trigged, icem_candpair_debug, cp); return EINVAL; } break; default: return EINVAL; } #if ICE_TRACE icecomp_printf(cp->comp, "Tx %H ---> %H (%s) %s %s\n", icem_cand_print, cp->lcand, icem_cand_print, cp->rcand, ice_candpair_state2name(cp->state), use_cand ? "[USE]" : "", trigged ? "[Trigged]" : ""); #else (void)trigged; #endif /* A connectivity check MUST utilize the STUN short term credential mechanism. */ /* The password is equal to the password provided by the peer */ if (!icem->rpwd) { DEBUG_WARNING("send: no remote password!\n"); } if (cp->ct_conn) { DEBUG_WARNING("send_req: CONNCHECK already Pending!\n"); return EBUSY; } switch (lcand->type) { case ICE_CAND_TYPE_RELAY: /* Creating Permissions for Relayed Candidates */ err = turnc_add_chan(cp->comp->turnc, &cp->rcand->addr, NULL, NULL); if (err) { DEBUG_WARNING("add channel: %m\n", err); break; } presz = 4; /*@fallthrough@*/ case ICE_CAND_TYPE_HOST: case ICE_CAND_TYPE_SRFLX: case ICE_CAND_TYPE_PRFLX: cp->ct_conn = mem_deref(cp->ct_conn); err = stun_request(&cp->ct_conn, icem->stun, icem->proto, cp->comp->sock, &cp->rcand->addr, presz, STUN_METHOD_BINDING, (uint8_t *)icem->rpwd, str_len(icem->rpwd), true, stunc_resp_handler, cp, 4, STUN_ATTR_USERNAME, username_buf, STUN_ATTR_PRIORITY, &prio_prflx, ctrl_attr, &icem->tiebrk, STUN_ATTR_USE_CAND, use_cand ? &use_cand : 0); break; default: DEBUG_WARNING("unknown candidate type %d\n", lcand->type); err = EINVAL; break; } return err; } static void abort_ice(struct icem *icem, int err) { icem->state = ICE_CHECKLIST_FAILED; tmr_cancel(&icem->tmr_pace); if (icem->chkh) { icem->chkh(err, icem->lrole == ICE_ROLE_CONTROLLING, icem->arg); } icem->chkh = NULL; } static void do_check(struct ice_candpair *cp) { int err; err = icem_conncheck_send(cp, false, false); if (err) { icem_candpair_failed(cp, err, 0); if (err == ENOMEM) { abort_ice(cp->icem, err); } else { pace_next(cp->icem); } } } /** * Scheduling Checks * * @param icem ICE Media object */ void icem_conncheck_schedule_check(struct icem *icem) { struct ice_candpair *cp; /* Find the highest priority pair in that check list that is in the Waiting state. */ cp = icem_candpair_find_st(&icem->checkl, 0, ICE_CANDPAIR_WAITING); if (cp) { do_check(cp); return; } /* If there is no such pair: */ /* Find the highest priority pair in that check list that is in the Frozen state. */ cp = icem_candpair_find_st(&icem->checkl, 0, ICE_CANDPAIR_FROZEN); if (cp) { /* If there is such a pair: */ /* Unfreeze the pair. Perform a check for that pair, causing its state to transition to In-Progress. */ do_check(cp); return; } /* If there is no such pair: */ /* Terminate the timer for that check list. */ #if 0 icem->state = ICE_CHECKLIST_COMPLETED; #endif } static void pace_timeout(void *arg) { struct icem *icem = arg; pace_next(icem); } static void rcand_wait_timeout(void *arg) { struct icem *icem = arg; /* Avoid long startup delay */ icem->rcand_wait = false; icem_printf(icem, "conncheck_start: " "mDNS timeout for remote candidate...\n"); icem_conncheck_start(icem); } /** * Scheduling Checks * * @param icem ICE Media object * * @return 0 if success, otherwise errorcode */ int icem_conncheck_start(struct icem *icem) { int err; if (!icem) return EINVAL; if (icem->rcand_wait) { icem_printf(icem, "conncheck_start: " "waiting mDNS for remote candidate...\n"); tmr_start(&icem->tmr_rcand, 100, rcand_wait_timeout, icem); return 0; } err = icem_checklist_form(icem); if (err) return err; icem->state = ICE_CHECKLIST_RUNNING; icem_printf(icem, "starting connectivity checks" " with %u candidate pairs\n", list_count(&icem->checkl)); /* add some delay, to wait for call to be 'established' */ tmr_start(&icem->tmr_pace, 0, pace_timeout, icem); return 0; } void icem_conncheck_continue(struct icem *icem) { if (!tmr_isrunning(&icem->tmr_pace)) tmr_start(&icem->tmr_pace, 1, pace_timeout, icem); } /** * Stop checklist, cancel all connectivity checks * * @param icem ICE Media object * @param err Error code */ void icem_conncheck_stop(struct icem *icem, int err) { struct le *le; icem->state = err ? ICE_CHECKLIST_FAILED : ICE_CHECKLIST_COMPLETED; tmr_cancel(&icem->tmr_pace); for (le = icem->checkl.head; le; le = le->next) { struct ice_candpair *cp = le->data; if (!icem_candpair_iscompleted(cp)) { icem_candpair_cancel(cp); icem_candpair_failed(cp, EINTR, 0); } } icem_checklist_update(icem); } ================================================ FILE: src/ice/ice.h ================================================ /** * @file ice.h Internal Interface to ICE * * Copyright (C) 2010 Creytiv.com */ #ifndef RELEASE #define ICE_TRACE 1 /**< Trace connectivity checks */ #endif enum ice_checkl_state { ICE_CHECKLIST_NULL = -1, ICE_CHECKLIST_RUNNING, ICE_CHECKLIST_COMPLETED, ICE_CHECKLIST_FAILED }; /** ICE protocol values */ enum { ICE_DEFAULT_Tr = 15, /**< Keepalive interval [s] */ ICE_DEFAULT_Ta_RTP = 20, /**< Pacing interval RTP [ms] */ ICE_DEFAULT_Ta_NON_RTP = 500, /**< Pacing interval [ms] */ ICE_DEFAULT_RTO_RTP = 100, /**< Retransmission TimeOut RTP [ms] */ ICE_DEFAULT_RTO_NONRTP = 500, /**< Retransmission TimeOut [ms] */ ICE_DEFAULT_RC = 7 /**< Retransmission count */ }; /** Defines a media-stream component */ struct icem_comp { struct le le; /**< Linked-list element */ struct icem *icem; /**< Parent ICE media */ struct ice_cand *def_lcand; /**< Default local candidate */ struct ice_cand *def_rcand; /**< Default remote candidate */ struct ice_candpair *cp_sel; /**< Selected candidate-pair */ struct udp_helper *uh; /**< UDP helper */ void *sock; /**< Transport socket */ uint16_t lport; /**< Local port number */ unsigned id; /**< Component ID */ bool concluded; /**< Concluded flag */ struct turnc *turnc; /**< TURN Client */ struct tmr tmr_ka; /**< Keep-alive timer */ }; /** Defines an ICE media-stream */ struct icem { struct ice_conf conf; /**< ICE Configuration */ struct stun *stun; /**< STUN Transport */ struct sa stun_srv; /**< STUN Server IP address and port */ struct list lcandl; /**< List of local candidates */ struct list rcandl; /**< List of remote candidates */ struct list checkl; /**< Check List of cand pairs (sorted) */ struct list validl; /**< Valid List of cand pairs (sorted) */ uint64_t tiebrk; /**< Tie-break value for roleconflict */ bool mismatch; /**< ICE mismatch flag */ bool rmode_lite; /**< Remote mode is Lite */ enum ice_role lrole; /**< Local role */ struct tmr tmr_pace; /**< Timer for pacing STUN requests */ struct tmr tmr_rcand; /**< Timer for remote candidate wait */ int proto; /**< Transport protocol */ int layer; /**< Protocol layer */ enum ice_checkl_state state; /**< State of the checklist */ struct list compl; /**< ICE media components */ char *lufrag; /**< Local Username fragment */ char *lpwd; /**< Local Password */ char *rufrag; /**< Remote Username fragment */ char *rpwd; /**< Remote Password */ ice_connchk_h *chkh; /**< Connectivity check handler */ void *arg; /**< Handler argument */ char name[32]; /**< Name of the media stream */ bool rcand_wait; /**< Waiting for remote candidate */ }; /** Defines a candidate */ struct ice_cand { struct le le; /**< List element */ enum ice_cand_type type; /**< Candidate type */ uint32_t prio; /**< Priority of this candidate */ char *foundation; /**< Foundation */ unsigned compid; /**< Component ID (1-256) */ struct sa rel; /**< Related IP address and port number */ struct sa addr; /**< Transport address */ enum ice_transp transp; /**< Transport protocol */ /* extra for local */ struct ice_cand *base; /**< Links to base candidate, if any */ char *ifname; /**< Network interface, for diagnostics */ }; /** Defines a candidate pair */ struct ice_candpair { struct le le; /**< List element */ struct icem *icem; /**< Pointer to parent ICE media */ struct icem_comp *comp; /**< Pointer to media-stream component */ struct ice_cand *lcand; /**< Local candidate */ struct ice_cand *rcand; /**< Remote candidate */ bool def; /**< Default flag */ bool valid; /**< Valid flag */ bool nominated; /**< Nominated flag */ enum ice_candpair_state state;/**< Candidate pair state */ uint64_t pprio; /**< Pair priority */ struct stun_ctrans *ct_conn; /**< STUN Transaction for conncheck */ int err; /**< Saved error code, if failed */ uint16_t scode; /**< Saved STUN code, if failed */ }; /* cand */ int icem_rcand_add(struct icem *icem, enum ice_cand_type type, unsigned compid, uint32_t prio, const struct sa *addr, const struct sa *rel_addr, const struct pl *foundation); int icem_rcand_add_prflx(struct ice_cand **rcp, struct icem *icem, unsigned compid, uint32_t prio, const struct sa *addr); struct ice_cand *icem_lcand_find_checklist(const struct icem *icem, unsigned compid); int icem_cands_debug(struct re_printf *pf, const struct list *lst); int icem_cand_print(struct re_printf *pf, const struct ice_cand *cand); /* candpair */ int icem_candpair_alloc(struct ice_candpair **cpp, struct icem *icem, struct ice_cand *lcand, struct ice_cand *rcand); int icem_candpair_clone(struct ice_candpair **cpp, struct ice_candpair *cp0, struct ice_cand *lcand, struct ice_cand *rcand); void icem_candpair_prio_order(struct list *lst); void icem_candpair_cancel(struct ice_candpair *cp); void icem_candpair_make_valid(struct ice_candpair *cp); void icem_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode); void icem_candpair_set_state(struct ice_candpair *cp, enum ice_candpair_state state); void icem_candpairs_flush(struct list *lst, enum ice_cand_type type, unsigned compid); bool icem_candpair_iscompleted(const struct ice_candpair *cp); bool icem_candpair_cmp(const struct ice_candpair *cp1, const struct ice_candpair *cp2); bool icem_candpair_cmp_fnd(const struct ice_candpair *cp1, const struct ice_candpair *cp2); struct ice_candpair *icem_candpair_find(const struct list *lst, const struct ice_cand *lcand, const struct ice_cand *rcand); struct ice_candpair *icem_candpair_find_st(const struct list *lst, unsigned compid, enum ice_candpair_state state); struct ice_candpair *icem_candpair_find_compid(const struct list *lst, unsigned compid); struct ice_candpair *icem_candpair_find_rcand(struct icem *icem, const struct ice_cand *rcand); int icem_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp); int icem_candpairs_debug(struct re_printf *pf, const struct list *list); /* stun server */ int icem_stund_recv(struct icem_comp *comp, const struct sa *src, struct stun_msg *req, size_t presz); /* ICE media */ void icem_printf(struct icem *icem, const char *fmt, ...); /* Checklist */ int icem_checklist_form(struct icem *icem); void icem_checklist_update(struct icem *icem); /* component */ int icem_comp_alloc(struct icem_comp **cp, struct icem *icem, int id, void *sock); int icem_comp_set_default_cand(struct icem_comp *comp); void icem_comp_set_default_rcand(struct icem_comp *comp, struct ice_cand *rcand); void icem_comp_set_selected(struct icem_comp *comp, struct ice_candpair *cp); struct icem_comp *icem_comp_find(const struct icem *icem, unsigned compid); void icem_comp_keepalive(struct icem_comp *comp, bool enable); void icecomp_printf(struct icem_comp *comp, const char *fmt, ...); int icecomp_debug(struct re_printf *pf, const struct icem_comp *comp); /* conncheck */ void icem_conncheck_schedule_check(struct icem *icem); void icem_conncheck_continue(struct icem *icem); int icem_conncheck_send(struct ice_candpair *cp, bool use_cand, bool trigger); /* icestr */ const char *ice_checkl_state2name(enum ice_checkl_state cst); /* util */ typedef void * (list_unique_h)(struct le *le1, struct le *le2); uint64_t ice_calc_pair_prio(uint32_t g, uint32_t d); void ice_switch_local_role(struct icem *icem); uint32_t ice_list_unique(struct list *list, list_unique_h *uh); ================================================ FILE: src/ice/icem.c ================================================ /** * @file icem.c ICE Media stream * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "icem" #define DEBUG_LEVEL 5 #include /* * ICE Implementation as of RFC 5245 */ static const struct ice_conf conf_default = { ICE_DEFAULT_RTO_RTP, ICE_DEFAULT_RC, ICE_POLICY_ALL, false }; /** Determining Role */ static void ice_determine_role(struct icem *icem, enum ice_role role) { if (!icem) return; if (icem->rmode_lite) icem->lrole = ICE_ROLE_CONTROLLING; else icem->lrole = role; } static void icem_destructor(void *data) { struct icem *icem = data; tmr_cancel(&icem->tmr_rcand); tmr_cancel(&icem->tmr_pace); list_flush(&icem->compl); list_flush(&icem->validl); list_flush(&icem->checkl); list_flush(&icem->lcandl); list_flush(&icem->rcandl); mem_deref(icem->lufrag); mem_deref(icem->lpwd); mem_deref(icem->rufrag); mem_deref(icem->rpwd); mem_deref(icem->stun); } /** * Add a new ICE Media object to the ICE Session * * @param icemp Pointer to allocated ICE Media object * @param role Local ICE role * @param proto Transport protocol * @param layer Protocol stack layer * @param tiebrk Tie-breaker value, must be same for all media streams * @param lufrag Local username fragment * @param lpwd Local password * @param chkh Connectivity check handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int icem_alloc(struct icem **icemp, enum ice_role role, int proto, int layer, uint64_t tiebrk, const char *lufrag, const char *lpwd, ice_connchk_h *chkh, void *arg) { struct icem *icem; int err = 0; if (!icemp || !tiebrk || !lufrag || !lpwd) return EINVAL; if (str_len(lufrag) < 4 || str_len(lpwd) < 22) { DEBUG_WARNING("alloc: lufrag/lpwd is too short\n"); return EINVAL; } if (proto != IPPROTO_UDP) return EPROTONOSUPPORT; icem = mem_zalloc(sizeof(*icem), icem_destructor); if (!icem) return ENOMEM; icem->conf = conf_default; tmr_init(&icem->tmr_pace); tmr_init(&icem->tmr_rcand); list_init(&icem->lcandl); list_init(&icem->rcandl); list_init(&icem->checkl); list_init(&icem->validl); icem->layer = layer; icem->proto = proto; icem->state = ICE_CHECKLIST_NULL; icem->chkh = chkh; icem->arg = arg; icem->tiebrk = tiebrk; err = str_dup(&icem->lufrag, lufrag); err |= str_dup(&icem->lpwd, lpwd); if (err) goto out; ice_determine_role(icem, role); err = stun_alloc(&icem->stun, NULL, NULL, NULL); if (err) goto out; /* Update STUN Transport */ stun_conf(icem->stun)->rto = icem->conf.rto; stun_conf(icem->stun)->rc = icem->conf.rc; out: if (err) mem_deref(icem); else if (icemp) *icemp = icem; return err; } /** * Get the ICE Configuration * * @param icem ICE Media object * * @return ICE Configuration */ struct ice_conf *icem_conf(struct icem *icem) { return icem ? &icem->conf : NULL; } enum ice_role icem_local_role(const struct icem *icem) { return icem ? icem->lrole : ICE_ROLE_UNKNOWN; } void icem_set_conf(struct icem *icem, const struct ice_conf *conf) { if (!icem || !conf) return; icem->conf = *conf; if (icem->stun) { /* Update STUN Transport */ stun_conf(icem->stun)->rto = icem->conf.rto; stun_conf(icem->stun)->rc = icem->conf.rc; } } /** * Set the local role on the ICE Session * * @param icem ICE Media object * @param role Local ICE role */ void icem_set_role(struct icem *icem, enum ice_role role) { if (!icem) return; ice_determine_role(icem, role); } /** * Set the name of the ICE Media object, used for debugging * * @param icem ICE Media object * @param name Media name */ void icem_set_name(struct icem *icem, const char *name) { if (!icem) return; str_ncpy(icem->name, name, sizeof(icem->name)); } /** * Add a new component to the ICE Media object * * @param icem ICE Media object * @param compid Component ID * @param sock Application protocol socket * * @return 0 if success, otherwise errorcode */ int icem_comp_add(struct icem *icem, unsigned compid, void *sock) { struct icem_comp *comp; int err; if (!icem) return EINVAL; if (icem_comp_find(icem, compid)) return EALREADY; err = icem_comp_alloc(&comp, icem, compid, sock); if (err) return err; list_append(&icem->compl, &comp->le, comp); return 0; } static void *unique_handler(struct le *le1, struct le *le2) { struct ice_cand *c1 = le1->data, *c2 = le2->data; if (c1->base != c2->base || !sa_cmp(&c1->addr, &c2->addr, SA_ALL)) return NULL; /* remove candidate with lower priority */ return c1->prio < c2->prio ? c1 : c2; } /** * Eliminating Redundant Candidates * * @param icem ICE Media object */ void icem_cand_redund_elim(struct icem *icem) { uint32_t n; n = ice_list_unique(&icem->lcandl, unique_handler); if (n > 0) { icem_printf(icem, "redundant candidates eliminated: %u\n", n); } } /** * Get the Default Local Candidate * * @param icem ICE Media object * @param compid Component ID * * @return Default Local Candidate address if set, otherwise NULL */ const struct sa *icem_cand_default(struct icem *icem, unsigned compid) { const struct icem_comp *comp = icem_comp_find(icem, compid); if (!comp || !comp->def_lcand) return NULL; return &comp->def_lcand->addr; } /** * Verifying ICE Support and set default remote candidate * * @param icem ICE Media * @param compid Component ID * @param raddr Address of default remote candidate * * @return True if ICE is supported, otherwise false */ bool icem_verify_support(struct icem *icem, unsigned compid, const struct sa *raddr) { struct ice_cand *rcand; bool match; if (!icem) return false; rcand = icem_cand_find(&icem->rcandl, compid, raddr); match = rcand != NULL; if (!match) icem->mismatch = true; if (rcand) { icem_comp_set_default_rcand(icem_comp_find(icem, compid), rcand); } return match; } /** * Add a TURN Channel for the selected remote address * * @param icem ICE Media object * @param compid Component ID * @param raddr Remote network address * * @return 0 if success, otherwise errorcode */ int icem_add_chan(struct icem *icem, unsigned compid, const struct sa *raddr) { struct icem_comp *comp; if (!icem) return EINVAL; comp = icem_comp_find(icem, compid); if (!comp) return ENOENT; if (comp->turnc) { DEBUG_NOTICE("{%s.%u} Add TURN Channel to peer %J\n", comp->icem->name, comp->id, raddr); return turnc_add_chan(comp->turnc, raddr, NULL, NULL); } return 0; } static void purge_relayed(struct icem *icem, struct icem_comp *comp) { if (comp->turnc) { DEBUG_NOTICE("{%s.%u} purge local RELAY candidates\n", icem->name, comp->id); } /* * Purge all Candidate-Pairs where the Local candidate * is of type "Relay" */ icem_candpairs_flush(&icem->checkl, ICE_CAND_TYPE_RELAY, comp->id); icem_candpairs_flush(&icem->validl, ICE_CAND_TYPE_RELAY, comp->id); comp->turnc = mem_deref(comp->turnc); } /** * Update the ICE Media object * * @param icem ICE Media object */ void icem_update(struct icem *icem) { struct le *le; if (!icem) return; for (le = icem->compl.head; le; le = le->next) { struct icem_comp *comp = le->data; /* remove TURN client if not used by local "Selected" */ if (comp->cp_sel) { if (comp->cp_sel->lcand->type != ICE_CAND_TYPE_RELAY) purge_relayed(icem, comp); } } } /** * Get the ICE Mismatch flag of the ICE Media object * * @param icem ICE Media object * * @return True if ICE mismatch, otherwise false */ bool icem_mismatch(const struct icem *icem) { return icem ? icem->mismatch : true; } /** * Print debug information for the ICE Media * * @param pf Print function for debug output * @param icem ICE Media object * * @return 0 if success, otherwise errorcode */ int icem_debug(struct re_printf *pf, const struct icem *icem) { struct le *le; int err = 0; if (!icem) return 0; err |= re_hprintf(pf, "----- ICE Media <%s> -----\n", icem->name); err |= re_hprintf(pf, " local_mode=Full, remote_mode=%s", icem->rmode_lite ? "Lite" : "Full"); err |= re_hprintf(pf, ", local_role=%s\n", ice_role2name(icem->lrole)); err |= re_hprintf(pf, " local_ufrag=\"%s\" local_pwd=\"%s\"\n", icem->lufrag, icem->lpwd); err |= re_hprintf(pf, " Components: (%u)\n", list_count(&icem->compl)); for (le = icem->compl.head; le; le = le->next) { struct icem_comp *comp = le->data; err |= re_hprintf(pf, " %H\n", icecomp_debug, comp); } err |= re_hprintf(pf, " Local Candidates: %H", icem_cands_debug, &icem->lcandl); err |= re_hprintf(pf, " Remote Candidates: %H", icem_cands_debug, &icem->rcandl); err |= re_hprintf(pf, " Check list: [state=%s]%H", ice_checkl_state2name(icem->state), icem_candpairs_debug, &icem->checkl); err |= re_hprintf(pf, " Valid list: %H", icem_candpairs_debug, &icem->validl); err |= stun_debug(pf, icem->stun); return err; } /** * Get the list of Local Candidates (struct cand) * * @param icem ICE Media object * * @return List of Local Candidates */ struct list *icem_lcandl(const struct icem *icem) { return icem ? (struct list *)&icem->lcandl : NULL; } /** * Get the list of Remote Candidates (struct cand) * * @param icem ICE Media object * * @return List of Remote Candidates */ struct list *icem_rcandl(const struct icem *icem) { return icem ? (struct list *)&icem->rcandl : NULL; } /** * Get the checklist of Candidate Pairs * * @param icem ICE Media object * * @return Checklist (struct ice_candpair) */ struct list *icem_checkl(const struct icem *icem) { return icem ? (struct list *)&icem->checkl : NULL; } /** * Get the list of valid Candidate Pairs * * @param icem ICE Media object * * @return Validlist (struct ice_candpair) */ struct list *icem_validl(const struct icem *icem) { return icem ? (struct list *)&icem->validl : NULL; } int icem_comps_set_default_cand(struct icem *icem) { struct le *le; int err = 0; if (!icem) return EINVAL; for (le = icem->compl.head; le; le = le->next) { struct icem_comp *comp = le->data; err |= icem_comp_set_default_cand(comp); } return err; } struct stun *icem_stun(struct icem *icem) { return icem ? icem->stun : NULL; } void icem_printf(struct icem *icem, const char *fmt, ...) { va_list ap; if (!icem || !icem->conf.debug) return; va_start(ap, fmt); (void)re_printf("{%11s. } %v", icem->name, fmt, &ap); va_end(ap); } /** * Check if remote ICE candidates are ready * * @param icem ICE Media object * * @return true if ready otherwise false */ bool icem_rcand_ready(struct icem *icem) { if (!icem) return false; if (icem->rcand_wait) return true; return !list_isempty(&icem->rcandl); } ================================================ FILE: src/ice/icesdp.c ================================================ /** * @file icesdp.c SDP Attributes for ICE * * Copyright (C) 2010 Creytiv.com */ #ifndef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "icesdp" #define DEBUG_LEVEL 5 #include const char ice_attr_cand[] = "candidate"; const char ice_attr_remote_cand[] = "remote-candidates"; const char ice_attr_lite[] = "ice-lite"; const char ice_attr_ufrag[] = "ice-ufrag"; const char ice_attr_pwd[] = "ice-pwd"; const char ice_attr_mismatch[] = "ice-mismatch"; static const char rel_addr_str[] = "raddr"; static const char rel_port_str[] = "rport"; struct rcand { int ai_family; struct icem *icem; enum ice_cand_type type; unsigned cid; uint32_t prio; uint32_t port; struct sa caddr; struct sa rel_addr; struct pl foundation; char domain[128]; }; /* Encode SDP Attributes */ static const char *transp_name(enum ice_transp transp) { switch (transp) { case ICE_TRANSP_UDP: return "UDP"; default: return "???"; } } static enum ice_transp transp_resolve(const struct pl *transp) { if (!pl_strcasecmp(transp, "UDP")) return ICE_TRANSP_UDP; return ICE_TRANSP_NONE; } /** * Encode SDP candidate attribute * * @param pf Print function * @param cand Candidate to encode * * @return 0 if success, otherwise errorcode */ int ice_cand_encode(struct re_printf *pf, const struct ice_cand *cand) { int err; err = re_hprintf(pf, "%s %u %s %u %j %u typ %s", cand->foundation, cand->compid, transp_name(cand->transp), cand->prio, &cand->addr, sa_port(&cand->addr), ice_cand_type2name(cand->type)); if (sa_isset(&cand->rel, SA_ADDR)) err |= re_hprintf(pf, " raddr %j", &cand->rel); if (sa_isset(&cand->rel, SA_PORT)) err |= re_hprintf(pf, " rport %u", sa_port(&cand->rel)); return err; } /** * Check if remote candidates are available * * @param icem ICE Media object * * @return True if available, otherwise false */ bool ice_remotecands_avail(const struct icem *icem) { if (!icem) return false; return icem->lrole == ICE_ROLE_CONTROLLING && icem->state == ICE_CHECKLIST_COMPLETED; } /** * Encode the SDP "remote-candidates" Attribute * * @param pf Print function * @param icem ICE Media object * * @return 0 if success, otherwise errorcode */ int ice_remotecands_encode(struct re_printf *pf, const struct icem *icem) { struct le *le; int err = 0; if (!icem) return EINVAL; for (le = icem->rcandl.head; le && !err; le = le->next) { const struct ice_cand *rcand = le->data; err = re_hprintf(pf, "%s%d %j %u", icem->rcandl.head==le ? "" : " ", rcand->compid, &rcand->addr, sa_port(&rcand->addr)); } return err; } /* Decode SDP Attributes */ static int ufrag_decode(struct icem *icem, const char *value) { char *ufrag = NULL; int err; err = str_dup(&ufrag, value); if (err) return err; mem_deref(icem->rufrag); icem->rufrag = mem_ref(ufrag); mem_deref(ufrag); return 0; } static int pwd_decode(struct icem *icem, const char *value) { char *pwd = NULL; int err; err = str_dup(&pwd, value); if (err) return err; mem_deref(icem->rpwd); icem->rpwd = mem_ref(pwd); mem_deref(pwd); return 0; } static int media_ufrag_decode(struct icem *icem, const char *value) { icem->rufrag = mem_deref(icem->rufrag); return str_dup(&icem->rufrag, value); } static int media_pwd_decode(struct icem *icem, const char *value) { icem->rpwd = mem_deref(icem->rpwd); return str_dup(&icem->rpwd, value); } static int getaddr_rcand(void *arg) { struct rcand *rcand = arg; struct addrinfo *res, *res0 = NULL; struct addrinfo hints = {.ai_flags = AI_ADDRCONFIG, .ai_family = rcand->ai_family}; int err; err = getaddrinfo(rcand->domain, NULL, &hints, &res0); if (err) return EADDRNOTAVAIL; for (res = res0; res; res = res->ai_next) { err = sa_set_sa(&rcand->caddr, res->ai_addr); if (err) continue; break; } sa_set_port(&rcand->caddr, rcand->port); freeaddrinfo(res0); return 0; } static void delayed_rcand(int err, void *arg) { struct rcand *rcand = arg; if (err) goto out; if (!rcand->icem->rcand_wait) { DEBUG_WARNING("late mDNS candidate: %s - %J\n", rcand->domain, rcand->caddr); goto out; } /* add only if not exist */ if (icem_cand_find(&rcand->icem->rcandl, rcand->cid, &rcand->caddr)) goto out; icem_rcand_add(rcand->icem, rcand->type, rcand->cid, rcand->prio, &rcand->caddr, &rcand->rel_addr, &rcand->foundation); out: mem_deref(rcand); } static void rcand_dealloc(void *arg) { struct rcand *rcand = arg; mem_deref(rcand->icem); mem_deref((char *)rcand->foundation.p); } static int cand_decode(struct icem *icem, const char *val) { struct pl foundation, compid, transp, prio, addr, port, cand_type; struct pl extra = pl_null; struct sa caddr, rel_addr; char type[8]; uint8_t cid; int err; sa_init(&rel_addr, AF_INET); err = re_regex(val, strlen(val), "[^ ]+ [0-9]+ [^ ]+ [0-9]+ [^ ]+ [0-9]+ typ [a-z]+[^]*", &foundation, &compid, &transp, &prio, &addr, &port, &cand_type, &extra); if (err) return err; if (ICE_TRANSP_NONE == transp_resolve(&transp)) { DEBUG_INFO("<%s> ignoring candidate with" " unknown transport=%r (%r:%r)\n", icem->name, &transp, &cand_type, &addr); return 0; } if (pl_isset(&extra)) { struct pl name, value; /* Loop through " SP attr SP value" pairs */ while (!re_regex(extra.p, extra.l, " [^ ]+ [^ ]+", &name, &value)) { pl_advance(&extra, value.p + value.l - extra.p); if (0 == pl_strcasecmp(&name, rel_addr_str)) { err = sa_set(&rel_addr, &value, sa_port(&rel_addr)); if (err) break; } else if (0 == pl_strcasecmp(&name, rel_port_str)) { sa_set_port(&rel_addr, pl_u32(&value)); } } } (void)pl_strcpy(&cand_type, type, sizeof(type)); cid = pl_u32(&compid); /* check for mdns .local address */ if (pl_strstr(&addr, ".local") != NULL) { /* try non blocking getaddr mdns resolution */ icem_printf(icem, "mDNS remote cand: %r\n", &addr); icem->rcand_wait = true; /* AF_INET IPv4 candidate */ struct rcand *rcand = mem_zalloc(sizeof(struct rcand), rcand_dealloc); if (!rcand) return ENOMEM; rcand->ai_family = AF_INET; rcand->icem = mem_ref(icem); rcand->type = ice_cand_name2type(type); rcand->cid = cid; rcand->prio = pl_u32(&prio); rcand->port = pl_u32(&port); rcand->rel_addr = rel_addr; pl_dup(&rcand->foundation, &foundation); (void)pl_strcpy(&addr, rcand->domain, sizeof(rcand->domain)); err = re_thread_async(getaddr_rcand, delayed_rcand, rcand); if (err) mem_deref(rcand); /* AF_INET6 IPv6 candidate * mDNS resolving can lead to long timeouts (~5s), so it's * better to resolve IPv4 and IPv6 separately to avoid long ice * startup delays. */ struct rcand *rcand6 = mem_zalloc(sizeof(struct rcand), rcand_dealloc); if (!rcand6) return ENOMEM; rcand6->ai_family = AF_INET6; rcand6->icem = mem_ref(icem); rcand6->type = ice_cand_name2type(type); rcand6->cid = cid; rcand6->prio = pl_u32(&prio); rcand6->port = pl_u32(&port); rcand6->rel_addr = rel_addr; pl_dup(&rcand6->foundation, &foundation); (void)pl_strcpy(&addr, rcand6->domain, sizeof(rcand6->domain)); err = re_thread_async(getaddr_rcand, delayed_rcand, rcand6); if (err) mem_deref(rcand6); return err; } err = sa_set(&caddr, &addr, pl_u32(&port)); if (err) return err; /* add only if not exist */ if (icem_cand_find(&icem->rcandl, cid, &caddr)) return 0; return icem_rcand_add(icem, ice_cand_name2type(type), cid, pl_u32(&prio), &caddr, &rel_addr, &foundation); } /** * Decode SDP session attributes * * @param icem ICE Media object * @param name Name of the SDP attribute * @param value Value of the SDP attribute (optional) * * @return 0 if success, otherwise errorcode */ int ice_sdp_decode(struct icem *icem, const char *name, const char *value) { if (!icem) return EINVAL; if (0 == str_casecmp(name, ice_attr_lite)) { icem->rmode_lite = true; icem->lrole = ICE_ROLE_CONTROLLING; } else if (0 == str_casecmp(name, ice_attr_ufrag)) return ufrag_decode(icem, value); else if (0 == str_casecmp(name, ice_attr_pwd)) return pwd_decode(icem, value); return 0; } /** * Decode SDP media attributes * * @param icem ICE Media object * @param name Name of the SDP attribute * @param value Value of the SDP attribute (optional) * * @return 0 if success, otherwise errorcode */ int icem_sdp_decode(struct icem *icem, const char *name, const char *value) { if (!icem) return EINVAL; if (0 == str_casecmp(name, ice_attr_cand)) return cand_decode(icem, value); else if (0 == str_casecmp(name, ice_attr_mismatch)) icem->mismatch = true; else if (0 == str_casecmp(name, ice_attr_ufrag)) return media_ufrag_decode(icem, value); else if (0 == str_casecmp(name, ice_attr_pwd)) return media_pwd_decode(icem, value); return 0; } static const char *ice_tcptype_name(enum ice_tcptype tcptype) { switch (tcptype) { case ICE_TCP_ACTIVE: return "active"; case ICE_TCP_PASSIVE: return "passive"; case ICE_TCP_SO: return "so"; default: return "???"; } } static enum ice_tcptype ice_tcptype_resolve(const struct pl *pl) { if (0 == pl_strcasecmp(pl, "active")) return ICE_TCP_ACTIVE; if (0 == pl_strcasecmp(pl, "passive")) return ICE_TCP_PASSIVE; if (0 == pl_strcasecmp(pl, "so")) return ICE_TCP_SO; return (enum ice_tcptype)-1; } int ice_cand_attr_encode(struct re_printf *pf, const struct ice_cand_attr *cand) { int err = 0; if (!cand) return 0; err |= re_hprintf(pf, "%s %u %s %u %j %u typ %s", cand->foundation, cand->compid, net_proto2name(cand->proto), cand->prio, &cand->addr, sa_port(&cand->addr), ice_cand_type2name(cand->type)); if (sa_isset(&cand->rel_addr, SA_ADDR)) err |= re_hprintf(pf, " raddr %j", &cand->rel_addr); if (sa_isset(&cand->rel_addr, SA_PORT)) err |= re_hprintf(pf, " rport %u", sa_port(&cand->rel_addr)); if (cand->proto == IPPROTO_TCP) { err |= re_hprintf(pf, " tcptype %s", ice_tcptype_name(cand->tcptype)); } return err; } int ice_cand_attr_decode(struct ice_cand_attr *cand, const char *val) { struct pl pl_fnd, pl_compid, pl_transp, pl_prio, pl_addr, pl_port; struct pl pl_type, pl_raddr, pl_rport, pl_opt = PL_INIT; size_t len; char type[8]; int err; if (!cand || !val) return EINVAL; memset(cand, 0, sizeof(*cand)); len = str_len(val); err = re_regex(val, len, "[^ ]+ [0-9]+ [a-z]+ [0-9]+ [^ ]+ [0-9]+ typ [a-z]+" "[^]*", &pl_fnd, &pl_compid, &pl_transp, &pl_prio, &pl_addr, &pl_port, &pl_type, &pl_opt); if (err) return err; (void)pl_strcpy(&pl_fnd, cand->foundation, sizeof(cand->foundation)); if (0 == pl_strcasecmp(&pl_transp, "UDP")) cand->proto = IPPROTO_UDP; else if (0 == pl_strcasecmp(&pl_transp, "TCP")) cand->proto = IPPROTO_TCP; else cand->proto = 0; err = sa_set(&cand->addr, &pl_addr, pl_u32(&pl_port)); if (err) return err; cand->compid = pl_u32(&pl_compid); cand->prio = pl_u32(&pl_prio); (void)pl_strcpy(&pl_type, type, sizeof(type)); cand->type = ice_cand_name2type(type); /* optional */ if (0 == re_regex(pl_opt.p, pl_opt.l, "raddr [^ ]+ rport [0-9]+", &pl_raddr, &pl_rport)) { err = sa_set(&cand->rel_addr, &pl_raddr, pl_u32(&pl_rport)); if (err) return err; } if (cand->proto == IPPROTO_TCP) { struct pl tcptype; err = re_regex(pl_opt.p, pl_opt.l, "tcptype [^ ]+", &tcptype); if (err) return err; cand->tcptype = ice_tcptype_resolve(&tcptype); } return 0; } ================================================ FILE: src/ice/icestr.c ================================================ /** * @file icestr.c ICE Strings * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "ice.h" const char *ice_cand_type2name(enum ice_cand_type type) { switch (type) { case ICE_CAND_TYPE_HOST: return "host"; case ICE_CAND_TYPE_SRFLX: return "srflx"; case ICE_CAND_TYPE_PRFLX: return "prflx"; case ICE_CAND_TYPE_RELAY: return "relay"; default: return "???"; } } enum ice_cand_type ice_cand_name2type(const char *name) { if (0 == str_casecmp(name, "host")) return ICE_CAND_TYPE_HOST; if (0 == str_casecmp(name, "srflx")) return ICE_CAND_TYPE_SRFLX; if (0 == str_casecmp(name, "prflx")) return ICE_CAND_TYPE_PRFLX; if (0 == str_casecmp(name, "relay")) return ICE_CAND_TYPE_RELAY; return (enum ice_cand_type)-1; } const char *ice_role2name(enum ice_role role) { switch (role) { case ICE_ROLE_UNKNOWN: return "Unknown"; case ICE_ROLE_CONTROLLING: return "Controlling"; case ICE_ROLE_CONTROLLED: return "Controlled"; default: return "???"; } } const char *ice_candpair_state2name(enum ice_candpair_state st) { switch (st) { case ICE_CANDPAIR_FROZEN: return "Frozen"; case ICE_CANDPAIR_WAITING: return "Waiting"; case ICE_CANDPAIR_INPROGRESS: return "InProgress"; case ICE_CANDPAIR_SUCCEEDED: return "Succeeded"; case ICE_CANDPAIR_FAILED: return "Failed"; default: return "???"; } } const char *ice_checkl_state2name(enum ice_checkl_state cst) { switch (cst) { case ICE_CHECKLIST_NULL: return "(NULL)"; case ICE_CHECKLIST_RUNNING: return "Running"; case ICE_CHECKLIST_COMPLETED: return "Completed"; case ICE_CHECKLIST_FAILED: return "Failed"; default: return "???"; } } ================================================ FILE: src/ice/stunsrv.c ================================================ /** * @file stunsrv.c Basic STUN Server for Connectivity checks * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "stunsrv" #define DEBUG_LEVEL 5 #include static const char *sw = "ice stunsrv v" RE_VERSION " (" ARCH "/" OS ")"; static void triggered_check(struct icem *icem, struct ice_cand *lcand, struct ice_cand *rcand) { struct ice_candpair *cp = NULL; int err; if (lcand && rcand) cp = icem_candpair_find(&icem->checkl, lcand, rcand); if (cp) { switch (cp->state) { #if 0 /* TODO: I am not sure why we should cancel the * pending Connectivity check here. this * can lead to a deadlock situation where * both agents are stuck on sending * triggered checks on the same candidate pair */ case ICE_CANDPAIR_INPROGRESS: icem_candpair_cancel(cp); /*@fallthrough@*/ #endif case ICE_CANDPAIR_FAILED: icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING); /*@fallthrough@*/ case ICE_CANDPAIR_FROZEN: case ICE_CANDPAIR_WAITING: err = icem_conncheck_send(cp, false, true); if (err) { DEBUG_WARNING("triggered check failed\n"); } break; case ICE_CANDPAIR_SUCCEEDED: default: break; } } else { #if 0 err = icem_candpair_alloc(&cp, icem, lcand, rcand); if (err) { DEBUG_WARNING("failed to allocate candpair:" " lcand=%p rcand=%p (%m)\n", lcand, rcand, err); return; } icem_candpair_prio_order(&icem->checkl); icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING); (void)icem_conncheck_send(cp, false, true); #endif } } /* * 7.2.1. Additional Procedures for Full Implementations */ static int handle_stun_full(struct icem *icem, struct icem_comp *comp, const struct sa *src, uint32_t prio, bool use_cand, bool tunnel) { struct ice_cand *lcand = NULL, *rcand; struct ice_candpair *cp = NULL; int err; rcand = icem_cand_find(&icem->rcandl, comp->id, src); if (!rcand) { err = icem_rcand_add_prflx(&rcand, icem, comp->id, prio, src); if (err) return err; } cp = icem_candpair_find_rcand(icem, rcand); if (cp) lcand = cp->lcand; else lcand = icem_lcand_find_checklist(icem, comp->id); if (!lcand) { DEBUG_NOTICE("{%s.%u} local candidate not found" " (checklist=%u) (src=%J)\n", icem->name, comp->id, list_count(&icem->checkl), src); return 0; } triggered_check(icem, lcand, rcand); if (!cp) { cp = icem_candpair_find_rcand(icem, rcand); if (!cp) { DEBUG_WARNING("{%s.%u} candidate pair not found:" " source=%J\n", icem->name, comp->id, src); return 0; } } #if ICE_TRACE icecomp_printf(comp, "Rx Binding Request from %J via %s" " (candpair=%s) %s\n", src, tunnel ? "Tunnel" : "Socket", ice_candpair_state2name(cp->state), use_cand ? "[USE]" : ""); #else (void)tunnel; #endif /* 7.2.1.5. Updating the Nominated Flag */ if (use_cand) { if (icem->lrole == ICE_ROLE_CONTROLLED && cp->state == ICE_CANDPAIR_SUCCEEDED) { if (!cp->nominated) { icecomp_printf(comp, "setting NOMINATED" " flag on candpair [%H]\n", icem_candpair_debug, cp); } cp->nominated = true; } } return 0; } static int stunsrv_ereply(struct icem_comp *comp, const struct sa *src, size_t presz, const struct stun_msg *req, uint16_t scode, const char *reason) { struct icem *icem = comp->icem; return stun_ereply(icem->proto, comp->sock, src, presz, req, scode, reason, (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 1, STUN_ATTR_SOFTWARE, sw); } int icem_stund_recv(struct icem_comp *comp, const struct sa *src, struct stun_msg *req, size_t presz) { struct icem *icem = comp->icem; struct stun_attr *attr; struct pl lu, ru; enum ice_role rrole = ICE_ROLE_UNKNOWN; uint64_t tiebrk = 0; uint32_t prio_prflx; bool use_cand = false; int err; /* RFC 5389: Fingerprint errors are silently discarded */ err = stun_msg_chk_fingerprint(req); if (err) return err; err = stun_msg_chk_mi(req, (uint8_t *)icem->lpwd, strlen(icem->lpwd)); if (err) { if (err == EBADMSG) goto unauth; else goto badmsg; } attr = stun_msg_attr(req, STUN_ATTR_USERNAME); if (!attr) goto badmsg; err = re_regex(attr->v.username, strlen(attr->v.username), "[^:]+:[^]+", &lu, &ru); if (err) { DEBUG_WARNING("could not parse USERNAME attribute (%s)\n", attr->v.username); goto unauth; } if (pl_strcmp(&lu, icem->lufrag)) goto unauth; if (str_isset(icem->rufrag) && pl_strcmp(&ru, icem->rufrag)) goto unauth; attr = stun_msg_attr(req, STUN_ATTR_CONTROLLED); if (attr) { rrole = ICE_ROLE_CONTROLLED; tiebrk = attr->v.uint64; } attr = stun_msg_attr(req, STUN_ATTR_CONTROLLING); if (attr) { rrole = ICE_ROLE_CONTROLLING; tiebrk = attr->v.uint64; } if (rrole == icem->lrole) { if (icem->tiebrk >= tiebrk) ice_switch_local_role(icem); else goto conflict; } attr = stun_msg_attr(req, STUN_ATTR_PRIORITY); if (attr) prio_prflx = attr->v.uint32; else goto badmsg; attr = stun_msg_attr(req, STUN_ATTR_USE_CAND); if (attr) { use_cand = true; } if (rrole == ICE_ROLE_CONTROLLED && use_cand) { DEBUG_NOTICE("remote peer is Controlled and" " should not send USE-CANDIDATE\n"); } err = handle_stun_full(icem, comp, src, prio_prflx, use_cand, presz > 0); if (err) goto badmsg; return stun_reply(icem->proto, comp->sock, src, presz, req, (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 2, STUN_ATTR_XOR_MAPPED_ADDR, src, STUN_ATTR_SOFTWARE, sw); badmsg: return stunsrv_ereply(comp, src, presz, req, 400, "Bad Request"); unauth: return stunsrv_ereply(comp, src, presz, req, 401, "Unauthorized"); conflict: return stunsrv_ereply(comp, src, presz, req, 487, "Role Conflict"); } ================================================ FILE: src/ice/util.c ================================================ /** * @file ice/util.c ICE Utilities * * Copyright (C) 2010 Creytiv.com */ #include #ifdef HAVE_SYS_TIME_H #include #endif #ifndef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include "ice.h" #define DEBUG_MODULE "iceutil" #define DEBUG_LEVEL 5 #include enum { CAND_PRIO_RELAY = 0, CAND_PRIO_SRFLX = 100, CAND_PRIO_PRFLX = 110, CAND_PRIO_HOST = 126 }; static uint32_t type_prio(enum ice_cand_type type) { switch (type) { case ICE_CAND_TYPE_HOST: return CAND_PRIO_HOST; case ICE_CAND_TYPE_SRFLX: return CAND_PRIO_SRFLX; case ICE_CAND_TYPE_PRFLX: return CAND_PRIO_PRFLX; case ICE_CAND_TYPE_RELAY: return CAND_PRIO_RELAY; default: return 0; } } uint32_t ice_cand_calc_prio(enum ice_cand_type type, uint16_t lpref, unsigned compid) { return type_prio(type)<<24 | (uint32_t)lpref<<8 | (256 - compid); } /* * g = controlling agent * d = controlled agent pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) */ uint64_t ice_calc_pair_prio(uint32_t g, uint32_t d) { const uint64_t m = min(g, d); const uint64_t x = max(g, d); return (m<<32) + 2*x + (g>d?1:0); } void ice_switch_local_role(struct icem *icem) { enum ice_role new_role; if (ICE_ROLE_CONTROLLING == icem->lrole) new_role = ICE_ROLE_CONTROLLED; else new_role = ICE_ROLE_CONTROLLING; DEBUG_NOTICE("Switch local role from %s to %s\n", ice_role2name(icem->lrole), ice_role2name(new_role)); icem->lrole = new_role; #if 0 /* recompute pair priorities for all media streams */ for (le = icem->le.list->head; le; le = le->next) { icem = le->data; icem_candpair_prio_order(&icem->checkl); } #endif } /** * Remove duplicate elements from list, preserving order * * @param list Linked list * @param uh Unique handler (return object to remove) * * @return Number of elements removed * * @note: O (n ^ 2) */ uint32_t ice_list_unique(struct list *list, list_unique_h *uh) { struct le *le1 = list_head(list); uint32_t n = 0; while (le1 && le1 != list->tail) { struct le *le2 = le1->next; void *data = NULL; while (le2) { data = uh(le1, le2); le2 = le2->next; if (!data) continue; if (le1->data == data) break; else { data = mem_deref(data); ++n; } } le1 = le1->next; if (data) { mem_deref(data); ++n; } } return n; } ================================================ FILE: src/json/decode.c ================================================ /** * @file json/decode.c JSON decoder * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include #include #include #include #include static inline long double mypower10(uint64_t e) { long double p = 10, n = 1; while (e > 0) { if (e & 1) n *= p; p *= p; e >>= 1; } return n; } static bool is_string(struct pl *c, const struct pl *pl) { if (pl->l < 2) return false; if (pl->p[0] != '"'|| pl->p[pl->l-1] != '"') return false; c->p = pl->p + 1; c->l = pl->l - 2; return true; } static bool is_number(long double *d, bool *isfloat, const struct pl *pl) { bool neg = false, pos = false, frac = false, exp = false; long double v = 0, mul = 1; const char *p; int64_t e = 0; if (!pl->l) return false; p = &pl->p[pl->l - 1]; while (p >= pl->p) { const char ch = *p; --p; if (ch == 'e' || ch == 'E') { if (exp || frac) return false; exp = true; e = (int64_t)(neg ? -v : v); v = 0; mul = 1; neg = false; pos = false; } else if (pos || neg) { return false; } else if (ch == '.') { if (frac) return false; frac = true; v /= mul; mul = 1; } else if ('0' <= ch && ch <= '9') { v += mul * (ch - '0'); mul *= 10; } else if (ch == '-') { neg = true; } else if (ch == '+') { pos = true; } else { return false; } } *isfloat = (frac || exp); if (exp) { if (e < 0) v /= mypower10(-e); else v *= mypower10(e); } if (neg) v = -v; *d = v; return true; } static int decode_name(char **str, const struct pl *pl) { struct pl pls; if (!pl->p) return EBADMSG; if (!is_string(&pls, pl)) return EBADMSG; return re_sdprintf(str, "%H", utf8_decode, &pls); } static int decode_value(struct json_value *val, const struct pl *pl) { long double dbl; struct pl pls; bool isfloat; int err = 0; if (!pl->p) return EBADMSG; if (is_string(&pls, pl)) { err = re_sdprintf(&val->v.str, "%H", utf8_decode, &pls); val->type = JSON_STRING; } else if (is_number(&dbl, &isfloat, pl)) { if (isfloat) { val->type = JSON_DOUBLE; val->v.dbl = dbl; } else { val->type = JSON_INT; val->v.integer = (int64_t)dbl; } } else if (!pl_strcasecmp(pl, "false")) { val->v.boolean = false; val->type = JSON_BOOL; } else if (!pl_strcasecmp(pl, "true")) { val->v.boolean = true; val->type = JSON_BOOL; } else if (!pl_strcasecmp(pl, "null")) { val->type = JSON_NULL; } else { err = EBADMSG; } return err; } static int object_entry(const struct pl *pl_name, const struct pl *pl_val, json_object_entry_h *oeh, void *arg) { struct json_value val; char *name; int err; err = decode_name(&name, pl_name); if (err) return err; err = decode_value(&val, pl_val); if (err) goto out; if (oeh) err = oeh(name, &val, arg); if (val.type == JSON_STRING) mem_deref(val.v.str); out: mem_deref(name); return err; } static int array_entry(unsigned idx, const struct pl *pl_val, json_array_entry_h *aeh, void *arg) { struct json_value val; int err; err = decode_value(&val, pl_val); if (err) return err; if (aeh) err = aeh(idx, &val, arg); if (val.type == JSON_STRING) mem_deref(val.v.str); return err; } static int object_start(const struct pl *pl_name, unsigned idx, struct json_handlers *h) { char *name = NULL; int err = 0; if (pl_name->p) { err = decode_name(&name, pl_name); if (err) return err; } if (h->oh) err = h->oh(name, idx, h); mem_deref(name); return err; } static int array_start(const struct pl *pl_name, unsigned idx, struct json_handlers *h) { char *name = NULL; int err = 0; if (pl_name->p) { err = decode_name(&name, pl_name); if (err) return err; } if (h->ah) err = h->ah(name, idx, h); mem_deref(name); return err; } static inline int chkval(struct pl *val, const char *p) { if (!val->p || pp) return EINVAL; val->l = p - val->p; return 0; } static int _json_decode(const char **str, size_t *len, unsigned depth, unsigned maxdepth, json_object_h *oh, json_array_h *ah, json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg) { bool esc = false, inquot = false, inobj = false, inarray = false; struct pl name = PL_INIT, val = PL_INIT; size_t ws = 0; unsigned idx = 0; int err; for (; *len>0; ++(*str), --(*len)) { if (inquot) { if (esc) esc = false; else if (**str == '\"') inquot = false; else if (**str == '\\') esc = true; continue; } switch (**str) { case ':': if (!inobj || name.p || chkval(&val, *str - ws)) return EBADMSG; name = val; val = pl_null; break; case ',': if (chkval(&val, *str - ws)) break; if (inobj) { if (!name.p) return EBADMSG; err = object_entry(&name, &val, oeh, arg); if (err) return err; } else if (inarray) { err = array_entry(idx, &val, aeh, arg); if (err) return err; ++idx; } else return EBADMSG; name = pl_null; val = pl_null; break; case '{': if (inobj || inarray) { struct json_handlers h = {oh,ah,oeh,aeh,arg}; if (depth >= maxdepth) return EOVERFLOW; if (inobj && !name.p) return EBADMSG; err = object_start(&name, idx, &h); if (err) return err; name = pl_null; err = _json_decode(str, len, depth + 1, maxdepth, h.oh, h.ah, h.oeh, h.aeh, h.arg); if (err) return err; if (inarray) ++idx; } else { inobj = true; } break; case '[': if (inobj || inarray) { struct json_handlers h = {oh,ah,oeh,aeh,arg}; if (depth >= maxdepth) return EOVERFLOW; if (inobj && !name.p) return EBADMSG; err = array_start(&name, idx, &h); if (err) return err; name = pl_null; err = _json_decode(str, len, depth + 1, maxdepth, h.oh, h.ah, h.oeh, h.aeh, h.arg); if (err) return err; if (inarray) ++idx; } else { inarray = true; idx = 0; } break; case '}': if (!inobj) return EBADMSG; if (chkval(&val, *str - ws)) return 0; if (!name.p) return EBADMSG; return object_entry(&name, &val, oeh, arg); case ']': if (!inarray) return EBADMSG; if (chkval(&val, *str - ws)) return 0; return array_entry(idx, &val, aeh, arg); case ' ': case '\t': case '\r': case '\n': ++ws; break; default: if (val.p) break; if (**str == '\"') inquot = true; val.p = *str; val.l = 0; ws = 0; if (!inobj && !inarray) { val.l = *len; if (array_entry(idx, &val, aeh, arg)) val.l = 0; } break; } } if (inobj || inarray) return EBADMSG; return 0; } int json_decode(const char *str, size_t len, unsigned maxdepth, json_object_h *oh, json_array_h *ah, json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg) { if (!str) return EINVAL; return _json_decode(&str, &len, 0, maxdepth, oh, ah, oeh, aeh, arg); } ================================================ FILE: src/json/decode_odict.c ================================================ /** * @file json/decode_odict.c JSON odict decode * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include #include #include #include #include static int container_add(const char *name, unsigned idx, enum odict_type type, struct json_handlers *h) { struct odict *o = h->arg, *oc; char index[64]; int err; if (!name) { if (re_snprintf(index, sizeof(index), "%u", idx) < 0) return ENOMEM; name = index; } err = odict_alloc(&oc, hash_bsize(o->ht)); if (err) return err; err = odict_entry_add(o, name, type, oc); mem_deref(oc); h->arg = oc; return err; } static int object_handler(const char *name, unsigned idx, struct json_handlers *h) { return container_add(name, idx, ODICT_OBJECT, h); } static int array_handler(const char *name, unsigned idx, struct json_handlers *h) { return container_add(name, idx, ODICT_ARRAY, h); } static int entry_add(struct odict *o, const char *name, const struct json_value *val) { switch (val->type) { case JSON_STRING: return odict_entry_add(o, name, ODICT_STRING, val->v.str); case JSON_INT: return odict_entry_add(o, name, ODICT_INT, val->v.integer); case JSON_DOUBLE: return odict_entry_add(o, name, ODICT_DOUBLE, val->v.dbl); case JSON_BOOL: return odict_entry_add(o, name, ODICT_BOOL, val->v.boolean); case JSON_NULL: return odict_entry_add(o, name, ODICT_NULL); default: return ENOSYS; } } static int object_entry_handler(const char *name, const struct json_value *val, void *arg) { struct odict *o = arg; return entry_add(o, name, val); } static int array_entry_handler(unsigned idx, const struct json_value *val, void *arg) { struct odict *o = arg; char index[64]; if (re_snprintf(index, sizeof(index), "%u", idx) < 0) return ENOMEM; return entry_add(o, index, val); } int json_decode_odict(struct odict **op, uint32_t hash_size, const char *str, size_t len, unsigned maxdepth) { struct odict *o; int err; if (!op || !str) return EINVAL; err = odict_alloc(&o, hash_size); if (err) return err; err = json_decode(str, len, maxdepth, object_handler, array_handler, object_entry_handler, array_entry_handler, o); if (err) mem_deref(o); else *op = o; return err; } ================================================ FILE: src/json/encode.c ================================================ /** * @file json/encode.c JSON encoder * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include #include #include static int encode_entry(struct re_printf *pf, const struct odict_entry *e) { struct odict *array; struct le *le; int err; if (!e) return 0; switch (odict_entry_type(e)) { case ODICT_OBJECT: err = json_encode_odict(pf, odict_entry_object(e)); break; case ODICT_ARRAY: array = odict_entry_array(e); if (!array) return 0; err = re_hprintf(pf, "["); for (le=array->lst.head; le; le=le->next) { const struct odict_entry *ae = le->data; err |= re_hprintf(pf, "%H%s", encode_entry, ae, le->next ? "," : ""); } err |= re_hprintf(pf, "]"); break; case ODICT_INT: err = re_hprintf(pf, "%lld", odict_entry_int(e)); break; case ODICT_DOUBLE: err = re_hprintf(pf, "%f", odict_entry_dbl(e)); break; case ODICT_STRING: err = re_hprintf(pf, "\"%H\"", utf8_encode, odict_entry_str(e)); break; case ODICT_BOOL: err = re_hprintf(pf, "%s", odict_entry_boolean(e) ? "true" : "false"); break; case ODICT_NULL: err = re_hprintf(pf, "null"); break; default: re_fprintf(stderr, "json: unsupported type %d\n", odict_entry_type(e)); err = EINVAL; } return err; } int json_encode_odict(struct re_printf *pf, const struct odict *o) { struct le *le; int err; if (!o) return 0; err = re_hprintf(pf, "{"); for (le=o->lst.head; le; le=le->next) { const struct odict_entry *e = le->data; err |= re_hprintf(pf, "\"%H\":%H%s", utf8_encode, odict_entry_key(e), encode_entry, e, le->next ? "," : ""); } err |= re_hprintf(pf, "}"); return err; } ================================================ FILE: src/list/list.c ================================================ /** * @file list.c Linked List implementation * * Copyright (C) 2010 Creytiv.com */ #include #include #include #define DEBUG_MODULE "list" #define DEBUG_LEVEL 5 #include /** * Initialise a linked list * * @param list Linked list */ void list_init(struct list *list) { if (!list) return; list->head = NULL; list->tail = NULL; list->cnt = 0; } /** * Flush a linked list and free all elements * * @param list Linked list */ void list_flush(struct list *list) { struct le *le; if (!list) return; le = list->head; while (le) { struct le *next = le->next; void *data = le->data; list_unlink(le); le->data = NULL; le = next; mem_deref(data); } list_init(list); } /** * Clear a linked list without dereferencing the elements * * @param list Linked list */ void list_clear(struct list *list) { struct le *le; if (!list) return; le = list->head; while (le) { struct le *next = le->next; le->list = NULL; le->prev = le->next = NULL; le->data = NULL; le = next; } list_init(list); } /** * Append a list element to a linked list * * @param list Linked list * @param le List element * @param data Element data */ void list_append(struct list *list, struct le *le, void *data) { if (!list || !le) return; if (le->list) { DEBUG_WARNING("append: le already linked to %p!\n", le->list); return; } le->prev = list->tail; le->next = NULL; le->list = list; le->data = data; if (!list->head) list->head = le; if (list->tail) list->tail->next = le; list->tail = le; ++list->cnt; } /** * Prepend a list element to a linked list * * @param list Linked list * @param le List element * @param data Element data */ void list_prepend(struct list *list, struct le *le, void *data) { if (!list || !le) return; if (le->list) { DEBUG_WARNING("prepend: le linked to %p\n", le->list); return; } le->prev = NULL; le->next = list->head; le->list = list; le->data = data; if (list->head) list->head->prev = le; if (!list->tail) list->tail = le; list->head = le; ++list->cnt; } /** * Insert a list element before a given list element * * @param list Linked list * @param le Given list element * @param ile List element to insert * @param data Element data */ void list_insert_before(struct list *list, struct le *le, struct le *ile, void *data) { if (!list || !le || !ile) return; if (ile->list) { DEBUG_WARNING("insert_before: le linked to %p\n", le->list); return; } if (le->prev) le->prev->next = ile; else if (list->head == le) list->head = ile; ile->prev = le->prev; ile->next = le; ile->list = list; ile->data = data; le->prev = ile; ++list->cnt; } /** * Insert a list element after a given list element * * @param list Linked list * @param le Given list element * @param ile List element to insert * @param data Element data */ void list_insert_after(struct list *list, struct le *le, struct le *ile, void *data) { if (!list || !le || !ile) return; if (ile->list) { DEBUG_WARNING("insert_after: le linked to %p\n", le->list); return; } if (le->next) le->next->prev = ile; else if (list->tail == le) list->tail = ile; ile->prev = le; ile->next = le->next; ile->list = list; ile->data = data; le->next = ile; ++list->cnt; } /** * Sorted insert into linked list with order defined by the sort handler * (optimized for tail insert) * * @param list Linked list * @param sh Sort handler * @param arg Handler argument * @param ile List element to insert * @param data Element data */ void list_insert_sorted(struct list *list, list_sort_h *sh, void *arg, struct le *ile, void *data) { struct le *le; if (!list || !sh) return; le = list->tail; ile->data = data; while (le) { if (sh(le, ile, arg)) { list_insert_after(list, le, ile, ile->data); return; } le = le->prev; } list_prepend(list, ile, ile->data); } /** * Remove a list element from a linked list * * @param le List element to remove */ void list_unlink(struct le *le) { struct list *list; if (!le || !le->list) return; list = le->list; if (le->prev) le->prev->next = le->next; else list->head = le->next; if (le->next) le->next->prev = le->prev; else list->tail = le->prev; le->next = NULL; le->prev = NULL; le->list = NULL; if (list->cnt) --list->cnt; } /** * Sort a linked list in an order defined by the sort handler * * @param list Linked list * @param sh Sort handler * @param arg Handler argument */ void list_sort(struct list *list, list_sort_h *sh, void *arg) { struct le *le; bool sort; if (!list || !sh) return; retry: le = list->head; sort = false; while (le && le->next) { if (sh(le, le->next, arg)) { le = le->next; } else { struct le *tle = le->next; list_unlink(le); list_insert_after(list, tle, le, le->data); sort = true; } } if (sort) { goto retry; } } /** * Call the apply handler for each element in a linked list * * @param list Linked list * @param fwd true to traverse from head to tail, false for reverse * @param ah Apply handler * @param arg Handler argument * * @return Current list element if handler returned true */ struct le *list_apply(const struct list *list, bool fwd, list_apply_h *ah, void *arg) { struct le *le; if (!list || !ah) return NULL; if (fwd) { le = list->head; goto fwd; } le = list->tail; while (le) { struct le *cur = le; le = le->prev; if (ah(cur, arg)) return cur; } return NULL; fwd: while (le) { struct le *cur = le; le = le->next; if (ah(cur, arg)) return cur; } return NULL; } /** * Get the first element in a linked list * * @param list Linked list * * @return First list element (NULL if empty) */ struct le *list_head(const struct list *list) { return list ? list->head : NULL; } /** * Get the last element in a linked list * * @param list Linked list * * @return Last list element (NULL if empty) */ struct le *list_tail(const struct list *list) { return list ? list->tail : NULL; } /** * Get the number of elements in a linked list * * @param list Linked list * * @return Number of list elements */ uint32_t list_count(const struct list *list) { return list ? (uint32_t)list->cnt : 0; } ================================================ FILE: src/main/init.c ================================================ /** * @file init.c Main initialisation routine * * Copyright (C) 2010 Creytiv.com */ #include #ifdef HAVE_SIGNAL #include #endif #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #endif #include #include #include #include #include #include #include #include #include "main.h" static bool exception_btrace = false; #ifdef HAVE_SIGNAL static void signal_handler(int sig) { struct btrace bt; (void)signal(sig, NULL); if (!exception_btrace) return; btrace(&bt); re_fprintf(stderr, "Error: Signal (%d) %H\n", sig, btrace_println, &bt); fflush(stderr); } #endif #ifdef WIN32 static LONG WINAPI exception_handler(EXCEPTION_POINTERS *ExceptionInfo) { struct btrace bt; if (!exception_btrace) return EXCEPTION_CONTINUE_SEARCH; if (EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode) { btrace(&bt); re_fprintf(stderr, "%H\n", btrace_println, &bt); } else { re_fprintf(stderr, "stack overflow\n"); } switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: re_fprintf(stderr, "Error: EXCEPTION_ACCESS_VIOLATION\n"); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: re_fprintf(stderr, "Error: EXCEPTION_ARRAY_BOUNDS_EXCEEDED\n"); break; case EXCEPTION_BREAKPOINT: re_fprintf(stderr, "Error: EXCEPTION_BREAKPOINT\n"); break; case EXCEPTION_DATATYPE_MISALIGNMENT: re_fprintf(stderr, "Error: EXCEPTION_DATATYPE_MISALIGNMENT\n"); break; case EXCEPTION_FLT_DENORMAL_OPERAND: re_fprintf(stderr, "Error: EXCEPTION_FLT_DENORMAL_OPERAND\n"); break; case EXCEPTION_FLT_DIVIDE_BY_ZERO: re_fprintf(stderr, "Error: EXCEPTION_FLT_DIVIDE_BY_ZERO\n"); break; case EXCEPTION_FLT_INEXACT_RESULT: re_fprintf(stderr, "Error: EXCEPTION_FLT_INEXACT_RESULT\n"); break; case EXCEPTION_FLT_INVALID_OPERATION: re_fprintf(stderr, "Error: EXCEPTION_FLT_INVALID_OPERATION\n"); break; case EXCEPTION_FLT_OVERFLOW: re_fprintf(stderr, "Error: EXCEPTION_FLT_OVERFLOW\n"); break; case EXCEPTION_FLT_STACK_CHECK: re_fprintf(stderr, "Error: EXCEPTION_FLT_STACK_CHECK\n"); break; case EXCEPTION_FLT_UNDERFLOW: re_fprintf(stderr, "Error: EXCEPTION_FLT_UNDERFLOW\n"); break; case EXCEPTION_ILLEGAL_INSTRUCTION: re_fprintf(stderr, "Error: EXCEPTION_ILLEGAL_INSTRUCTION\n"); break; case EXCEPTION_IN_PAGE_ERROR: re_fprintf(stderr, "Error: EXCEPTION_IN_PAGE_ERROR\n"); break; case EXCEPTION_INT_DIVIDE_BY_ZERO: re_fprintf(stderr, "Error: EXCEPTION_INT_DIVIDE_BY_ZERO\n"); break; case EXCEPTION_INT_OVERFLOW: re_fprintf(stderr, "Error: EXCEPTION_INT_OVERFLOW\n"); break; case EXCEPTION_INVALID_DISPOSITION: re_fprintf(stderr, "Error: EXCEPTION_INVALID_DISPOSITION\n"); break; case EXCEPTION_NONCONTINUABLE_EXCEPTION: re_fprintf(stderr, "Error: EXCEPTION_NONCONTINUABLE_EXCEPTION\n"); break; case EXCEPTION_PRIV_INSTRUCTION: re_fprintf(stderr, "Error: EXCEPTION_PRIV_INSTRUCTION\n"); break; case EXCEPTION_SINGLE_STEP: re_fprintf(stderr, "Error: EXCEPTION_SINGLE_STEP\n"); break; case EXCEPTION_STACK_OVERFLOW: re_fprintf(stderr, "Error: EXCEPTION_STACK_OVERFLOW\n"); break; default: re_fprintf(stderr, "Error: Unrecognized Exception\n"); break; } fflush(stderr); return EXCEPTION_EXECUTE_HANDLER; } #endif /** * Initialise main library * * @return 0 if success, errorcode if failure */ int libre_init(void) { int err; if (exception_btrace) { #ifdef HAVE_SIGNAL (void)signal(SIGSEGV, signal_handler); (void)signal(SIGABRT, signal_handler); (void)signal(SIGILL, signal_handler); #endif #ifdef WIN32 SetUnhandledExceptionFilter(exception_handler); #endif } #ifdef USE_OPENSSL err = openssl_init(); if (err) return err; #endif err = net_sock_init(); if (err) { net_sock_close(); return err; } err = re_thread_init(); return err; } /** * Close library and free up all resources */ void libre_close(void) { (void)fd_setsize(0); net_sock_close(); re_thread_close(); } /** * Enable/Disable exception signal handling (SIGSEGV, SIGABRT, SIGILL...) * * @param enable True to enable, false to disable */ void libre_exception_btrace(bool enable) { exception_btrace = enable; } ================================================ FILE: src/main/main.c ================================================ /** * @file main.c Main polling routine * * Copyright (C) 2010 Creytiv.com * Copyright (C) Sebastian Reimers */ #include #ifdef HAVE_SYS_TIME_H #include #endif #include #undef _STRICT_ANSI #include #ifdef HAVE_UNISTD_H #include #endif #ifdef WIN32 #include #else #include #endif #ifdef HAVE_SIGNAL #include #endif #ifdef HAVE_SELECT_H #include #endif #ifdef HAVE_EPOLL #include #endif #ifdef HAVE_KQUEUE #include #include #include #undef LIST_INIT #undef LIST_FOREACH #undef LIST_FOREACH_SAFE #endif #include #include #include #include #include #include #include #include #include #include #include "main.h" #define DEBUG_MODULE "main" #define DEBUG_LEVEL 5 #include /** Main loop values */ enum { RE_THREAD_WORKERS = 4, MAX_BLOCKING = 500, /**< Maximum time spent in handler in [ms] */ #if defined(FD_SETSIZE) DEFAULT_MAXFDS = FD_SETSIZE #else DEFAULT_MAXFDS = 128 #endif }; /** File descriptor handler struct */ struct re_fhs { int index; re_sock_t fd; /**< File Descriptor */ int flags; /**< Polling flags (Read, Write, etc.) */ fd_h* fh; /**< Event handler */ void* arg; /**< Handler argument */ struct re_fhs* next; /**< Next element in the delete list */ }; /** Polling loop data */ struct re { int maxfds; /**< Maximum number of polling fds */ int nfds; /**< Number of active file descriptors */ enum poll_method method; /**< The current polling method */ RE_ATOMIC bool polling; /**< Is polling flag */ int sig; /**< Last caught signal */ struct tmrl *tmrl; /**< List of timers */ struct re_fhs *fhsld; /**< fhs single-linked delete list */ #ifdef HAVE_SELECT struct re_fhs **fhsl; /**< Select fhs pointer list */ #endif #ifdef HAVE_EPOLL struct epoll_event *events; /**< Event set for epoll() */ int epfd; /**< epoll control file descriptor */ #endif #ifdef HAVE_KQUEUE struct kevent *evlist; int kqfd; #endif mtx_t *mutex; /**< Mutex for thread synchronization */ mtx_t *mutexp; /**< Pointer to active mutex */ thrd_t tid; /**< Thread id */ RE_ATOMIC bool thread_enter; /**< Thread enter is called */ struct re_async *async; /**< Async object */ }; static struct re *re_global = NULL; static tss_t key; static once_flag flag = ONCE_FLAG_INIT; static void poll_close(struct re *re); static void fhsld_flush(struct re *re) { struct re_fhs *fhs = re->fhsld; re->fhsld = NULL; while (fhs) { struct re_fhs *next = fhs->next; mem_deref(fhs); fhs = next; } } static void re_destructor(void *arg) { struct re *re = arg; poll_close(re); fhsld_flush(re); mem_deref(re->mutex); mem_deref(re->async); mem_deref(re->tmrl); } /** fallback destructor if thread gets destroyed before re_thread_close() */ static void thread_destructor(void *arg) { struct re *re = arg; if (!re) return; mem_deref(re); } int re_alloc(struct re **rep) { struct re *re; int err; if (!rep) return EINVAL; re = mem_zalloc(sizeof(struct re), re_destructor); if (!re) return ENOMEM; err = mutex_alloc_tp(&re->mutex, mtx_recursive); if (err) { DEBUG_WARNING("thread_init: mtx_init error\n"); goto out; } re->mutexp = re->mutex; err = tmrl_alloc(&re->tmrl); if (err) { DEBUG_WARNING("thread_init: tmrl_alloc error\n"); goto out; } re->async = NULL; re->tid = thrd_current(); #ifdef HAVE_EPOLL re->epfd = -1; #endif #ifdef HAVE_KQUEUE re->kqfd = -1; #endif out: if (err) mem_deref(re); else *rep = re; return err; } static void re_once(void) { int err; err = tss_create(&key, thread_destructor) != thrd_success; if (err) { DEBUG_WARNING("tss_create failed\n"); exit(ENOMEM); } } /** * Get thread specific re pointer (fallback to re_global if called by non re * thread) * * @return re pointer on success, otherwise NULL if libre_init() or * re_thread_init() is missing */ static struct re *re_get(void) { struct re *re; call_once(&flag, re_once); re = tss_get(key); if (!re) re = re_global; return re; } static inline void re_lock(struct re *re) { if (thrd_success != mtx_lock(re->mutexp)) DEBUG_WARNING("re_lock error\n"); } static inline void re_unlock(struct re *re) { if (thrd_success != mtx_unlock(re->mutexp)) DEBUG_WARNING("re_unlock error\n"); } #if MAIN_DEBUG /** * Call the application event handler * * @param re Poll state * @param i File descriptor handler index * @param flags Event flags */ static void fd_handler(struct re_fhs *fhs, int flags) { const uint64_t tick = tmr_jiffies(); uint32_t diff; DEBUG_INFO("event on fd=%d (flags=0x%02x)...\n", fhs->fd, flags); fhs->fh(flags, fhs->arg); diff = (uint32_t)(tmr_jiffies() - tick); if (diff > MAX_BLOCKING) { DEBUG_WARNING("long async blocking: %u>%u ms (h=%p arg=%p)\n", diff, MAX_BLOCKING, fhs->fh, fhs->arg); } } #endif #ifdef HAVE_SELECT static int set_select_fds(struct re *re, struct re_fhs *fhs) { int i = -1; if (!re || !fhs) return EINVAL; if (fhs->index != -1) { i = fhs->index; } else { /* if nothing is found a linear search for the first * zeroed handler */ for (int j = 0; j < re->maxfds; j++) { if (!re->fhsl[j]) { i = j; break; } } } if (i == -1) return ERANGE; if (fhs->flags) { re->fhsl[i] = fhs; fhs->index = i; } else { re->fhsl[i] = NULL; fhs->index = -1; } return 0; } #endif #ifdef HAVE_EPOLL static int set_epoll_fds(struct re *re, struct re_fhs *fhs) { struct epoll_event event; int err = 0; if (!re || !fhs) return EINVAL; re_sock_t fd = fhs->fd; int flags = fhs->flags; if (re->epfd < 0) return EBADFD; memset(&event, 0, sizeof(event)); DEBUG_INFO("set_epoll_fds: fd=%d flags=0x%02x\n", fd, flags); if (flags) { event.data.ptr = fhs; if (flags & FD_READ) event.events |= EPOLLIN; if (flags & FD_WRITE) event.events |= EPOLLOUT; if (flags & FD_EXCEPT) event.events |= EPOLLERR; /* Try to add it first */ if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_ADD, fd, &event)) { /* If already exist then modify it */ if (EEXIST == errno) { if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_MOD, fd, &event)) { err = errno; DEBUG_WARNING("epoll_ctl:" " EPOLL_CTL_MOD:" " fd=%d (%m)\n", fd, err); } } else { err = errno; DEBUG_WARNING("epoll_ctl: EPOLL_CTL_ADD:" " fd=%d (%m)\n", fd, err); } } } else { if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_DEL, fd, &event)) { err = errno; DEBUG_INFO("epoll_ctl: EPOLL_CTL_DEL: fd=%d (%m)\n", fd, err); } } return err; } #endif #ifdef HAVE_KQUEUE static int set_kqueue_fds(struct re *re, struct re_fhs *fhs) { struct kevent kev[2]; int r, n = 0; if (!fhs) return EINVAL; re_sock_t fd = fhs->fd; int flags = fhs->flags; memset(kev, 0, sizeof(kev)); /* always delete the events */ EV_SET(&kev[0], fd, EVFILT_READ, EV_DELETE, 0, 0, 0); EV_SET(&kev[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0); kevent(re->kqfd, kev, 2, NULL, 0, NULL); memset(kev, 0, sizeof(kev)); if (flags & FD_WRITE) { EV_SET(&kev[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, fhs); ++n; } if (flags & FD_READ) { EV_SET(&kev[n], fd, EVFILT_READ, EV_ADD, 0, 0, fhs); ++n; } if (n) { r = kevent(re->kqfd, kev, n, NULL, 0, NULL); if (r < 0) { int err = errno; DEBUG_WARNING("set: [fd=%d, flags=%x] kevent: %m\n", fd, flags, err); return err; } } return 0; } #endif static int poll_init(struct re *re) { DEBUG_INFO("poll init (maxfds=%d)\n", re->maxfds); if (!re->maxfds) { DEBUG_WARNING("poll init: maxfds is 0\n"); return EINVAL; } switch (re->method) { #ifdef HAVE_SELECT case METHOD_SELECT: if (re->fhsl) return 0; re->fhsl = mem_zalloc(re->maxfds * sizeof(void *), NULL); if (!re->fhsl) return ENOMEM; break; #endif #ifdef HAVE_EPOLL case METHOD_EPOLL: if (!re->events) { DEBUG_INFO("allocate %u bytes for epoll set\n", re->maxfds * sizeof(*re->events)); re->events = mem_zalloc(re->maxfds*sizeof(*re->events), NULL); if (!re->events) return ENOMEM; } if (re->epfd < 0 && -1 == (re->epfd = epoll_create(re->maxfds))) { int err = errno; DEBUG_WARNING("epoll_create: %m (maxfds=%d)\n", err, re->maxfds); return err; } DEBUG_INFO("init: epoll_create() epfd=%d\n", re->epfd); break; #endif #ifdef HAVE_KQUEUE case METHOD_KQUEUE: if (!re->evlist) { size_t sz = re->maxfds * sizeof(*re->evlist); re->evlist = mem_zalloc(sz, NULL); if (!re->evlist) return ENOMEM; } if (re->kqfd < 0) { re->kqfd = kqueue(); if (re->kqfd < 0) return errno; DEBUG_INFO("kqueue: fd=%d\n", re->kqfd); } break; #endif default: DEBUG_WARNING("poll init: no method\n"); return EINVAL; break; } return 0; } /** Free all resources */ static void poll_close(struct re *re) { if (!re) return; DEBUG_INFO("poll close\n"); re->maxfds = 0; re->nfds = 0; re->method = METHOD_NULL; #ifdef HAVE_SELECT re->fhsl = mem_deref(re->fhsl); #endif #ifdef HAVE_EPOLL DEBUG_INFO("poll_close: epfd=%d\n", re->epfd); if (re->epfd >= 0) { (void)close(re->epfd); re->epfd = -1; } re->events = mem_deref(re->events); #endif #ifdef HAVE_KQUEUE if (re->kqfd >= 0) { close(re->kqfd); re->kqfd = -1; } re->evlist = mem_deref(re->evlist); #endif } static int poll_setup(struct re *re) { int err; err = fd_setsize(DEFAULT_MAXFDS); if (err) goto out; if (METHOD_NULL == re->method) { err = poll_method_set(poll_method_best()); if (err) goto out; DEBUG_INFO("poll setup: poll method not set - set to `%s'\n", poll_method_name(re->method)); } err = poll_init(re); out: if (err) poll_close(re); return err; } /** * Listen for events on a file descriptor * * @param fhsp File descriptor handler struct pointer (don't use mem_deref(), * use fd_close() instead) * @param fd File descriptor * @param flags Wanted event flags * @param fh Event handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int fd_listen(struct re_fhs **fhsp, re_sock_t fd, int flags, fd_h *fh, void *arg) { struct re *re = re_get(); struct re_fhs *fhs; int err = 0; if (!re) { DEBUG_WARNING("fd_listen: re not ready\n"); return EINVAL; } if (!fhsp || !flags || !fh) return EINVAL; #ifndef RELEASE err = re_thread_check(true); if (err) return err; #endif if (fd == RE_BAD_SOCK) { DEBUG_WARNING("fd_listen: corrupt fd %d\n", fd); return EBADF; } err = poll_setup(re); if (err) return err; fhs = *fhsp; if (!fhs) { fhs = mem_zalloc(sizeof(struct re_fhs), NULL); if (!fhs) return ENOMEM; fhs->fd = fd; fhs->index = -1; DEBUG_INFO("fd_listen/new: fd=%d flags=0x%02x\n", fd, flags); if (++re->nfds > re->maxfds) { DEBUG_WARNING("fd_listen maxfds reached %d > %d\n", re->nfds, re->maxfds); --re->nfds; err = EMFILE; goto out; } } else { if (unlikely(fhs->fd != fd)) { DEBUG_WARNING("fd_listen: fhs reuse conflict %d\n", fd); return EBADF; } DEBUG_INFO("fd_listen/update: fd=%d flags=0x%02x\n", fd, flags); } fhs->flags = flags; fhs->fh = fh; fhs->arg = arg; switch (re->method) { #ifdef HAVE_SELECT case METHOD_SELECT: err = set_select_fds(re, fhs); break; #endif #ifdef HAVE_EPOLL case METHOD_EPOLL: err = set_epoll_fds(re, fhs); break; #endif #ifdef HAVE_KQUEUE case METHOD_KQUEUE: err = set_kqueue_fds(re, fhs); break; #endif default: err = ENOTSUP; break; } out: if (err) { mem_deref(fhs); DEBUG_WARNING("fd_listen err: fd=%d flags=0x%02x (%m)\n", fd, flags, err); } else { *fhsp = fhs; } return err; } /** * Stop and destruct listening for events on a file descriptor * * @param fhs File descriptor handler struct pointer * * @return always NULL */ struct re_fhs *fd_close(struct re_fhs *fhs) { struct re *re = re_get(); int err = 0; if (!fhs || !re) return NULL; fhs->flags = 0; fhs->fh = NULL; fhs->arg = NULL; switch (re->method) { #ifdef HAVE_SELECT case METHOD_SELECT: err = set_select_fds(re, fhs); break; #endif #ifdef HAVE_EPOLL case METHOD_EPOLL: err = set_epoll_fds(re, fhs); break; #endif #ifdef HAVE_KQUEUE case METHOD_KQUEUE: err = set_kqueue_fds(re, fhs); break; #endif default: err = ENOTSUP; break; } if (err) { DEBUG_WARNING("fd_close err: fd=%d (%m)\n", fhs->fd, err); } else { DEBUG_INFO("fd_close: fd=%d\n", fhs->fd); } re_assert(fhs->next == NULL); fhs->next = re->fhsld; re->fhsld = fhs; --re->nfds; return NULL; } /** * Polling loop * * @param re Poll state. * * @return 0 if success, otherwise errorcode */ static int fd_poll(struct re *re) { const uint64_t to = tmr_next_timeout(re->tmrl); int i, n; int nfds = re->nfds; int err = 0; struct re_fhs *fhs = NULL; #ifdef HAVE_SELECT fd_set rfds, wfds, efds; #endif DEBUG_INFO("next timer: %llu ms\n", to); /* Wait for I/O */ switch (re->method) { #ifdef HAVE_SELECT case METHOD_SELECT: { struct timeval tv; int max_fd_plus_1 = 0; int cfds = 0; /* Clear and update fd sets */ FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); for (i = 0; cfds < nfds; i++) { fhs = re->fhsl[i]; if (!fhs || !fhs->fh) continue; ++cfds; re_sock_t fd = fhs->fd; if (fhs->flags & FD_READ) FD_SET(fd, &rfds); if (fhs->flags & FD_WRITE) FD_SET(fd, &wfds); if (fhs->flags & FD_EXCEPT) FD_SET(fd, &efds); /* not needed on WIN32 since select nfds arg is ignored */ #if !defined(WIN32) max_fd_plus_1 = max(max_fd_plus_1, fd + 1); #endif } nfds = re->maxfds; #ifdef WIN32 tv.tv_sec = (long) to / 1000; #else tv.tv_sec = (time_t) to / 1000; #endif tv.tv_usec = (uint32_t) (to % 1000) * 1000; re_unlock(re); n = select(max_fd_plus_1, &rfds, &wfds, &efds, to ? &tv : NULL); re_lock(re); } break; #endif #ifdef HAVE_EPOLL case METHOD_EPOLL: re_unlock(re); n = epoll_wait(re->epfd, re->events, re->maxfds, to ? (int)to : -1); re_lock(re); break; #endif #ifdef HAVE_KQUEUE case METHOD_KQUEUE: { struct timespec timeout; timeout.tv_sec = (time_t) (to / 1000); timeout.tv_nsec = (to % 1000) * 1000000; re_unlock(re); n = kevent(re->kqfd, NULL, 0, re->evlist, re->maxfds, to ? &timeout : NULL); re_lock(re); } break; #endif default: (void)to; DEBUG_WARNING("no polling method set\n"); err = EINVAL; goto out; } if (n < 0) { err = RE_ERRNO_SOCK; goto out; } /* Check for events */ for (i=0; (n > 0) && (i < nfds); i++) { re_sock_t fd; int flags = 0; switch (re->method) { #ifdef HAVE_SELECT case METHOD_SELECT: fhs = re->fhsl[i]; if (!fhs) break; fd = fhs->fd; if (FD_ISSET(fd, &rfds)) flags |= FD_READ; if (FD_ISSET(fd, &wfds)) flags |= FD_WRITE; if (FD_ISSET(fd, &efds)) flags |= FD_EXCEPT; break; #endif #ifdef HAVE_EPOLL case METHOD_EPOLL: fhs = re->events[i].data.ptr; fd = fhs->fd; if (re->events[i].events & EPOLLIN) flags |= FD_READ; if (re->events[i].events & EPOLLOUT) flags |= FD_WRITE; if (re->events[i].events & (EPOLLERR|EPOLLHUP)) flags |= FD_EXCEPT; if (!flags) { DEBUG_WARNING("epoll: no flags fd=%d\n", fd); } break; #endif #ifdef HAVE_KQUEUE case METHOD_KQUEUE: { struct kevent *kev = &re->evlist[i]; fd = (int)kev->ident; fhs = kev->udata; if (kev->filter == EVFILT_READ) flags |= FD_READ; else if (kev->filter == EVFILT_WRITE) flags |= FD_WRITE; else { DEBUG_WARNING("kqueue: unhandled " "filter %x\n", kev->filter); } if (kev->flags & EV_EOF) { flags |= FD_EXCEPT; } if (kev->flags & EV_ERROR) { DEBUG_WARNING("kqueue: EV_ERROR on fd %d\n", fd); } if (!flags) { DEBUG_WARNING("kqueue: no flags fd=%d\n", fd); } } break; #endif default: err = EINVAL; goto out; } if (!flags) continue; if (fhs && fhs->fh) { #if MAIN_DEBUG fd_handler(fhs, flags); #else fhs->fh(flags, fhs->arg); #endif } /* Handle only active events */ --n; } out: /* Delayed fhs deref to avoid dangling fhs pointers */ fhsld_flush(re); return err; } /** * Set the maximum number of file descriptors * * @note Only first call inits maxfds and fhs, so call after libre_init() and * before re_main() in custom applications. * * @param maxfds Max FDs. 0 to free and -1 for RLIMIT_NOFILE (Linux/Unix only) * * * @return 0 if success, otherwise errorcode */ int fd_setsize(int maxfds) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("fd_setsize: re not ready\n"); return EINVAL; } if (!maxfds) { poll_close(re); return 0; } #ifdef WIN32 if (maxfds < 0) return ENOSYS; #else if (maxfds < 0) { struct rlimit limits; int err; err = getrlimit(RLIMIT_NOFILE, &limits); if (err) { DEBUG_WARNING("fd_setsize: error rlimit: %m\n", err); return err; } maxfds = (int)limits.rlim_cur; } #endif if (!re->maxfds) re->maxfds = maxfds; return 0; } #ifdef HAVE_SIGNAL /* Thread-safe signal handling */ static void signal_handler(int sig) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("signal_handler: re not ready\n"); return; } (void)signal(sig, signal_handler); re->sig = sig; } #endif /** * Main polling loop for async I/O events. This function will only return when * re_cancel() is called or an error occurred. * * @param signalh Optional Signal handler * * @return 0 if success, otherwise errorcode */ int re_main(re_signal_h *signalh) { struct re *re = re_get(); int err; if (!re) { DEBUG_WARNING("re_main: re not ready\n"); return EINVAL; } #ifdef HAVE_SIGNAL if (signalh) { (void)signal(SIGINT, signal_handler); (void)signal(SIGALRM, signal_handler); (void)signal(SIGTERM, signal_handler); } #endif if (re_atomic_rlx(&re->polling)) { DEBUG_WARNING("main loop already polling\n"); return EALREADY; } err = poll_setup(re); if (err) goto out; DEBUG_INFO("Using async I/O polling method: `%s'\n", poll_method_name(re->method)); re_atomic_rlx_set(&re->polling, true); re_lock(re); for (;;) { if (re->sig) { if (signalh) signalh(re->sig); re->sig = 0; } if (!re_atomic_rlx(&re->polling)) { err = 0; break; } err = fd_poll(re); if (err) { if (EINTR == err) continue; #ifdef DARWIN /* NOTE: workaround for Darwin */ if (EBADF == err) continue; #endif #ifdef WIN32 if (WSAEINVAL == err) { tmr_poll(re->tmrl); continue; } #endif break; } tmr_poll(re->tmrl); } re_unlock(re); out: re_atomic_rlx_set(&re->polling, false); return err; } /** * Cancel the main polling loop */ void re_cancel(void) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_cancel: re not ready\n"); return; } re_atomic_rlx_set(&re->polling, false); } /** * Debug the main polling loop * * @param pf Print handler where debug output is printed to * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int re_debug(struct re_printf *pf, void *unused) { struct re *re = re_get(); int err = 0; (void)unused; if (!re) { DEBUG_WARNING("re_debug: re not ready\n"); return EINVAL; } err |= re_hprintf(pf, "re main loop:\n"); err |= re_hprintf(pf, " maxfds: %d\n", re->maxfds); err |= re_hprintf(pf, " nfds: %d\n", re->nfds); err |= re_hprintf(pf, " method: %s\n", poll_method_name(re->method)); err |= re_hprintf(pf, " polling: %d\n", re_atomic_rlx(&re->polling)); err |= re_hprintf(pf, " sig: %d\n", re->sig); err |= re_hprintf(pf, " timers: %u\n", tmrl_count(re->tmrl)); err |= re_hprintf(pf, " mutex: %p\n", re->mutex); err |= re_hprintf(pf, " tid: %p\n", re->tid); err |= re_hprintf(pf, " thread_enter: %d\n", re_atomic_rlx(&re->thread_enter)); err |= re_hprintf(pf, " async: %p\n", re->async); return err; } /** * Get number of active file descriptors * * @return nfds */ int re_nfds(void) { struct re *re = re_get(); return re ? re->nfds : 0; } /** * Get current async I/O polling method. * * @return enum poll_method */ enum poll_method poll_method_get(void) { struct re *re = re_get(); return re ? re->method : METHOD_NULL; } /** * Set async I/O polling method. This function can only called once, before * poll init/setup. * * @param method New polling method * * @return 0 if success, otherwise errorcode */ int poll_method_set(enum poll_method method) { struct re *re = re_get(); int err; if (!re) { DEBUG_WARNING("poll_method_set: re not ready\n"); return EINVAL; } if (re->method != METHOD_NULL) { DEBUG_WARNING("poll_method_set: already set\n"); return EINVAL; } err = fd_setsize(DEFAULT_MAXFDS); if (err) return err; switch (method) { #ifdef HAVE_SELECT case METHOD_SELECT: if (re->maxfds > (int)FD_SETSIZE) { DEBUG_WARNING("SELECT: maxfds > FD_SETSIZE\n"); return EMFILE; } break; #endif #ifdef HAVE_EPOLL case METHOD_EPOLL: break; #endif #ifdef HAVE_KQUEUE case METHOD_KQUEUE: break; #endif default: DEBUG_WARNING("poll method not supported: '%s'\n", poll_method_name(method)); return EINVAL; } re->method = method; DEBUG_INFO("Setting async I/O polling method to `%s'\n", poll_method_name(re->method)); err = poll_init(re); return err; } /** * Add a worker thread for this thread * * @note: for main thread this is called by libre_init() * * @return 0 if success, otherwise errorcode */ int re_thread_init(void) { struct re *re; int err; call_once(&flag, re_once); re = tss_get(key); if (re) { DEBUG_NOTICE("thread_init: already added for thread\n"); return 0; } err = re_alloc(&re); if (err) return err; if (!re_global) re_global = re; err = tss_set(key, re) != thrd_success; if (err) { err = ENOMEM; DEBUG_WARNING("thread_init: tss_set error\n"); } return err; } /** * Remove the worker thread for this thread */ void re_thread_close(void) { struct re *re; call_once(&flag, re_once); re = tss_get(key); if (re) { if (re == re_global) re_global = NULL; mem_deref(re); tss_set(key, NULL); } } /** * Enter an 're' thread */ void re_thread_enter(void) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_thread_enter: re not ready\n"); return; } if (!re_atomic_rlx(&re->polling)) return; re_lock(re); /* set only for non-re threads */ if (!thrd_equal(re->tid, thrd_current())) { re_atomic_rlx_set(&re->thread_enter, true); } } /** * Leave an 're' thread */ void re_thread_leave(void) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_thread_leave: re not ready\n"); return; } if (!re_atomic_rlx(&re->polling)) return; /* Dummy async event, to ensure timers are properly handled */ if (re->async) re_thread_async(NULL, NULL, NULL); re_atomic_rlx_set(&re->thread_enter, false); re_unlock(re); } /** * Attach the current thread to re context * * @param context Re context * * @return 0 if success, otherwise errorcode */ int re_thread_attach(struct re *context) { struct re *re; if (!context) return EINVAL; call_once(&flag, re_once); re = tss_get(key); if (re) { if (re != context) return EALREADY; return 0; } tss_set(key, context); return 0; } /** * Detach the current thread from re context */ void re_thread_detach(void) { call_once(&flag, re_once); tss_set(key, NULL); } /** * Set an external mutex for this thread * * @param mutexp Pointer to external mutex, NULL to use internal */ void re_set_mutex(void *mutexp) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_set_mutex: re not ready\n"); return; } re->mutexp = mutexp ? mutexp : re->mutex; } /** * Check for NON-RE thread calls * * @param debug True to print debug warning * * @return 0 if success, otherwise EPERM */ int re_thread_check(bool debug) { struct re *re = re_get(); if (!re) return EINVAL; if (re_atomic_rlx(&re->thread_enter)) return 0; if (thrd_equal(re->tid, thrd_current())) return 0; if (debug) { DEBUG_WARNING( "thread check: called from a NON-RE thread without " "thread_enter()!\n"); #if DEBUG_LEVEL > 5 struct btrace trace; btrace(&trace); DEBUG_INFO("%H", btrace_println, &trace); #endif } return EPERM; } /** * Get the timer-list for this thread * * @return Timer list * * @note only used by tmr module */ struct tmrl *re_tmrl_get(void) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_tmrl_get: re not ready\n"); return NULL; } return re->tmrl; } /** * Initialize re async object * * @param workers Number of async worker threads * * @return 0 if success, otherwise errorcode */ int re_thread_async_init(uint16_t workers) { struct re *re = re_get(); int err; if (!re) { DEBUG_WARNING("re_thread_async_workers: re not ready\n"); return EINVAL; } if (re->async) return EALREADY; err = re_async_alloc(&re->async, workers); if (err) DEBUG_WARNING("re_async_alloc: %m\n", err); return err; } /** * Close/Dereference async object */ void re_thread_async_close(void) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_thread_async_close: re not ready\n"); return; } re->async = mem_deref(re->async); } /** * Execute work handler for current event loop * * @param work Work handler * @param cb Callback handler (called by re poll thread) * @param arg Handler argument (has to be thread-safe and mem_deref-safe) * * @return 0 if success, otherwise errorcode */ int re_thread_async(re_async_work_h *work, re_async_h *cb, void *arg) { struct re *re = re_get(); int err; if (unlikely(!re)) { DEBUG_WARNING("re_thread_async: re not ready\n"); return EAGAIN; } if (unlikely(!re->async)) { /* fallback needed for internal libre functions */ err = re_async_alloc(&re->async, RE_THREAD_WORKERS); if (err) return err; } return re_async(re->async, 0, work, cb, arg); } /** * Execute work handler for re_global main event loop * * @param work Work handler * @param cb Callback handler (called by re global main poll thread) * @param arg Handler argument (has to be thread-safe and mem_deref-safe) * * @return 0 if success, otherwise errorcode */ int re_thread_async_main(re_async_work_h *work, re_async_h *cb, void *arg) { struct re *re = re_global; int err; if (unlikely(!re)) { DEBUG_WARNING("re_thread_async: re not ready\n"); return EAGAIN; } if (unlikely(!re->async)) { /* fallback needed for internal libre functions */ err = re_async_alloc(&re->async, RE_THREAD_WORKERS); if (err) return err; } return re_async(re->async, 0, work, cb, arg); } /** * Execute work handler for current event loop with identifier * * @param id Work identifier * @param work Work handler * @param cb Callback handler (called by re poll thread) * @param arg Handler argument (has to be thread-safe and mem_deref-safe) * * @return 0 if success, otherwise errorcode */ int re_thread_async_id(intptr_t id, re_async_work_h *work, re_async_h *cb, void *arg) { struct re *re = re_get(); int err; if (unlikely(!re)) { DEBUG_WARNING("re_thread_async_id: re not ready\n"); return EAGAIN; } if (unlikely(!re->async)) { /* fallback needed for internal libre functions */ err = re_async_alloc(&re->async, RE_THREAD_WORKERS); if (err) return err; } return re_async(re->async, id, work, cb, arg); } /** * Execute work handler for re_global main event loop with identifier * * @param id Work identifier * @param work Work handler * @param cb Callback handler (called by re poll thread) * @param arg Handler argument (has to be thread-safe and mem_deref-safe) * * @return 0 if success, otherwise errorcode */ int re_thread_async_main_id(intptr_t id, re_async_work_h *work, re_async_h *cb, void *arg) { struct re *re = re_global; int err; if (unlikely(!re)) { DEBUG_WARNING("re_thread_async_id: re not ready\n"); return EAGAIN; } if (unlikely(!re->async)) { /* fallback needed for internal libre functions */ err = re_async_alloc(&re->async, RE_THREAD_WORKERS); if (err) return err; } return re_async(re->async, id, work, cb, arg); } /** * Cancel pending async work and callback * * @param id Work identifier */ void re_thread_async_cancel(intptr_t id) { struct re *re = re_get(); if (unlikely(!re)) { DEBUG_WARNING("re_thread_async_cancel: re not ready\n"); return; } re_async_cancel(re->async, id); } /** * Cancel pending async work and callback for re_global main event loop * * @param id Work identifier */ void re_thread_async_main_cancel(intptr_t id) { struct re *re = re_global; if (unlikely(!re)) { DEBUG_WARNING("re_thread_async_cancel: re not ready\n"); return; } re_async_cancel(re->async, id); } /** * Flush file descriptors handlers if re loop is not running */ void re_fhs_flush(void) { struct re *re = re_get(); if (!re) { DEBUG_WARNING("re_fhs_flush: re not ready\n"); return; } if (re_atomic_rlx(&re->polling)) { DEBUG_WARNING("re_fhs_flush: re polling is running\n"); return; } fhsld_flush(re); } ================================================ FILE: src/main/main.h ================================================ /** * @file main.h Internal interface to main polling loop * * Copyright (C) 2010 Creytiv.com */ #ifdef __cplusplus extern "C" { #endif #ifdef USE_OPENSSL int openssl_init(void); #endif #ifdef __cplusplus } #endif ================================================ FILE: src/main/method.c ================================================ /** * @file method.c Polling methods * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include "main.h" static const char str_select[] = "select"; /**< POSIX.1-2001 select */ static const char str_epoll[] = "epoll"; /**< Linux epoll */ static const char str_kqueue[] = "kqueue"; /** * Choose the best async I/O polling method * * @return Polling method */ enum poll_method poll_method_best(void) { #ifdef HAVE_EPOLL /* Supported from Linux 2.5.66 */ return METHOD_EPOLL; #endif #ifdef HAVE_KQUEUE return METHOD_KQUEUE; #endif #ifdef HAVE_SELECT return METHOD_SELECT; #endif return METHOD_NULL; } /** * Get the name of the polling method * * @param method Polling method * * @return Polling name string */ const char *poll_method_name(enum poll_method method) { switch (method) { case METHOD_SELECT: return str_select; case METHOD_EPOLL: return str_epoll; case METHOD_KQUEUE: return str_kqueue; default: return "???"; } } /** * Get the polling method type from a string * * @param method Returned polling method * @param name Polling method name string * * @return 0 if success, otherwise errorcode */ int poll_method_type(enum poll_method *method, const struct pl *name) { if (!method || !name) return EINVAL; if (0 == pl_strcasecmp(name, str_select)) *method = METHOD_SELECT; else if (0 == pl_strcasecmp(name, str_epoll)) *method = METHOD_EPOLL; else if (0 == pl_strcasecmp(name, str_kqueue)) *method = METHOD_KQUEUE; else return ENOENT; return 0; } ================================================ FILE: src/main/openssl.c ================================================ /** * @file openssl.c OpenSSL initialisation and multi-threading routines * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_SIGNAL #include #endif #include #include "main.h" #ifdef SIGPIPE static void sigpipe_handler(int x) { (void)x; (void)signal(SIGPIPE, sigpipe_handler); } #endif int openssl_init(void) { #ifdef SIGPIPE (void)signal(SIGPIPE, sigpipe_handler); #endif return 0; } ================================================ FILE: src/mbuf/mbuf.c ================================================ /** * @file mbuf.c Memory buffers * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #define DEBUG_MODULE "mbuf" #define DEBUG_LEVEL 4 #include enum {DEFAULT_SIZE=512}; static void mbuf_destructor(void *data) { struct mbuf *mb = data; mem_deref(mb->buf); } /** * Allocate a new memory buffer * * @param size Initial buffer size * * @return New memory buffer, NULL if no memory */ struct mbuf *mbuf_alloc(size_t size) { struct mbuf *mb; mb = mem_zalloc(sizeof(*mb), mbuf_destructor); if (!mb) return NULL; if (mbuf_resize(mb, size ? size : DEFAULT_SIZE)) return mem_deref(mb); return mb; } /** * Duplicate memory buffer * * @param mbd Memory buffer to duplicate * * @return Duplicated memory buffer, NULL if no memory */ struct mbuf *mbuf_dup(struct mbuf *mbd) { struct mbuf *mb; if (!mbd) return NULL; mb = mbuf_alloc(mbd->size); if (!mb) return NULL; mb->size = mbd->size; mb->pos = mbd->pos; mb->end = mbd->end; memcpy(mb->buf, mbd->buf, mbd->size); return mb; } /** * Allocate a new memory buffer with a reference to another mbuf * * @param mbr Memory buffer to reference * * @return New memory buffer, NULL if no memory */ struct mbuf *mbuf_alloc_ref(struct mbuf *mbr) { struct mbuf *mb; if (!mbr) return NULL; mb = mem_zalloc(sizeof(*mb), mbuf_destructor); if (!mb) return NULL; mb->buf = mem_ref(mbr->buf); mb->size = mbr->size; mb->pos = mbr->pos; mb->end = mbr->end; return mb; } /** * Initialize a memory buffer * * @param mb Memory buffer to initialize */ void mbuf_init(struct mbuf *mb) { if (!mb) return; mb->buf = NULL; mb->size = 0; mb->pos = 0; mb->end = 0; } /** * Reset a memory buffer * * @param mb Memory buffer to reset */ void mbuf_reset(struct mbuf *mb) { if (!mb) return; mb->buf = mem_deref(mb->buf); mbuf_init(mb); } /** * Resize a memory buffer * * @param mb Memory buffer to resize * @param size New buffer size * * @return 0 if success, otherwise errorcode */ int mbuf_resize(struct mbuf *mb, size_t size) { uint8_t *buf; if (!mb) return EINVAL; buf = mb->buf ? mem_realloc(mb->buf, size) : mem_alloc(size, NULL); if (!buf) return ENOMEM; mb->buf = buf; mb->size = size; return 0; } /** * Trim unused trailing bytes in a memory buffer, resize if necessary * * @param mb Memory buffer to trim */ void mbuf_trim(struct mbuf *mb) { int err; if (!mb || !mb->end || mb->end == mb->size) return; /* We shrink - this cannot fail */ err = mbuf_resize(mb, mb->end); if (err) { DEBUG_WARNING("trim: resize failed (%m)\n", err); } } /** * Shift mbuf content position * * @param mb Memory buffer to shift * @param shift Shift offset count * * @return 0 if success, otherwise errorcode */ int mbuf_shift(struct mbuf *mb, ssize_t shift) { size_t rsize; uint8_t *p; if (!mb) return EINVAL; if (((ssize_t)mb->pos + shift) < 0 || ((ssize_t)mb->end + shift) < 0) return ERANGE; rsize = mb->end + shift; if (rsize > mb->size) { int err; err = mbuf_resize(mb, rsize); if (err) return err; } p = mbuf_buf(mb); memmove(p + shift, p, mbuf_get_left(mb)); mb->pos += shift; mb->end += shift; return 0; } /** * Write a block of memory to a memory buffer * * @param mb Memory buffer * @param buf Memory block to write * @param size Number of bytes to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_mem(struct mbuf *mb, const uint8_t *buf, size_t size) { size_t rsize; if (!mb || !buf) return EINVAL; rsize = mb->pos + size; if (rsize > mb->size) { const size_t dsize = mb->size ? (mb->size * 2) : DEFAULT_SIZE; int err; err = mbuf_resize(mb, MAX(rsize, dsize)); if (err) return err; } memcpy(mb->buf + mb->pos, buf, size); mb->pos += size; mb->end = MAX(mb->end, mb->pos); return 0; } /** * Write an Pointer to a memory buffer * * @param mb Memory buffer * @param v Pointer to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_ptr(struct mbuf *mb, intptr_t v) { return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v)); } /** * Write an 8-bit value to a memory buffer * * @param mb Memory buffer * @param v 8-bit value to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_u8(struct mbuf *mb, uint8_t v) { return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v)); } /** * Write a 16-bit value to a memory buffer * * @param mb Memory buffer * @param v 16-bit value to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_u16(struct mbuf *mb, uint16_t v) { return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v)); } /** * Write a 32-bit value to a memory buffer * * @param mb Memory buffer * @param v 32-bit value to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_u32(struct mbuf *mb, uint32_t v) { return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v)); } /** * Write a 64-bit value to a memory buffer * * @param mb Memory buffer * @param v 64-bit value to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_u64(struct mbuf *mb, uint64_t v) { return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v)); } /** * Write a null-terminated string to a memory buffer * * @param mb Memory buffer * @param str Null terminated string to write * * @return 0 if success, otherwise errorcode */ int mbuf_write_str(struct mbuf *mb, const char *str) { if (!str) return EINVAL; return mbuf_write_mem(mb, (const uint8_t *)str, strlen(str)); } /** * Write a pointer-length string to a memory buffer * * @param mb Memory buffer * @param pl Pointer-length string * * @return 0 if success, otherwise errorcode */ int mbuf_write_pl(struct mbuf *mb, const struct pl *pl) { if (!pl) return EINVAL; return mbuf_write_mem(mb, (const uint8_t *)pl->p, pl->l); } /** * Read a block of memory from a memory buffer * * @param mb Memory buffer * @param buf Buffer to read data to * @param size Size of buffer * * @return 0 if success, otherwise errorcode */ int mbuf_read_mem(struct mbuf *mb, uint8_t *buf, size_t size) { if (!mb || !buf) return EINVAL; if (size > mbuf_get_left(mb)) { DEBUG_WARNING("tried to read beyond mbuf end (%zu > %zu)\n", size, mbuf_get_left(mb)); return EOVERFLOW; } memcpy(buf, mb->buf + mb->pos, size); mb->pos += size; return 0; } /** * Read an Pointer from a memory buffer * * @param mb Memory buffer * * @return Pointer on success, otherwise 0 */ intptr_t mbuf_read_ptr(struct mbuf *mb) { intptr_t v; return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0; } /** * Read an 8-bit value from a memory buffer * * @param mb Memory buffer * * @return 8-bit value */ uint8_t mbuf_read_u8(struct mbuf *mb) { uint8_t v; return (0 == mbuf_read_mem(mb, &v, sizeof(v))) ? v : 0; } /** * Read a 16-bit value from a memory buffer * * @param mb Memory buffer * * @return 16-bit value */ uint16_t mbuf_read_u16(struct mbuf *mb) { uint16_t v; return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0; } /** * Read a 32-bit value from a memory buffer * * @param mb Memory buffer * * @return 32-bit value */ uint32_t mbuf_read_u32(struct mbuf *mb) { uint32_t v; return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0; } /** * Read a 64-bit value from a memory buffer * * @param mb Memory buffer * * @return 64-bit value */ uint64_t mbuf_read_u64(struct mbuf *mb) { uint64_t v; return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0; } /** * Read a string from a memory buffer * * @param mb Memory buffer * @param str Buffer to read string to * @param size Size of buffer * * @return 0 if success, otherwise errorcode */ int mbuf_read_str(struct mbuf *mb, char *str, size_t size) { if (!mb || !str) return EINVAL; while (size--) { const uint8_t c = mbuf_read_u8(mb); *str++ = c; if ('\0' == c) break; } return 0; } /** * Duplicate a null-terminated string from a memory buffer * * @param mb Memory buffer * @param strp Pointer to destination string; allocated and set * @param len Length of string * * @return 0 if success, otherwise errorcode */ int mbuf_strdup(struct mbuf *mb, char **strp, size_t len) { char *str; int err; if (!mb || !strp) return EINVAL; str = mem_alloc(len + 1, NULL); if (!str) return ENOMEM; err = mbuf_read_mem(mb, (uint8_t *)str, len); if (err) goto out; str[len] = '\0'; out: if (err) mem_deref(str); else *strp = str; return err; } static int vprintf_handler(const char *p, size_t size, void *arg) { struct mbuf *mb = arg; return mbuf_write_mem(mb, (const uint8_t *)p, size); } /** * Print a formatted variable argument list to a memory buffer * * @param mb Memory buffer * @param fmt Formatted string * @param ap Variable argument list * * @return 0 if success, otherwise errorcode */ int mbuf_vprintf(struct mbuf *mb, const char *fmt, va_list ap) { return re_vhprintf(fmt, ap, vprintf_handler, mb); } /** * Print a formatted string to a memory buffer * * @param mb Memory buffer * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int _mbuf_printf(struct mbuf *mb, const char *fmt, ...) { int err = 0; va_list ap; va_start(ap, fmt); err = re_vhprintf(fmt, ap, vprintf_handler, mb); va_end(ap); return err; } /** * Print a safe formatted string to a memory buffer * * @param mb Memory buffer * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int _mbuf_printf_s(struct mbuf *mb, const char *fmt, ...) { int err = 0; va_list ap; va_start(ap, fmt); err = re_vhprintf_s(fmt, ap, vprintf_handler, mb); va_end(ap); return err; } /** * Write a pointer-length string to a memory buffer, excluding a section * * @param mb Memory buffer * @param pl Pointer-length string * @param skip Part of pl to exclude * * @return 0 if success, otherwise errorcode * * @todo: create substf variante */ int mbuf_write_pl_skip(struct mbuf *mb, const struct pl *pl, const struct pl *skip) { struct pl r; int err; if (!pl || !skip) return EINVAL; if (pl->p > skip->p || (skip->p + skip->l) > (pl->p + pl->l)) return ERANGE; r.p = pl->p; r.l = skip->p - pl->p; err = mbuf_write_mem(mb, (const uint8_t *)r.p, r.l); if (err) return err; r.p = skip->p + skip->l; r.l = pl->p + pl->l - r.p; return mbuf_write_mem(mb, (const uint8_t *)r.p, r.l); } /** * Write n bytes of value 'c' to a memory buffer * * @param mb Memory buffer * @param c Value to write * @param n Number of bytes to write * * @return 0 if success, otherwise errorcode */ int mbuf_fill(struct mbuf *mb, uint8_t c, size_t n) { size_t rsize; if (!mb || !n) return EINVAL; rsize = mb->pos + n; if (rsize > mb->size) { const size_t dsize = mb->size ? (mb->size * 2) : DEFAULT_SIZE; int err; err = mbuf_resize(mb, MAX(rsize, dsize)); if (err) return err; } memset(mb->buf + mb->pos, c, n); mb->pos += n; mb->end = MAX(mb->end, mb->pos); return 0; } /** * Set absolute position and end position * * @param mb Memory buffer * @param pos Position * @param end End position */ void mbuf_set_posend(struct mbuf *mb, size_t pos, size_t end) { if (!mb) return; if (pos > end) { DEBUG_WARNING("set_posend: pos %zu > end %zu\n", pos, end); return; } if (end > mb->size) { DEBUG_WARNING("set_posend: end %zu > size %zu\n", end, mb->size); return; } mb->pos = pos; mb->end = end; MBUF_CHECK_POS(mb); MBUF_CHECK_END(mb); } /** * Debug the memory buffer * * @param pf Print handler * @param mb Memory buffer * * @return 0 if success, otherwise errorcode */ int mbuf_debug(struct re_printf *pf, const struct mbuf *mb) { if (!mb) return 0; return re_hprintf(pf, "buf=%p pos=%zu end=%zu size=%zu", mb->buf, mb->pos, mb->end, mb->size); } ================================================ FILE: src/md5/wrap.c ================================================ /** * @file wrap.c MD5 wrappers * * Copyright (C) 2010 Creytiv.com */ #ifdef USE_OPENSSL #include #include #include #elif defined (__APPLE__) #include #elif defined (WIN32) #include #include #elif defined (USE_MBEDTLS) #include #include #endif #include #include #include #include #include #define DEBUG_MODULE "md5" #define DEBUG_LEVEL 5 #include /** * Calculate the MD5 hash from a buffer * * @param d Data buffer (input) * @param n Number of input bytes * @param md Calculated MD5 hash (output) */ void md5(const uint8_t *d, size_t n, uint8_t *md) { #ifdef USE_OPENSSL EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_md5(), NULL); EVP_DigestUpdate(ctx, d, n); EVP_DigestFinal_ex(ctx, md, NULL); EVP_MD_CTX_free(ctx); #elif defined (__APPLE__) CC_MD5(d, (unsigned int)n, md); #elif defined (WIN32) HCRYPTPROV context; HCRYPTHASH hash; DWORD hash_size = MD5_SIZE; CryptAcquireContext(&context, 0, 0, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT); CryptCreateHash(context, CALG_MD5, 0, 0, &hash); CryptHashData(hash, d, (DWORD)n, 0); CryptGetHashParam(hash, HP_HASHVAL, md, &hash_size, 0); CryptDestroyHash(hash); CryptReleaseContext(context, 0); #elif defined (MBEDTLS_MD_C) int err; err = mbedtls_md5(d, n, md); if (err) DEBUG_WARNING("mbedtls_md5: %s\n", mbedtls_high_level_strerr(err)); #else #error missing MD5 backend #endif } /** * Calculate the MD5 hash from a formatted string * * @param md Calculated MD5 hash * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int md5_printf(uint8_t *md, const char *fmt, ...) { struct mbuf mb; va_list ap; int err; mbuf_init(&mb); va_start(ap, fmt); err = mbuf_vprintf(&mb, fmt, ap); va_end(ap); if (!err) md5(mb.buf, mb.end, md); mbuf_reset(&mb); return err; } ================================================ FILE: src/mem/mem.c ================================================ /** * @file mem.c Memory management with reference counting * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MODULE "mem" #define DEBUG_LEVEL 5 #include #ifndef RELEASE #define MEM_DEBUG 1 /**< Enable memory debugging */ #endif /** Defines a reference-counting memory object */ struct mem { RE_ATOMIC uint32_t nrefs; /**< Number of references */ uint32_t size; /**< Size of memory object */ mem_destroy_h *dh; /**< Destroy handler */ #if MEM_DEBUG size_t magic; /**< Magic number */ struct le le; /**< Linked list element */ struct btrace btraces; /**< Backtrace array */ #endif }; #if MEM_DEBUG /* Memory debugging */ static struct list meml = LIST_INIT; static const size_t mem_magic = 0xe7fb9ac4; static ssize_t threshold = -1; /**< Memory threshold, disabled by default */ static struct memstat memstat = { 0,0 }; static once_flag flag = ONCE_FLAG_INIT; static mtx_t mtx; static void mem_lock_init(void) { mtx_init(&mtx, mtx_plain); } static inline void mem_lock(void) { call_once(&flag, mem_lock_init); mtx_lock(&mtx); } static inline void mem_unlock(void) { mtx_unlock(&mtx); } /** Update statistics for mem_zalloc() */ #define STAT_ALLOC(_m, _size) \ mem_lock(); \ memstat.bytes_cur += (_size); \ ++memstat.blocks_cur; \ mem_unlock(); \ (_m)->size = (uint32_t)(_size); \ (_m)->magic = mem_magic; /** Update statistics for mem_realloc() */ #define STAT_REALLOC(_m, _size) \ mem_lock(); \ memstat.bytes_cur += ((_size) - (_m)->size); \ mem_unlock(); \ (_m)->size = (uint32_t)(_size) /** Update statistics for mem_deref() */ #define STAT_DEREF(_m) \ mem_lock(); \ memstat.bytes_cur -= (_m)->size; \ --memstat.blocks_cur; \ mem_unlock(); \ memset((_m), 0xb5, (size_t)mem_header_size + (_m)->size) /** Check magic number in memory object */ #define MAGIC_CHECK(_m) \ if (mem_magic != (_m)->magic) { \ DEBUG_WARNING("%s: magic check failed 0x%08zx (%p)\n", \ __func__, (_m)->magic, get_mem_data((_m))); \ RE_BREAKPOINT; \ } #else #define STAT_ALLOC(_m, _size) (_m)->size = (uint32_t)(_size); #define STAT_REALLOC(_m, _size) (_m)->size = (uint32_t)(_size); #define STAT_DEREF(_m) #define MAGIC_CHECK(_m) #endif enum { #if defined(__x86_64__) /* Use 16-byte alignment on x86-x32 as well */ mem_alignment = 16u, #else mem_alignment = sizeof(void*) >= 8u ? 16u : 8u, #endif alignment_mask = mem_alignment - 1u, mem_header_size = (sizeof(struct mem) + alignment_mask) & (~(size_t)alignment_mask) }; #define MEM_SIZE_MAX \ (size_t)(sizeof(size_t) > sizeof(uint32_t) ? \ (~(uint32_t)0u) : (~(size_t)0u) - mem_header_size) static inline struct mem *get_mem(void *p) { return (struct mem *)(void *)(((unsigned char *)p) - mem_header_size); } static inline void *get_mem_data(struct mem *m) { return (void *)(((unsigned char *)m) + mem_header_size); } /** * Allocate a new reference-counted memory object * * @param size Size of memory object * @param dh Optional destructor, called when destroyed * * @return Pointer to allocated object */ void *mem_alloc(size_t size, mem_destroy_h *dh) { struct mem *m; if (size > MEM_SIZE_MAX) return NULL; #if MEM_DEBUG mem_lock(); if (-1 != threshold && (memstat.blocks_cur >= (size_t)threshold)) { mem_unlock(); return NULL; } mem_unlock(); #endif m = malloc(mem_header_size + size); if (!m) return NULL; #if MEM_DEBUG btrace(&m->btraces); memset(&m->le, 0, sizeof(struct le)); mem_lock(); list_append(&meml, &m->le, m); mem_unlock(); #endif re_atomic_rlx_set(&m->nrefs, 1u); m->dh = dh; STAT_ALLOC(m, size); return get_mem_data(m); } /** * Allocate a new reference-counted memory object. Memory is zeroed. * * @param size Size of memory object * @param dh Optional destructor, called when destroyed * * @return Pointer to allocated object */ void *mem_zalloc(size_t size, mem_destroy_h *dh) { void *p; p = mem_alloc(size, dh); if (!p) return NULL; memset(p, 0, size); return p; } /** * Re-allocate a reference-counted memory object * * @param data Memory object * @param size New size of memory object * * @return New pointer to allocated object * * @note Realloc NULL pointer is not supported */ void *mem_realloc(void *data, size_t size) { struct mem *m, *m2; if (!data) return NULL; if (size > MEM_SIZE_MAX) return NULL; m = get_mem(data); MAGIC_CHECK(m); if (re_atomic_acq(&m->nrefs) > 1u) { void* p = mem_alloc(size, m->dh); if (p) { memcpy(p, data, (m->size < size) ? m->size : size); mem_deref(data); } return p; } #if MEM_DEBUG mem_lock(); /* Simulate OOM */ if (-1 != threshold && size > m->size) { if (memstat.blocks_cur >= (size_t)threshold) { mem_unlock(); return NULL; } } list_unlink(&m->le); mem_unlock(); #endif m2 = realloc(m, mem_header_size + size); #if MEM_DEBUG mem_lock(); list_append(&meml, m2 ? &m2->le : &m->le, m2 ? m2 : m); mem_unlock(); #endif if (!m2) { return NULL; } STAT_REALLOC(m2, size); return get_mem_data(m2); } /** * Re-allocate a reference-counted array * * @param ptr Pointer to existing array, NULL to allocate a new array * @param nmemb Number of members in array * @param membsize Number of bytes in each member * @param dh Optional destructor, only used when ptr is NULL * * @return New pointer to allocated array */ void *mem_reallocarray(void *ptr, size_t nmemb, size_t membsize, mem_destroy_h *dh) { size_t tsize; if (membsize && nmemb > MEM_SIZE_MAX / membsize) { return NULL; } tsize = nmemb * membsize; if (ptr) { return mem_realloc(ptr, tsize); } else { return mem_alloc(tsize, dh); } } /** * Set or unset a destructor for a memory object * * @param data Memory object * @param dh called when destroyed, NULL for remove */ void mem_destructor(void *data, mem_destroy_h *dh) { struct mem *m; if (!data) return; m = get_mem(data); MAGIC_CHECK(m); m->dh = dh; } /** * Reference a reference-counted memory object * * @param data Memory object * * @return Memory object (same as data) */ void *mem_ref(void *data) { struct mem *m; if (!data) return NULL; m = get_mem(data); MAGIC_CHECK(m); re_atomic_rlx_add(&m->nrefs, 1u); return data; } /** * Dereference a reference-counted memory object. When the reference count * is zero, the destroy handler will be called (if present) and the memory * will be freed * * @param data Memory object * * @return Always NULL */ /* coverity[-tainted_data_sink: arg-0] */ void *mem_deref(void *data) { struct mem *m; if (!data) return NULL; m = get_mem(data); MAGIC_CHECK(m); if (re_atomic_acq_sub(&m->nrefs, 1u) > 1u) { return NULL; } if (m->dh) m->dh(data); /* NOTE: check if the destructor called mem_ref() */ if (re_atomic_rlx(&m->nrefs) > 0u) return NULL; #if MEM_DEBUG mem_lock(); list_unlink(&m->le); mem_unlock(); #endif STAT_DEREF(m); free(m); return NULL; } /** * Get number of references to a reference-counted memory object * * @param data Memory object * * @return Number of references */ uint32_t mem_nrefs(const void *data) { struct mem *m; if (!data) return 0; m = get_mem((void*)data); MAGIC_CHECK(m); return (uint32_t)re_atomic_acq(&m->nrefs); } #if MEM_DEBUG static bool debug_handler(struct le *le, void *arg) { struct mem *m = le->data; const uint8_t *p = get_mem_data(m); size_t i; uint32_t *last_np = arg; (void)re_fprintf(stderr, " %p: nrefs=%-2u", p, (uint32_t)re_atomic_rlx(&m->nrefs)); (void)re_fprintf(stderr, " size=%-7u", m->size); (void)re_fprintf(stderr, " ["); for (i=0; i<16; i++) { if (i >= m->size) (void)re_fprintf(stderr, " "); else (void)re_fprintf(stderr, "%02x ", p[i]); } (void)re_fprintf(stderr, "] ["); for (i=0; i<16; i++) { if (i >= m->size) (void)re_fprintf(stderr, " "); else (void)re_fprintf(stderr, "%c", isprint(p[i]) ? p[i] : '.'); } (void)re_fprintf(stderr, "]"); MAGIC_CHECK(m); (void)re_fprintf(stderr, "\n"); re_fprintf(stderr, "%H\n", btrace_println, &m->btraces); if (last_np && !--(*last_np)) return true; return false; } #endif /** * Debug all allocated memory objects */ void mem_debug(void) { #if MEM_DEBUG uint32_t n; mem_lock(); n = list_count(&meml); mem_unlock(); if (!n) return; DEBUG_WARNING("Possible memory leaks (%u):\n", n); mem_lock(); (void)list_apply(&meml, true, debug_handler, NULL); mem_unlock(); #endif } /** * Debug last n allocated memory objects/blocks * * @param last_n Last number of blocks */ void mem_debug_tail(uint32_t last_n) { #if MEM_DEBUG uint32_t n; if (!last_n) return; mem_lock(); n = list_count(&meml); mem_unlock(); if (!n) return; DEBUG_WARNING("Possible Memory leaks (%u/%u) :\n", last_n, n); mem_lock(); (void)list_apply(&meml, false, debug_handler, &last_n); mem_unlock(); #else (void)last_n; #endif } /** * Set the memory allocation threshold. This is only used for debugging * and out-of-memory simulation * * @param n Threshold value */ void mem_threshold_set(ssize_t n) { #if MEM_DEBUG mem_lock(); threshold = n; mem_unlock(); #else (void)n; #endif } /** * Print memory status * * @param pf Print handler for debug output * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int mem_status(struct re_printf *pf, void *unused) { #if MEM_DEBUG struct memstat stat; uint32_t c; int err = 0; (void)unused; mem_lock(); memcpy(&stat, &memstat, sizeof(stat)); c = list_count(&meml); mem_unlock(); err |= re_hprintf(pf, "Memory status: (%zu bytes overhead per block)\n", (size_t)mem_header_size); err |= re_hprintf(pf, " Cur: %zu blocks, %zu bytes (total %zu bytes)\n", stat.blocks_cur, stat.bytes_cur, stat.bytes_cur + (stat.blocks_cur * (size_t)mem_header_size)); err |= re_hprintf(pf, " Total %u blocks allocated\n", c); return err; #else (void)pf; (void)unused; return 0; #endif } /** * Get memory statistics * * @param mstat Returned memory statistics * * @return 0 if success, otherwise errorcode */ int mem_get_stat(struct memstat *mstat) { if (!mstat) return EINVAL; #if MEM_DEBUG mem_lock(); memcpy(mstat, &memstat, sizeof(*mstat)); mem_unlock(); return 0; #else return ENOSYS; #endif } ================================================ FILE: src/mem/mem_pool.c ================================================ /** * @file mem_pool.c Pre-Allocated Memory pool management * * Copyright (C) 2025 Sebastian Reimers */ #include #include #include #include #define DEBUG_MODULE "mem_pool" #define DEBUG_LEVEL 5 #include struct mem_pool { size_t nmemb; size_t membsize; struct mem_pool_entry *freel; /* single linked list */ mem_destroy_h *membdh; struct mem_pool_entry **objs; mtx_t *lock; }; struct mem_pool_entry { struct mem_pool_entry *next; void *member; }; static void mem_pool_destroy(void *data) { struct mem_pool *p = data; for (size_t i = 0; i < p->nmemb; i++) { if (p->objs[i]) mem_deref(p->objs[i]->member); mem_deref(p->objs[i]); } mem_deref(p->objs); mem_deref(p->lock); } static inline void next_free(struct mem_pool *pool, struct mem_pool_entry *e) { e->next = pool->freel; pool->freel = e; } /** * @brief Allocate a memory pool * * This function initializes a memory pool with a specified number of elements, * each of a given size. Optionally, a destructor callback can be provided * to handle cleanup when a member is released or pool is destroyed * * @param poolp Pointer to the memory pool pointer to be initialized * @param nmemb Number of elements to allocate in the pool * @param membsize Size of each element in the pool * @param dh Optional destructor callback for pool cleanup (can be * NULL) * * @return 0 for success, otherwise error code */ int mem_pool_alloc(struct mem_pool **poolp, size_t nmemb, size_t membsize, mem_destroy_h *dh) { int err; if (!poolp || !nmemb || !membsize) return EINVAL; struct mem_pool *p = mem_zalloc(sizeof(struct mem_pool), NULL); if (!p) return ENOMEM; p->nmemb = nmemb; p->membsize = membsize; p->membdh = dh; p->objs = mem_zalloc(nmemb * sizeof(struct mem_pool_entry *), NULL); if (!p->objs) { err = ENOMEM; goto error; } mem_destructor(p, mem_pool_destroy); err = mutex_alloc(&p->lock); if (err) goto error; for (size_t i = 0; i < nmemb; i++) { p->objs[i] = mem_zalloc(sizeof(struct mem_pool_entry), NULL); if (!p->objs[i]) { err = ENOMEM; goto error; } p->objs[i]->member = mem_zalloc(membsize, dh); if (!p->objs[i]->member) { err = ENOMEM; goto error; } next_free(p, p->objs[i]); } *poolp = p; return 0; error: mem_deref(p); return err; } /** * @brief Extend an existing memory pool * * Adds additional elements to an existing memory pool * * @param pool Pointer to the memory pool to extend * @param num Number of additional elements to add to the pool * * @return 0 for success, otherwise error code */ int mem_pool_extend(struct mem_pool *pool, size_t num) { if (!pool || !num) return EINVAL; mtx_lock(pool->lock); size_t nmemb = pool->nmemb + num; struct mem_pool_entry **objs; objs = mem_zalloc(nmemb * sizeof(struct mem_pool_entry *), NULL); if (!objs) { mtx_unlock(pool->lock); return ENOMEM; } /* Copy old members */ size_t i = 0; for (; i < pool->nmemb; i++) { objs[i] = pool->objs[i]; } /* Allocate new members */ for (; i < nmemb; i++) { objs[i] = mem_zalloc(sizeof(struct mem_pool_entry), NULL); if (!objs[i]) { mem_deref(objs); mtx_unlock(pool->lock); return ENOMEM; } objs[i]->member = mem_zalloc(pool->membsize, pool->membdh); if (!objs[i]->member) { mem_deref(objs[i]); mem_deref(objs); mtx_unlock(pool->lock); return ENOMEM; } next_free(pool, objs[i]); } mem_deref(pool->objs); pool->objs = objs; pool->nmemb = nmemb; mtx_unlock(pool->lock); return 0; } /** * @brief Borrow an entry from the memory pool * * Retrieves an unused entry from the memory pool for temporary use * * @param pool Pointer to the memory pool * * @return Pointer to a memory pool entry, or NULL if no entries are available */ struct mem_pool_entry *mem_pool_borrow(struct mem_pool *pool) { if (!pool) return NULL; mtx_lock(pool->lock); struct mem_pool_entry *e = pool->freel; if (e) { pool->freel = e->next; mtx_unlock(pool->lock); return e; } mtx_unlock(pool->lock); return NULL; } /** * Borrow an entry from the memory pool, extend the pool if necessary * * @param pool Pointer to the memory pool * * @return Pointer to a memory pool entry, or NULL on error */ struct mem_pool_entry *mem_pool_borrow_extend(struct mem_pool *pool) { struct mem_pool_entry *e = mem_pool_borrow(pool); if (e) return e; mem_pool_extend(pool, pool->nmemb * 2); return mem_pool_borrow(pool); } /** * @brief Release a borrowed entry back to the memory pool * * Returns a previously borrowed memory pool entry back to the pool * When the entry is released, the member destructor callback (if provided) * is called to perform any necessary cleanup. Additionally, the memory * associated with the entry is re-initialized to zero to ensure a clean state * for future use * * @param pool Pointer to the memory pool * @param e Pointer to the memory pool entry to release * * @return Always NULL */ void *mem_pool_release(struct mem_pool *pool, struct mem_pool_entry *e) { if (!pool || !e) return NULL; mtx_lock(pool->lock); if (pool->membdh) pool->membdh(e->member); memset(e->member, 0, pool->membsize); next_free(pool, e); mtx_unlock(pool->lock); return NULL; } /** * Flush mem_pool members * * @param pool Pointer to the memory pool entry */ void mem_pool_flush(struct mem_pool *pool) { mtx_lock(pool->lock); for (size_t i = 0; i < pool->nmemb; i++) { struct mem_pool_entry *e = pool->objs[i]; if (pool->membdh) pool->membdh(e->member); memset(e->member, 0, pool->membsize); next_free(pool, e); } mtx_unlock(pool->lock); } /** * Return Pool member * * @param entry Pointer to the memory pool entry * * @return Pointer to the data associated with the memory pool entry or NULL */ void *mem_pool_member(const struct mem_pool_entry *entry) { return entry ? entry->member : NULL; } ================================================ FILE: src/mem/secure.c ================================================ /** * @file mem/secure.c Secure memory functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #if !defined(__GNUC__) && defined(WIN32) #if !defined(WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN #endif #include #endif /* !defined(__GNUC__) && defined(WIN32) */ /** * Compare two byte strings in constant time. This function can be used * by secure code to compare secret data, such as authentication tags, * to avoid side-channel attacks. * * @param s1 First byte string * @param s2 Second byte string * @param n Number of bytes * * @return a negative number if argument errors * 0 if both byte strings matching * a positive number if not matching */ int mem_seccmp(const uint8_t *s1, const uint8_t *s2, size_t n) { uint8_t val = 0; const volatile uint8_t *p1 = s1; const volatile uint8_t *p2 = s2; if (!p1 || !p2) return -1; while (n--) val |= *p1++ ^ *p2++; return val; } #if !defined(__GNUC__) && !defined(WIN32) /* Use a volatile pointer to memset to force the compiler always * call it and not optimize away. */ typedef void *(memset_t)(void *, int, size_t); static memset_t *const volatile memset_ptr = &memset; #endif /** * Securely clean memory. This function is guaranteed not to get optimized * away by compiler. * * @param data Pointer to data buffer * @param size Size of the buffer */ void mem_secclean(void *data, size_t size) { #if defined(__GNUC__) memset(data, 0, size); /* Insert an asm statement that may potentially depend * on the memory contents that were affected by memset. * This prevents optimizing away the memset. */ __asm__ __volatile__("" : : "r" (data), "r" (size) : "memory"); #elif defined(WIN32) SecureZeroMemory(data, size); #else (*memset_ptr)(data, 0, size); #endif } ================================================ FILE: src/mod/dl.c ================================================ /** * @file dl.c Interface to dynamic linking loader * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "mod_internal.h" #define DEBUG_MODULE "dl" #define DEBUG_LEVEL 5 #include static const int dl_flag = RTLD_NOW | RTLD_LOCAL; /** * Load a dynamic library file * * @param name Name of library to load * * @return Opaque library handle, NULL if not loaded */ void *_mod_open(const char *name) { void *h; if (!name) return NULL; h = dlopen(name, dl_flag); if (!h) { DEBUG_WARNING("mod: %s (%s)\n", name, dlerror()); return NULL; } return h; } /** * Resolve address of symbol in dynamic library * * @param h Library handle * @param symbol Name of symbol to resolve * * @return Address, NULL if failure */ void *_mod_sym(void *h, const char *symbol) { void *sym; const char *err; if (!h || !symbol) return NULL; (void)dlerror(); /* Clear any existing error */ sym = dlsym(h, symbol); err = dlerror(); if (err) { DEBUG_WARNING("dlsym: %s\n", err); return NULL; } return sym; } /** * Unload a dynamic library * * @param h Library handle */ void _mod_close(void *h) { int err; if (!h) return; err = dlclose(h); if (0 != err) { DEBUG_WARNING("dlclose: %d\n", err); } } ================================================ FILE: src/mod/mod.c ================================================ /** * @file mod.c Loadable modules * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "mod_internal.h" #define DEBUG_MODULE "mod" #define DEBUG_LEVEL 5 #include /** Defines a loadable module */ struct mod { struct le le; /**< Linked list element */ void *h; /**< Module handler */ const struct mod_export *me; /**< Module exports */ }; static struct list modl; /* struct mod */ /** * Initialise module loading */ void mod_init(void) { list_init(&modl); } /** * Unload all modules */ void mod_close(void) { list_flush(&modl); } static void mod_destructor(void *data) { struct mod *m = data; const struct mod_export *me = m->me; int err; if (me && me->close && (err = me->close())) { DEBUG_NOTICE("close: error (%m)\n", err); } list_unlink(&m->le); _mod_close(m->h); } /** * Find a module by name in the list of loaded modules * * @param name Name of module to find * * @return Module if found, NULL if not found */ struct mod *mod_find(const char *name) { struct le *le; struct pl x; if (!name) return NULL; if (re_regex(name, strlen(name), "[/]*[^./]+" MOD_EXT, NULL, &x)) return NULL; for (le = modl.head; le; le = le->next) { struct mod *m = le->data; if (0 == pl_strcasecmp(&x, m->me->name)) return m; } return NULL; } /** * Load and initialise a loadable module by name * * @param mp Pointer to allocated module object * @param name Name of loadable module * * @return 0 if success, otherwise errorcode */ int mod_load(struct mod **mp, const char *name) { struct mod *m; int err = 0; if (!mp || !name) return EINVAL; /* check if already loaded */ m = mod_find(name); if (m) { DEBUG_NOTICE("module already loaded: %s\n", name); return EALREADY; } m = mem_zalloc(sizeof(*m), mod_destructor); if (!m) return ENOMEM; list_append(&modl, &m->le, m); m->h = _mod_open(name); if (!m->h) { err = ENOENT; goto out; } m->me = _mod_sym(m->h, "exports"); if (!m->me) { err = ELIBBAD; goto out; } if (m->me->init && (err = m->me->init())) goto out; out: if (err) mem_deref(m); else *mp = m; return err; } /** * Add and initialise an external module with exports * * @param mp Pointer to allocated module object * @param me Module exports * * @return 0 if success, otherwise errorcode */ int mod_add(struct mod **mp, const struct mod_export *me) { struct mod *m; int err = 0; if (!mp || !me) return EINVAL; /* check if already loaded */ m = mod_find(me->name); if (m) { DEBUG_NOTICE("module already loaded: %s\n", me->name); return EALREADY; } m = mem_zalloc(sizeof(*m), mod_destructor); if (!m) return ENOMEM; list_append(&modl, &m->le, m); m->me = me; if (m->me->init) err = m->me->init(); if (err) mem_deref(m); else *mp = m; return err; } /** * Get module export from a loadable module * * @param m Loadable module * * @return Module export */ const struct mod_export *mod_export(const struct mod *m) { return m ? m->me : NULL; } /** * Get the list of loaded modules * * @return Module list */ struct list *mod_list(void) { return &modl; } /** * Debug loadable modules * * @param pf Print handler for debug output * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int mod_debug(struct re_printf *pf, void *unused) { struct le *le; int err; (void)unused; err = re_hprintf(pf, "\n--- Modules (%u) ---\n", list_count(&modl)); for (le = modl.head; le && !err; le = le->next) { const struct mod *m = le->data; const struct mod_export *me = m->me; err = re_hprintf(pf, " %16s type=%-12s ref=%u\n", me->name, me->type, mem_nrefs(m)); } err |= re_hprintf(pf, "\n"); return err; } ================================================ FILE: src/mod/mod_internal.h ================================================ /** * @file mod_internal.h Internal interface to loadable module * * Copyright (C) 2010 Creytiv.com */ #ifdef __cplusplus extern "C" { #endif void *_mod_open(const char *name); void *_mod_sym(void *h, const char *symbol); void _mod_close(void *h); #ifdef __cplusplus } #endif ================================================ FILE: src/mod/win32/dll.c ================================================ /** * @file dll.c Dynamic library loading for Windows * * Copyright (C) 2010 Creytiv.com */ #include #include #include "../mod_internal.h" #define DEBUG_MODULE "dll" #define DEBUG_LEVEL 5 #include /* * Open a DLL file * * @param name Name of DLL to open * * @return Handle (NULL if failed) */ void *_mod_open(const char *name) { HINSTANCE DllHandle = 0; DEBUG_INFO("loading %s\n", name); DllHandle = LoadLibraryA(name); if (!DllHandle) { DEBUG_WARNING("open: %s LoadLibraryA() failed\n", name); return NULL; } return DllHandle; } /* * Resolve a symbol address in a DLL * * @param h DLL Handle * @param symbol Symbol to resolve * * @return Address of symbol */ void *_mod_sym(void *h, const char *symbol) { HINSTANCE DllHandle = (HINSTANCE)h; union { FARPROC sym; void *ptr; } u; if (!DllHandle) return NULL; DEBUG_INFO("get symbol: %s\n", symbol); u.sym = GetProcAddress(DllHandle, symbol); if (!u.sym) { DEBUG_WARNING("GetProcAddress: no symbol %s\n", symbol); return NULL; } return u.ptr; } /* * Close a DLL * * @param h DLL Handle */ void _mod_close(void *h) { HINSTANCE DllHandle = (HINSTANCE)h; DEBUG_INFO("unloading %p\n", h); if (!DllHandle) return; FreeLibrary(DllHandle); } ================================================ FILE: src/mqueue/mqueue.c ================================================ /** * @file mqueue.c Thread Safe Message Queue * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include "mqueue.h" #define MAGIC 0x14553399 #ifdef WIN32 #include #include #define close closesocket #endif /** * Defines a Thread-safe Message Queue * * The Message Queue can be used to communicate between two threads. The * receiving thread must run the re_main() loop which will be woken up on * incoming messages from other threads. The sender thread can be any thread. */ struct mqueue { re_sock_t pfd[2]; struct re_fhs *fhs; mqueue_h *h; void *arg; }; struct msg { void *data; uint32_t magic; int id; }; static void destructor(void *arg) { struct mqueue *q = arg; if (q->pfd[0] != RE_BAD_SOCK) { q->fhs = fd_close(q->fhs); (void)close(q->pfd[0]); } if (q->pfd[1] != RE_BAD_SOCK) (void)close(q->pfd[1]); } static void event_handler(int flags, void *arg) { struct mqueue *mq = arg; struct msg msg; ssize_t n; if (!(flags & FD_READ)) return; n = pipe_read(mq->pfd[0], &msg, sizeof(msg)); if (n < 0) return; if (n != sizeof(msg)) { (void)re_fprintf(stderr, "mqueue: short read of %d bytes\n", n); return; } if (msg.magic != MAGIC) { (void)re_fprintf(stderr, "mqueue: bad magic on read (%08x)\n", msg.magic); return; } mq->h(msg.id, msg.data, mq->arg); } /** * Allocate a new Message Queue * * @param mqp Pointer to allocated Message Queue * @param h Message handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int mqueue_alloc(struct mqueue **mqp, mqueue_h *h, void *arg) { struct mqueue *mq; int err = 0; if (!mqp || !h) return EINVAL; mq = mem_zalloc(sizeof(*mq), destructor); if (!mq) return ENOMEM; mq->fhs = NULL; mq->h = h; mq->arg = arg; mq->pfd[0] = mq->pfd[1] = RE_BAD_SOCK; if (pipe(mq->pfd) < 0) { err = RE_ERRNO_SOCK; goto out; } err = net_sockopt_blocking_set(mq->pfd[0], false); if (err) goto out; err = net_sockopt_blocking_set(mq->pfd[1], false); if (err) goto out; err = fd_listen(&mq->fhs, mq->pfd[0], FD_READ, event_handler, mq); if (err) goto out; out: if (err) mem_deref(mq); else *mqp = mq; return err; } /** * Push a new message onto the Message Queue * * @param mq Message Queue * @param id General purpose Identifier * @param data Application data * * @return 0 if success, otherwise errorcode */ int mqueue_push(struct mqueue *mq, int id, void *data) { struct msg msg; ssize_t n; if (!mq) return EINVAL; msg.id = id; msg.data = data; msg.magic = MAGIC; n = pipe_write(mq->pfd[1], &msg, sizeof(msg)); if (n < 0) return errno; return (n != sizeof(msg)) ? EPIPE : 0; } ================================================ FILE: src/mqueue/mqueue.h ================================================ /** * @file mqueue.h Thread Safe Message Queue -- Internal API * * Copyright (C) 2010 Creytiv.com */ #ifdef WIN32 int pipe(re_sock_t fds[2]); ssize_t pipe_read(re_sock_t s, void *buf, size_t len); ssize_t pipe_write(re_sock_t s, const void *buf, size_t len); #else static inline ssize_t pipe_read(re_sock_t s, void *buf, size_t len) { return read(s, buf, len); } static inline ssize_t pipe_write(re_sock_t s, const void *buf, size_t len) { return write(s, buf, len); } #endif ================================================ FILE: src/mqueue/win32/pipe.c ================================================ /** * @file pipe.c Pipe-emulation for Windows * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include "../mqueue.h" /* * Emulate pipe on Windows -- pipe() with select() is not working */ int pipe(re_sock_t fds[2]) { SOCKET s, rd, wr; struct sockaddr_in serv_addr; int len = sizeof(serv_addr); if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) return ENOSYS; memset((void *) &serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(0); serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (bind(s, (SOCKADDR *) & serv_addr, len) == SOCKET_ERROR) goto error; if (listen(s, 1) == SOCKET_ERROR) goto error; if (getsockname(s, (SOCKADDR *) &serv_addr, &len) == SOCKET_ERROR) goto error; if ((wr = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) goto error; if (connect(wr, (SOCKADDR *) &serv_addr, len) == SOCKET_ERROR) { closesocket(wr); goto error; } rd = accept(s, (SOCKADDR *) &serv_addr, &len); if (rd == INVALID_SOCKET) { closesocket(wr); goto error; } fds[0] = rd; fds[1] = wr; closesocket(s); return 0; error: closesocket(s); return ENOSYS; } ssize_t pipe_read(re_sock_t s, void *buf, size_t len) { int ret = recv(s, buf, (int)len, 0); if (ret < 0 && WSAGetLastError() == WSAECONNRESET) ret = 0; return ret; } ssize_t pipe_write(re_sock_t s, const void *buf, size_t len) { return send(s, buf, (int)len, 0); } ================================================ FILE: src/msg/ctype.c ================================================ /** * @file ctype.c Content-Type decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Decode a pointer-length string into Content-Type header * * @param ctype Content-Type header * @param pl Pointer-length string * * @return 0 for success, otherwise errorcode */ int msg_ctype_decode(struct msg_ctype *ctype, const struct pl *pl) { struct pl ws; if (!ctype || !pl) return EINVAL; if (re_regex(pl->p, pl->l, "[ \t\r\n]*[^ \t\r\n;/]+[ \t\r\n]*/[ \t\r\n]*[^ \t\r\n;]+" "[^]*", &ws, &ctype->type, NULL, NULL, &ctype->subtype, &ctype->params)) return EBADMSG; if (ws.p != pl->p) return EBADMSG; return 0; } /** * Compare Content-Type * * @param ctype Content-Type header * @param type Media type * @param subtype Media sub-type * * @return true if match, false if no match */ bool msg_ctype_cmp(const struct msg_ctype *ctype, const char *type, const char *subtype) { if (!ctype || !type || !subtype) return false; if (pl_strcasecmp(&ctype->type, type)) return false; if (pl_strcasecmp(&ctype->subtype, subtype)) return false; return true; } ================================================ FILE: src/msg/param.c ================================================ /** * @file param.c SIP Parameter decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Check if a parameter exists * * @param pl Pointer-length string * @param name Parameter name * @param val Returned parameter value * * @return 0 for success, otherwise errorcode */ int msg_param_exists(const struct pl *pl, const char *name, struct pl *val) { struct pl v1, v2; char xpr[128]; if (!pl || !name || !val) return EINVAL; (void)re_snprintf(xpr, sizeof(xpr), ";[ \t\r\n]*%s[ \t\r\n;=]*", name); if (re_regex(pl->p, pl->l, xpr, &v1, &v2)) return ENOENT; if (!v2.l && v2.p < pl->p + pl->l) return ENOENT; val->p = v1.p - 1; val->l = v2.p - val->p; return 0; } /** * Decode a Parameter * * @param pl Pointer-length string * @param name Parameter name * @param val Returned parameter value * * @return 0 for success, otherwise errorcode */ int msg_param_decode(const struct pl *pl, const char *name, struct pl *val) { char expr[128]; struct pl v; if (!pl || !name || !val) return EINVAL; (void)re_snprintf(expr, sizeof(expr), ";[ \t\r\n]*%s[ \t\r\n]*=[ \t\r\n]*[~ \t\r\n;]+", name); if (re_regex(pl->p, pl->l, expr, NULL, NULL, NULL, &v)) return ENOENT; *val = v; return 0; } ================================================ FILE: src/net/bsd/brt.c ================================================ /** * @file bsd/brt.c BSD routing table code * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include /* * See https://github.com/boundary/libdnet/blob/master/src/route-bsd.c */ #ifdef __APPLE__ #define RT_MSGHDR_ALIGNMENT sizeof(uint32_t) #else #define RT_MSGHDR_ALIGNMENT sizeof(unsigned long) #endif #define ROUNDUP(a) \ ((a) > 0 \ ? (1 + (((size_t)(a) - 1) | (RT_MSGHDR_ALIGNMENT - 1))) \ : RT_MSGHDR_ALIGNMENT) int net_rt_list(net_rt_h *rth, void *arg) { /* net.route.0.inet.flags.gateway */ int mib[] = {CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_FLAGS, RTF_GATEWAY}; char ifname[IFNAMSIZ], *buf, *p; struct rt_msghdr *rt; struct sockaddr *sa, *sa_tab[RTAX_MAX]; struct sa dst, gw; size_t l; int i, err = 0; if (sysctl(mib, sizeof(mib)/sizeof(int), 0, &l, 0, 0) < 0) return errno; if (!l) return ENOENT; buf = mem_alloc(l, NULL); if (!buf) return ENOMEM; if (sysctl(mib, sizeof(mib)/sizeof(int), buf, &l, 0, 0) < 0) { err = errno; goto out; } for (p = buf; prtm_msglen) { rt = (void *)p; /* buffer is aligned */ sa = (struct sockaddr *)(rt + 1); if (rt->rtm_type != RTM_GET) continue; if (!(rt->rtm_flags & RTF_UP)) continue; for (i=0; irtm_addrs & (1 << i)) { sa_tab[i] = sa; sa = (struct sockaddr *) ((char *)sa + ROUNDUP(sa->sa_len)); } else { sa_tab[i] = NULL; } } if ((rt->rtm_addrs & RTA_DST) == RTA_DST) { err = sa_set_sa(&dst, sa_tab[RTAX_DST]); if (err) continue; } if ((rt->rtm_addrs & RTA_GATEWAY) == RTA_GATEWAY) { err = sa_set_sa(&gw, sa_tab[RTAX_GATEWAY]); if (err) continue; } if_indextoname(rt->rtm_index, ifname); if (rth(ifname, &dst, 0, &gw, arg)) break; } out: mem_deref(buf); return err; } ================================================ FILE: src/net/if.c ================================================ /** * @file net/if.c Network interface code * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #define DEBUG_MODULE "netif" #define DEBUG_LEVEL 5 #include /** Interface address entry */ struct ifentry { int af; /**< Address family */ char *ifname; /**< Interface name */ struct sa *ip; /**< IP address */ size_t sz; /**< Size of buffer */ bool found; /**< Found flag */ }; static bool if_getname_handler(const char *ifname, const struct sa *sa, void *arg) { struct ifentry *ife = arg; if (ife->af != sa_af(sa)) return false; if (sa_cmp(sa, ife->ip, SA_ADDR)) { str_ncpy(ife->ifname, ifname, ife->sz); ife->found = true; return true; } return false; } /** * Get the name of the interface for a given IP address * * @param ifname Buffer for returned network interface name * @param sz Size of buffer * @param af Address Family * @param ip Given IP address * * @return 0 if success, otherwise errorcode */ int net_if_getname(char *ifname, size_t sz, int af, const struct sa *ip) { struct ifentry ife; int err; if (!ifname || !sz || !ip) return EINVAL; ife.af = af; ife.ifname = ifname; ife.ip = (struct sa *)ip; ife.sz = sz; ife.found = false; err = net_if_apply(if_getname_handler, &ife); if (err) return err; if (!ife.found) return ENODEV; return 0; } static bool if_getaddr_handler(const char *ifname, const struct sa *sa, void *arg) { struct ifentry *ife = arg; /* Match name of interface? */ if (str_isset(ife->ifname) && 0 != str_casecmp(ife->ifname, ifname)) return false; if (!sa_isset(sa, SA_ADDR)) return false; #if 1 /* skip loopback and link-local IP */ if (sa_is_loopback(sa) || sa_is_linklocal(sa)) return false; #endif /* Match address family */ if (ife->af != sa_af(sa)) return false; /* Match - copy address */ sa_cpy(ife->ip, sa); ife->found = true; return ife->found; } /** * Get IP address for a given network interface * * @param ifname Network interface name (optional) * @param af Address Family * @param ip Returned IP address * * @return 0 if success, otherwise errorcode * * @deprecated Works for IPv4 only */ int net_if_getaddr(const char *ifname, int af, struct sa *ip) { struct ifentry ife; int err; if (!ip) return EINVAL; ife.af = af; ife.ifname = (char *)ifname; ife.ip = ip; ife.sz = 0; ife.found = false; #ifdef HAVE_GETIFADDRS err = net_getifaddrs(if_getaddr_handler, &ife); #else err = net_if_list(if_getaddr_handler, &ife); #endif return ife.found ? err : ENODEV; } static bool if_debug_handler(const char *ifname, const struct sa *sa, void *arg) { struct re_printf *pf = arg; (void)re_hprintf(pf, " %10s: %j\n", ifname, sa); return false; } /** * Debug network interfaces * * @param pf Print handler for debug output * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int net_if_debug(struct re_printf *pf, void *unused) { int err; (void)unused; err = re_hprintf(pf, "net interfaces:\n"); #ifdef HAVE_GETIFADDRS err |= net_getifaddrs(if_debug_handler, pf); #else err |= net_if_list(if_debug_handler, pf); #endif return err; } static bool linklocal_handler(const char *ifname, const struct sa *sa, void *arg) { void **argv = arg; int af = *(int *)argv[1]; if (argv[0] && 0 != str_casecmp(argv[0], ifname)) return false; if (af != AF_UNSPEC && af != sa_af(sa)) return false; if (sa_is_linklocal(sa)) { *((struct sa *)argv[2]) = *sa; return true; } return false; } /** * Get the Link-local address for a specific network interface * * @param ifname Name of the interface * @param af Address family * @param ip Returned link-local address * * @return 0 if success, otherwise errorcode */ int net_if_getlinklocal(const char *ifname, int af, struct sa *ip) { struct sa addr; void *argv[3]; int err; if (!ip) return EINVAL; sa_init(&addr, af); argv[0] = (void *)ifname; argv[1] = ⁡ argv[2] = &addr; err = net_if_apply(linklocal_handler, argv); if (err) return err; if (!sa_isset(&addr, SA_ADDR)) return ENOENT; *ip = addr; return 0; } ================================================ FILE: src/net/ifaddrs.c ================================================ /** * @file ifaddrs.c Network interface code using getifaddrs(). * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #define DEBUG_MODULE "ifaddrs" #define DEBUG_LEVEL 5 #include /** * Get a list of all network interfaces including name and IP address. * Both IPv4 and IPv6 are supported. * * @param ifh Interface handler, called once per network interface. * @param arg Handler argument. * * @return 0 if success, otherwise errorcode. */ int net_getifaddrs(net_ifaddr_h *ifh, void *arg) { struct ifaddrs *ifa, *ifp; int err; if (!ifh) return EINVAL; if (0 != getifaddrs(&ifa)) { err = errno; DEBUG_WARNING("getifaddrs: %m\n", err); return err; } for (ifp = ifa; ifa; ifa = ifa->ifa_next) { struct sa sa; DEBUG_INFO("ifaddr: %10s flags=%08x\n", ifa->ifa_name, ifa->ifa_flags); if (ifa->ifa_flags & IFF_UP) { err = sa_set_sa(&sa, ifa->ifa_addr); if (err) continue; if (ifh(ifa->ifa_name, &sa, arg)) break; } } freeifaddrs(ifp); return 0; } ================================================ FILE: src/net/linux/addrs.c ================================================ /** * @file linux/addrs.c Get interface addresses (See rtnetlink(7)) * * Copyright (C) 2024 Sebastian Reimers */ #include #include #include #include #include #include #include #include #include #include #include #include "macros.h" #define DEBUG_MODULE "linuxaddrs" #define DEBUG_LEVEL 5 #include enum { RE_NETLINK_BUFSZ = 8192 }; struct iff_up_e { struct le le; uint32_t ifi_index; }; static void parse_rtattr(struct rtattr *tb[], struct rtattr *rta, int len) { memset(tb, 0, sizeof(struct rtattr *) * (IFA_MAX + 1)); while (RTA_OK(rta, len)) { if (rta->rta_type <= IFA_MAX) { tb[rta->rta_type] = rta; } rta = RTA_NEXT(rta, len); } } static bool is_ipv6_deprecated(uint32_t flags) { if (flags & (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC | IFA_F_DADFAILED | IFA_F_DEPRECATED)) return true; return false; } static int parse_msg_link(struct nlmsghdr *msg, ssize_t len, struct list *iff_up_l) { struct nlmsghdr *nlh; struct ifinfomsg *ifi; for (nlh = msg; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type == NLMSG_DONE) { return 0; } if (nlh->nlmsg_type == NLMSG_ERROR) { DEBUG_WARNING("netlink recv error\n"); return EBADMSG; } ifi = NLMSG_DATA(nlh); if (!(ifi->ifi_flags & IFF_UP)) continue; struct iff_up_e *e = mem_zalloc(sizeof(struct iff_up_e), NULL); if (!e) return ENOMEM; e->ifi_index = ifi->ifi_index; list_append(iff_up_l, &e->le, e); } return EALREADY; } static int parse_msg_addr(struct nlmsghdr *msg, ssize_t len, net_ifaddr_h *ifh, struct list *iff_up_l, void *arg) { struct nlmsghdr *nlh; for (nlh = msg; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { struct sa sa; uint32_t flags; bool iff_up = false; void *addr; char if_name[IF_NAMESIZE]; if (nlh->nlmsg_type == NLMSG_DONE) { return 0; } if (nlh->nlmsg_type == NLMSG_ERROR) { DEBUG_WARNING("netlink recv error\n"); return EBADMSG; } struct ifaddrmsg *ifa = NLMSG_DATA(nlh); struct rtattr *rta_tb[IFA_MAX + 1]; parse_rtattr(rta_tb, IFA_RTA(ifa), nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa))); if (!rta_tb[IFA_ADDRESS]) continue; struct le *le; LIST_FOREACH(iff_up_l, le) { struct iff_up_e *e = le->data; if (ifa->ifa_index == e->ifi_index) { iff_up = true; break; } } if (!iff_up) continue; if (rta_tb[IFA_FLAGS] && ifa->ifa_family == AF_INET6) { flags = *(uint32_t *)RTA_DATA(rta_tb[IFA_FLAGS]); if (is_ipv6_deprecated(flags)) continue; } if (rta_tb[IFA_LOCAL]) /* looks like point-to-point network, use local * address, instead of peer */ addr = RTA_DATA(rta_tb[IFA_LOCAL]); else addr = RTA_DATA(rta_tb[IFA_ADDRESS]); if (ifa->ifa_family == AF_INET) { sa_init(&sa, AF_INET); sa.u.in.sin_addr.s_addr = *(uint32_t *)addr; } else if (ifa->ifa_family == AF_INET6) { sa_set_in6(&sa, addr, 0); sa_set_scopeid(&sa, ifa->ifa_index); } else continue; if (!if_indextoname(ifa->ifa_index, if_name)) continue; if (ifh(if_name, &sa, arg)) return 0; } return EALREADY; } int net_netlink_addrs(net_ifaddr_h *ifh, void *arg) { int err = 0; re_sock_t sock; ssize_t len; struct list iff_up_l = LIST_INIT; struct { struct nlmsghdr nlh; union { struct ifinfomsg ifi; struct ifaddrmsg ifa; } u; } req; if (!ifh) return EINVAL; void *buffer = mem_zalloc(RE_NETLINK_BUFSZ, NULL); if (!buffer) return ENOMEM; if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { err = errno; DEBUG_WARNING("socket failed %m\n", err); return err; } struct timeval timeout = {5, 0}; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); /* GETLINK */ memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.nlh.nlmsg_type = RTM_GETLINK; if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) { err = errno; DEBUG_WARNING("GETLINK send failed %m\n", err); goto out; } while ((len = recv(sock, buffer, RE_NETLINK_BUFSZ, 0)) > 0) { err = parse_msg_link((struct nlmsghdr *)buffer, len, &iff_up_l); if (err != EALREADY) break; } if (err) goto out; if (len < 0) { err = errno; DEBUG_WARNING("GETLINK recv failed %m\n", err); goto out; } /* GETADDR */ memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.nlh.nlmsg_type = RTM_GETADDR; if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) { err = errno; DEBUG_WARNING("GETADDR send failed %m\n", err); goto out; } while ((len = recv(sock, buffer, RE_NETLINK_BUFSZ, 0)) > 0) { err = (parse_msg_addr((struct nlmsghdr *)buffer, len, ifh, &iff_up_l, arg)); if (err != EALREADY) break; } if (err) goto out; if (len < 0) { err = errno; DEBUG_WARNING("GETADDR recv failed %m\n", err); } out: close(sock); list_flush(&iff_up_l); mem_deref(buffer); return err; } ================================================ FILE: src/net/linux/macros.h ================================================ /* Override macros to avoid casting alignment warning */ #undef RTM_RTA #define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))) #undef RTA_NEXT #define RTA_NEXT(rta, len) \ ((len) -= RTA_ALIGN((rta)->rta_len), \ (void *)(((char *)(rta)) + RTA_ALIGN((rta)->rta_len))) #undef NLMSG_NEXT #define NLMSG_NEXT(nlh, len) \ ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (void *)(((char *)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) #undef IFA_RTA #define IFA_RTA(r) \ ((void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) ================================================ FILE: src/net/linux/rt.c ================================================ /** * @file linux/rt.c Routing table code for Linux. See rtnetlink(7) * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "macros.h" #define DEBUG_MODULE "linuxrt" #define DEBUG_LEVEL 5 #include enum {BUFSIZE = 8192}; /** Defines a network route */ struct net_rt { char ifname[IFNAMSIZ]; /**< Interface name */ struct sa dst; /**< Destination IP address/network */ int dstlen; /**< Prefix length of destination */ struct sa gw; /**< Gateway IP address */ }; static int read_sock(int fd, uint8_t *buf, size_t size, uint32_t seq, int pid) { struct nlmsghdr *nlhdr; int n = 0, len = 0; do { /* Receive response from the kernel */ if ((n = (int)recv(fd, buf, size - len, 0)) < 0) { DEBUG_WARNING("SOCK READ: %m\n", errno); return -1; } nlhdr = (struct nlmsghdr *)(void *)buf; /* Check if the header is valid */ if (0 == NLMSG_OK(nlhdr, (uint32_t)n) || NLMSG_ERROR == nlhdr->nlmsg_type) { DEBUG_WARNING("Error in received packet\n"); return -1; } /* Check if the its the last message */ if (NLMSG_DONE == nlhdr->nlmsg_type) { break; } else{ /* Else move the pointer to buffer appropriately */ buf += n; len += n; } /* Check if its a multi part message */ if (0 == (nlhdr->nlmsg_flags & NLM_F_MULTI)) { /* return if its not */ break; } } while (nlhdr->nlmsg_seq != seq || nlhdr->nlmsg_pid != (uint32_t)pid); return len; } /* Parse one route */ static int rt_parse(const struct nlmsghdr *nlhdr, struct net_rt *rt) { struct rtmsg *rtmsg; struct rtattr *rtattr; int len; rtmsg = (struct rtmsg *)NLMSG_DATA(nlhdr); /* If the route does not belong to main routing table then return. */ if (RT_TABLE_MAIN != rtmsg->rtm_table) return EINVAL; sa_init(&rt->dst, rtmsg->rtm_family); rt->dstlen = rtmsg->rtm_dst_len; sa_init(&rt->gw, rtmsg->rtm_family); /* get the rtattr field */ rtattr = (struct rtattr *)RTM_RTA(rtmsg); len = RTM_PAYLOAD(nlhdr); for (;RTA_OK(rtattr, len); rtattr = RTA_NEXT(rtattr, len)) { switch (rtattr->rta_type) { case RTA_OIF: if_indextoname(*(int *)RTA_DATA(rtattr), rt->ifname); break; case RTA_GATEWAY: switch (rtmsg->rtm_family) { case AF_INET: sa_init(&rt->gw, AF_INET); rt->gw.u.in.sin_addr.s_addr = *(uint32_t *)RTA_DATA(rtattr); break; case AF_INET6: sa_set_in6(&rt->gw, RTA_DATA(rtattr), 0); break; default: DEBUG_WARNING("RTA_GW: unknown family %d\n", rtmsg->rtm_family); break; } break; case RTA_DST: switch (rtmsg->rtm_family) { case AF_INET: sa_init(&rt->dst, AF_INET); rt->dst.u.in.sin_addr.s_addr = *(uint32_t *)RTA_DATA(rtattr); break; case AF_INET6: sa_set_in6(&rt->dst, RTA_DATA(rtattr), 0); break; default: DEBUG_WARNING("RTA_DST: unknown family %d\n", rtmsg->rtm_family); break; } break; } } return 0; } /** * List all entries in the routing table * * @param rth Route entry handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int net_rt_list(net_rt_h *rth, void *arg) { union { uint8_t buf[BUFSIZE]; struct nlmsghdr msg[1]; } u; struct nlmsghdr *nlmsg; uint32_t seq = 0; int sock, len, err = 0; if (!rth) return EINVAL; /* Create Socket */ if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) { DEBUG_WARNING("list: socket(): (%m)\n", errno); return errno; } /* Initialize the buffer */ memset(u.buf, 0, sizeof(u.buf)); /* point the header and the msg structure pointers into the buffer */ nlmsg = u.msg; /* Fill in the nlmsg header*/ nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); nlmsg->nlmsg_type = RTM_GETROUTE; nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; nlmsg->nlmsg_seq = seq++; nlmsg->nlmsg_pid = getpid(); /* Send the request */ if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) { err = errno; DEBUG_WARNING("list: write to socket failed (%m)\n", err); goto out; } /* Read the response */ if ((len = read_sock(sock, u.buf, sizeof(u.buf), seq, getpid())) < 0) { err = errno; DEBUG_WARNING("list: read from socket failed (%m)\n", err); goto out; } /* Parse and print the response */ for (; NLMSG_OK(nlmsg,(uint32_t)len); nlmsg = NLMSG_NEXT(nlmsg,len)) { struct net_rt rt; memset(&rt, 0, sizeof(struct net_rt)); if (0 != rt_parse(nlmsg, &rt)) continue; if (AF_INET6 == sa_af(&rt.dst) && IN6_IS_ADDR_UNSPECIFIED(&rt.dst.u.in6.sin6_addr)) continue; if (rth(rt.ifname, &rt.dst, rt.dstlen, &rt.gw, arg)) break; } out: close(sock); return err; } ================================================ FILE: src/net/net.c ================================================ /** * @file net.c Networking code. * * Copyright (C) 2010 Creytiv.com */ #include #include #if !defined(WIN32) #include #include #endif #include #include #include #include #include #include #include #define DEBUG_MODULE "net" #define DEBUG_LEVEL 5 #include /** * Get the source IP address for a specified destination * * @param dst Destination IP address * @param ip Returned Source IP address * * @return 0 if success, otherwise errorcode */ int net_dst_source_addr_get(const struct sa *dst, struct sa *ip) { int err; struct udp_sock *us; if (!dst || !ip || !sa_isset(dst, SA_ADDR)) { return EINVAL; } if (sa_af(dst) == AF_INET6) err = sa_set_str(ip, "::", 0); else err = sa_set_str(ip, "0.0.0.0", 0); if (err) return err; err = udp_listen(&us, ip, NULL, NULL); if (err) return err; err = udp_connect(us, dst); if (err) goto out; err = udp_local_get(us, ip); out: mem_deref(us); return err; } /** * Get the default source IP address * * @param af Address Family * @param ip Returned IP address * * @return 0 if success, otherwise errorcode */ int net_default_source_addr_get(int af, struct sa *ip) { struct sa dst; int err; #if !defined(WIN32) char ifname[64] = ""; #endif sa_init(&dst, af); if (af == AF_INET6) sa_set_str(&dst, "1::1", 53); else sa_set_str(&dst, "1.1.1.1", 53); err = net_dst_source_addr_get(&dst, ip); if (af == AF_INET6 && sa_is_linklocal(ip)) { sa_init(ip, af); return 0; } if (!err) return 0; #ifdef WIN32 return err; #else #ifdef HAVE_ROUTE_LIST /* Get interface with default route */ (void)net_rt_default_get(af, ifname, sizeof(ifname)); #endif /* First try with default interface */ if (0 == net_if_getaddr(ifname, af, ip)) return 0; /* Then try first real IP */ return net_if_getaddr(NULL, af, ip); #endif } /** * Get a list of all network interfaces including name and IP address. * Both IPv4 and IPv6 are supported * * @param ifh Interface handler, called once per network interface * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int net_if_apply(net_ifaddr_h *ifh, void *arg) { #ifdef LINUX return net_netlink_addrs(ifh, arg); #elif HAVE_GETIFADDRS return net_getifaddrs(ifh, arg); #else return net_if_list(ifh, arg); #endif } static bool net_rt_handler(const char *ifname, const struct sa *dst, int dstlen, const struct sa *gw, void *arg) { void **argv = arg; struct sa *ip = argv[1]; (void)dst; (void)dstlen; if (0 == str_cmp(ifname, argv[0])) { *ip = *gw; return true; } return false; } /** * Get the IP-address of the default gateway * * @param af Address Family * @param gw Returned Gateway address * * @return 0 if success, otherwise errorcode */ int net_default_gateway_get(int af, struct sa *gw) { char ifname[64]; void *argv[2]; int err; if (!af || !gw) return EINVAL; err = net_rt_default_get(af, ifname, sizeof(ifname)); if (err) return err; argv[0] = ifname; argv[1] = gw; err = net_rt_list(net_rt_handler, argv); if (err) return err; return 0; } ================================================ FILE: src/net/netstr.c ================================================ /** * @file netstr.c Network strings * * Copyright (C) 2010 Creytiv.com */ #include #include #include /** * Get the name of a protocol * * @param proto Protocol * * @return Protocol name */ const char *net_proto2name(int proto) { switch (proto) { case IPPROTO_UDP: return "UDP"; case IPPROTO_TCP: return "TCP"; #ifdef IPPROTO_SCTP case IPPROTO_SCTP: return "SCTP"; #endif default: return "???"; } } /** * Get the name of a address family * * @param af Address family * * @return Address family name */ const char *net_af2name(int af) { switch (af) { case AF_UNSPEC: return "AF_UNSPEC"; case AF_INET: return "AF_INET"; case AF_INET6: return "AF_INET6"; default: return "???"; } } ================================================ FILE: src/net/posix/pif.c ================================================ /** * @file posix/pif.c POSIX network interface code * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include /*#include */ #ifdef __sun #include #endif #include #include #include #include #include #define DEBUG_MODULE "posixif" #define DEBUG_LEVEL 5 #include /** * Enumerate all network interfaces * * @param ifh Interface handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode * * @deprecated Works for IPv4 only */ int net_if_list(net_ifaddr_h *ifh, void *arg) { struct ifreq ifrv[32], *ifr; struct ifconf ifc; int sockfd = -1; int err = 0; if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) { err = errno; DEBUG_WARNING("interface list: socket(): (%m)\n", err); goto out; } ifc.ifc_len = sizeof(ifrv); ifc.ifc_req = ifrv; if (0 != ioctl(sockfd, SIOCGIFCONF, &ifc)) { err = errno; DEBUG_WARNING("interface list: ioctl SIOCFIFCONF: %m\n", err); goto out; } for (ifr = ifc.ifc_req; (char *)ifr < ((char *)ifc.ifc_buf + ifc.ifc_len); ++ifr) { struct ifreq ifrr; struct sa sa; if (ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data) continue; /* duplicate, skip it */ if (ioctl(sockfd, SIOCGIFFLAGS, ifr)) continue; /* failed to get flags, skip it */ #if 0 if (ifr->ifr_flags & IFF_LOOPBACK) continue; #endif if (!(ifr->ifr_flags & IFF_UP)) continue; ifrr.ifr_addr.sa_family = AF_INET; str_ncpy(ifrr.ifr_name, ifr->ifr_name, sizeof(ifrr.ifr_name)); if (ioctl(sockfd, SIOCGIFADDR, &ifrr) < 0) { err = errno; continue; } err = sa_set_sa(&sa, &ifrr.ifr_ifru.ifru_addr); if (err) { DEBUG_WARNING("if_list: sa_set_sa %m\n", err); break; } if (ifh && ifh(ifr->ifr_name, &sa, arg)) break; } out: if (sockfd >= 0) (void)close(sockfd); return err; } ================================================ FILE: src/net/rt.c ================================================ /** * @file net/rt.c Generic routing table code * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include struct net_rt { int af; char *ifname; size_t size; int prefix; }; static bool rt_debug_handler(const char *ifname, const struct sa *dst, int dstlen, const struct sa *gw, void *arg) { char addr[64]; struct re_printf *pf = arg; int err = 0; (void)re_snprintf(addr, sizeof(addr), "%j/%d", dst, dstlen); err |= re_hprintf(pf, " %-44s", addr); err |= re_hprintf(pf, "%-40j", gw); err |= re_hprintf(pf, " %-15s ", ifname); if (AF_INET6 == sa_af(dst)) { const struct sockaddr_in6 *sin6 = &dst->u.in6; const struct in6_addr *in6 = &sin6->sin6_addr; if (IN6_IS_ADDR_MULTICAST(in6)) err |= re_hprintf(pf, " MULTICAST"); if (IN6_IS_ADDR_LINKLOCAL(in6)) err |= re_hprintf(pf, " LINKLOCAL"); if (IN6_IS_ADDR_SITELOCAL(in6)) err |= re_hprintf(pf, " SITELOCAL"); } err |= re_hprintf(pf, "\n"); return 0 != err; } /** * Dump the routing table * * @param pf Print function for output * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int net_rt_debug(struct re_printf *pf, void *unused) { int err = 0; (void)unused; err |= re_hprintf(pf, "net routes:\n"); err |= re_hprintf(pf, " Destination " "Next Hop" " Iface " "Type\n"); err |= net_rt_list(rt_debug_handler, pf); return err; } static bool rt_default_get_handler(const char *_ifname, const struct sa *dst, int dstlen, const struct sa *gw, void *arg) { struct net_rt *rt = arg; (void)dstlen; (void)gw; if (sa_af(dst) != rt->af) return false; switch (rt->af) { case AF_INET: if (0 == sa_in(dst)) { str_ncpy(rt->ifname, _ifname, rt->size); return true; } break; case AF_INET6: if (IN6_IS_ADDR_MULTICAST(&dst->u.in6.sin6_addr)) return false; if (IN6_IS_ADDR_LINKLOCAL(&dst->u.in6.sin6_addr)) return false; if (dstlen < rt->prefix) { rt->prefix = dstlen; str_ncpy(rt->ifname, _ifname, rt->size); return false; } break; } return false; } /** * Get the interface name of the default route * * @param af Address family * @param ifname Buffer for returned interface name * @param size Size of buffer * * @return 0 if success, otherwise errorcode */ int net_rt_default_get(int af, char *ifname, size_t size) { struct net_rt rt; int err; rt.af = af; rt.ifname = ifname; rt.size = size; rt.prefix = 256; err = net_rt_list(rt_default_get_handler, &rt); if (err) return err; return '\0' != ifname[0] ? 0 : EINVAL; } #ifndef HAVE_ROUTE_LIST /* We must provide a stub */ int net_rt_list(net_rt_h *rth, void *arg) { (void)rth; (void)arg; return ENOSYS; } #endif ================================================ FILE: src/net/sock.c ================================================ /** * @file net/sock.c Networking sockets code * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #define DEBUG_MODULE "netsock" #define DEBUG_LEVEL 5 #include static bool inited = false; #ifdef WIN32 static int wsa_init(void) { WORD wVersionRequested = MAKEWORD(2, 2); WSADATA wsaData; int err; err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { DEBUG_WARNING("Could not load winsock (%m)\n", err); return err; } /* Confirm that the WinSock DLL supports 2.2.*/ /* Note that if the DLL supports versions greater */ /* than 2.2 in addition to 2.2, it will still return */ /* 2.2 in wVersion since that is the version we */ /* requested. */ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2 ) { WSACleanup(); DEBUG_WARNING("Bad winsock version (%d.%d)\n", HIBYTE(wsaData.wVersion), LOBYTE(wsaData.wVersion)); return EINVAL; } return 0; } #endif /** * Initialise network sockets * * @return 0 if success, otherwise errorcode */ int net_sock_init(void) { int err = 0; DEBUG_INFO("sock init: inited=%d\n", inited); if (inited) return 0; #ifdef WIN32 err = wsa_init(); #endif inited = true; return err; } /** * Cleanup network sockets */ void net_sock_close(void) { #ifdef WIN32 const int err = WSACleanup(); if (0 != err) { DEBUG_WARNING("sock close: WSACleanup (%d)\n", err); } #endif inited = false; DEBUG_INFO("sock close\n"); } ================================================ FILE: src/net/sockopt.c ================================================ /** * @file sockopt.c Networking socket options * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #define DEBUG_MODULE "sockopt" #define DEBUG_LEVEL 5 #include /** Platform independent buffer type cast */ #ifdef WIN32 #define BUF_CAST (char *) #else #define BUF_CAST #endif /** * Set socket option blocking or non-blocking * * @param fd Socket file descriptor * @param blocking true for blocking, false for non-blocking * * @return 0 if success, otherwise errorcode */ int net_sockopt_blocking_set(re_sock_t fd, bool blocking) { #ifdef WIN32 unsigned long noblock = !blocking; int err = 0; if (0 != ioctlsocket(fd, FIONBIO, &noblock)) { err = WSAGetLastError(); DEBUG_WARNING("nonblock set: fd=%d err=%d (%m)\n", fd, err, err); } return err; #else int flags; int err = 0; flags = fcntl(fd, F_GETFL); if (-1 == flags) { err = errno; DEBUG_WARNING("sockopt set: fnctl F_GETFL: (%m)\n", err); goto out; } if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; if (-1 == fcntl(fd, F_SETFL, flags)) { err = errno; DEBUG_WARNING("sockopt set: fcntl F_SETFL non-block (%m)\n", err); } out: return err; #endif } /** * Set socket option to reuse address and port * * @param fd Socket file descriptor * @param reuse true for reuse, false for no reuse * * @return 0 if success, otherwise errorcode */ int net_sockopt_reuse_set(re_sock_t fd, bool reuse) { int r = reuse; #ifdef SO_REUSEADDR if (-1 == setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, BUF_CAST &r, sizeof(r))) { DEBUG_WARNING("SO_REUSEADDR: %m\n", errno); return errno; } #endif #ifdef SO_REUSEPORT if (-1 == setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, BUF_CAST &r, sizeof(r))) { DEBUG_INFO("SO_REUSEPORT: %m\n", errno); return errno; } #endif #if !defined(SO_REUSEADDR) && !defined(SO_REUSEPORT) (void)r; (void)fd; (void)reuse; return ENOSYS; #else return 0; #endif } /** * Set socket IPV6_V6ONLY option (not supported on OpenBSD - readonly) * * @param fd Socket file descriptor * @param only true for IPv6 only, false for dual socket * * @return 0 if success, otherwise errorcode */ int net_sockopt_v6only(re_sock_t fd, bool only) { int on = only; #ifndef OPENBSD #ifdef IPV6_V6ONLY if (-1 == setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, BUF_CAST &on, sizeof(on))) { int err = RE_ERRNO_SOCK; DEBUG_WARNING("IPV6_V6ONLY: %m\n", err); return err; } #endif #endif return 0; } ================================================ FILE: src/net/win32/wif.c ================================================ /** * @file wif.c Windows network interface code * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #define DEBUG_MODULE "wif" #define DEBUG_LEVEL 5 #include /** * List interfaces using GetAdaptersAddresses, which handles both * IPv4 and IPv6 address families. * * This is available from Windows XP and Windows Server 2003 */ static int if_list_gaa(net_ifaddr_h *ifh, void *arg) { IP_ADAPTER_ADDRESSES addrv[64], *cur; ULONG ret, len = sizeof(addrv); const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST; HANDLE hLib; union { FARPROC proc; ULONG (WINAPI *gaa)(ULONG, ULONG, PVOID, PIP_ADAPTER_ADDRESSES, PULONG); } u; bool stop = false; int err = 0; hLib = LoadLibrary(TEXT("iphlpapi.dll")); if (!hLib) return ENOSYS; u.proc = GetProcAddress(hLib, TEXT("GetAdaptersAddresses")); if (!u.proc) { err = ENOSYS; goto out; } ret = (*u.gaa)(AF_UNSPEC, flags, NULL, addrv, &len); if (ret != ERROR_SUCCESS) { DEBUG_WARNING("if_list: GetAdaptersAddresses ret=%u\n", ret); err = ENODEV; goto out; } for (cur = addrv; cur && !stop; cur = cur->Next) { PIP_ADAPTER_UNICAST_ADDRESS ip; if (cur->OperStatus != IfOperStatusUp) continue; /* an interface can have many IP-addresses */ for (ip = cur->FirstUnicastAddress; ip; ip = ip->Next) { struct sa sa; sa_set_sa(&sa, ip->Address.lpSockaddr); if (ifh && ifh(cur->AdapterName, &sa, arg)) { stop = true; break; } } } out: FreeLibrary(hLib); return err; } /** * List interfaces using GetAdaptersInfo, which handles only IPv4 family. * * This is available from Windows 2000, and also works under Wine. */ static int if_list_gai(net_ifaddr_h *ifh, void *arg) { IP_ADAPTER_INFO info[32]; PIP_ADAPTER_INFO p = info; ULONG ulOutBufLen = sizeof(info); DWORD ret; ret = GetAdaptersInfo(info, &ulOutBufLen); if (ret != ERROR_SUCCESS) { DEBUG_WARNING("if_list: GetAdaptersInfo ret=%u\n", ret); return ENODEV; } for (p = info; p; p = p->Next) { struct sa sa; if (sa_set_str(&sa, p->IpAddressList.IpAddress.String, 0)) continue; if (ifh && ifh(p->AdapterName, &sa, arg)) break; } return 0; } /* * Enumerate all network interfaces * * @param ifh Interface handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int net_if_list(net_ifaddr_h *ifh, void *arg) { /* Try both methods .. */ if (!if_list_gaa(ifh, arg)) return 0; return if_list_gai(ifh, arg); } ================================================ FILE: src/odict/entry.c ================================================ /** * @file odict/entry.c Ordered Dictionary -- entry * * Copyright (C) 2010 - 2015 Creytiv.com */ #include "re_types.h" #include "re_fmt.h" #include "re_mem.h" #include "re_list.h" #include "re_hash.h" #include "re_odict.h" #include "odict.h" static void destructor(void *arg) { struct odict_entry *e = arg; switch (e->type) { case ODICT_OBJECT: case ODICT_ARRAY: mem_deref(e->u.odict); break; case ODICT_STRING: mem_deref(e->u.str); break; default: break; } hash_unlink(&e->he); list_unlink(&e->le); mem_deref(e->key); } int odict_entry_add(struct odict *o, const char *key, int type, ...) { struct odict_entry *e; va_list ap; int err; if (!o || !key) return EINVAL; e = mem_zalloc(sizeof(*e), destructor); if (!e) return ENOMEM; e->type = type; err = str_dup(&e->key, key); if (err) goto out; va_start(ap, type); switch (e->type) { case ODICT_OBJECT: case ODICT_ARRAY: e->u.odict = mem_ref(va_arg(ap, struct odict *)); break; case ODICT_STRING: err = str_dup(&e->u.str, va_arg(ap, const char *)); break; case ODICT_INT: e->u.integer = va_arg(ap, int64_t); break; case ODICT_DOUBLE: e->u.dbl = va_arg(ap, double); break; case ODICT_BOOL: e->u.boolean = va_arg(ap, int); break; case ODICT_NULL: break; default: err = EINVAL; break; } va_end(ap); if (err) goto out; list_append(&o->lst, &e->le, e); hash_append(o->ht, hash_fast_str(e->key), &e->he, e); out: if (err) mem_deref(e); return err; } int odict_pl_add(struct odict *od, const char *key, const struct pl *val) { char *str; int err = pl_strdup(&str, val); if (err) return err; err = odict_entry_add(od, key, ODICT_STRING, str); mem_deref(str); return err; } void odict_entry_del(struct odict *o, const char *key) { mem_deref((struct odict_entry *)odict_lookup(o, key)); } int odict_entry_debug(struct re_printf *pf, const struct odict_entry *e) { int err; if (!e) return 0; err = re_hprintf(pf, "%s", e->key); switch (e->type) { case ODICT_OBJECT: case ODICT_ARRAY: err |= re_hprintf(pf, ":%H", odict_debug, e->u.odict); break; case ODICT_STRING: err |= re_hprintf(pf, ":%s", e->u.str); break; case ODICT_INT: err |= re_hprintf(pf, ":%lli", e->u.integer); break; case ODICT_DOUBLE: err |= re_hprintf(pf, ":%f", e->u.dbl); break; case ODICT_BOOL: err |= re_hprintf(pf, ":%s", e->u.boolean ? "true" : "false"); break; case ODICT_NULL: case ODICT_ERR: break; } return err; } ================================================ FILE: src/odict/get.c ================================================ /** * @file get.c Ordered Dictionary -- high level accessors * * Copyright (C) 2010 Creytiv.com */ #include "re_types.h" #include "re_fmt.h" #include "re_mem.h" #include "re_list.h" #include "re_hash.h" #include "re_odict.h" #include "odict.h" struct odict *odict_get_object(const struct odict *o, const char *key) { const struct odict_entry *entry; if (!o || !key) return NULL; entry = odict_get_type(o, ODICT_OBJECT, key); if (!entry) return NULL; return entry->u.odict; } struct odict *odict_get_array(const struct odict *o, const char *key) { const struct odict_entry *entry; if (!o || !key) return NULL; entry = odict_get_type(o, ODICT_ARRAY, key); if (!entry) return NULL; return entry->u.odict; } struct odict *odict_entry_object(const struct odict_entry *e) { if (!e || e->type != ODICT_OBJECT ) return NULL; return e->u.odict; } struct odict *odict_entry_array(const struct odict_entry *e) { if (!e || e->type != ODICT_ARRAY) return NULL; return e->u.odict; } char *odict_entry_str(const struct odict_entry *e) { if (!e || e->type != ODICT_STRING) return NULL; return e->u.str; } int64_t odict_entry_int(const struct odict_entry *e) { if (!e || e->type != ODICT_INT) return 0; return e->u.integer; } double odict_entry_dbl(const struct odict_entry *e) { if (!e || e->type != ODICT_DOUBLE) return 0.0; return e->u.dbl; } bool odict_entry_boolean(const struct odict_entry *e) { if (!e || e->type != ODICT_BOOL) return false; return e->u.boolean; } enum odict_type odict_entry_type(const struct odict_entry *e) { if (!e) return ODICT_ERR; return e->type; } const char *odict_entry_key(const struct odict_entry *e) { if (!e) return NULL; return e->key; } const struct odict_entry *odict_get_type(const struct odict *o, enum odict_type type, const char *key) { const struct odict_entry *entry; if (!o || !key) return NULL; entry = odict_lookup(o, key); if (!entry) return NULL; if (entry->type != type) return NULL; return entry; } const char *odict_string(const struct odict *o, const char *key) { const struct odict_entry *entry; entry = odict_get_type(o, ODICT_STRING, key); if (!entry) return NULL; return entry->u.str; } bool odict_get_number(const struct odict *o, uint64_t *num, const char *key) { const struct odict_entry *entry; if (!o || !key) return false; entry = odict_lookup(o, key); if (!entry) return false; switch (entry->type) { case ODICT_DOUBLE: if (num) *num = (uint64_t)entry->u.dbl; break; case ODICT_INT: if (num) *num = entry->u.integer; break; default: return false; } return true; } bool odict_get_boolean(const struct odict *o, bool *value, const char *key) { const struct odict_entry *entry; entry = odict_get_type(o, ODICT_BOOL, key); if (!entry) return false; if (value) *value = entry->u.boolean; return true; } ================================================ FILE: src/odict/odict.c ================================================ /** * @file odict.c Ordered Dictionary * * Copyright (C) 2010 Creytiv.com */ #include "re_types.h" #include "re_fmt.h" #include "re_mem.h" #include "re_list.h" #include "re_hash.h" #include "re_odict.h" #include "odict.h" #include #include static void destructor(void *arg) { struct odict *o = arg; hash_clear(o->ht); list_flush(&o->lst); mem_deref(o->ht); } int odict_alloc(struct odict **op, uint32_t hash_size) { struct odict *o; int err; if (!op || !hash_size) return EINVAL; o = mem_zalloc(sizeof(*o), destructor); if (!o) return ENOMEM; err = hash_alloc(&o->ht, hash_valid_size(hash_size)); if (err) goto out; out: if (err) mem_deref(o); else *op = o; return err; } const struct odict_entry *odict_lookup(const struct odict *o, const char *key) { struct le *le; if (!o || !key) return NULL; le = list_head(hash_list(o->ht, hash_fast_str(key))); while (le) { const struct odict_entry *e = le->data; if (!str_cmp(e->key, key)) return e; le = le->next; } return NULL; } size_t odict_count(const struct odict *o, bool nested) { struct le *le; size_t n = 0; if (!o) return 0; if (!nested) return list_count(&o->lst); for (le=o->lst.head; le; le=le->next) { const struct odict_entry *e = le->data; switch (e->type) { case ODICT_OBJECT: case ODICT_ARRAY: n += odict_count(e->u.odict, true); break; default: n += 1; /* count all entries */ break; } } return n; } int odict_debug(struct re_printf *pf, const struct odict *o) { struct le *le; int err; if (!o) return 0; err = re_hprintf(pf, "{"); for (le=o->lst.head; le; le=le->next) { const struct odict_entry *e = le->data; err |= re_hprintf(pf, " %H", odict_entry_debug, e); } err |= re_hprintf(pf, " }"); return err; } static bool cmp_double(double a, double b) { return fabs(a - b) < DBL_EPSILON; } bool odict_value_compare(const struct odict_entry *e1, const struct odict_entry *e2, bool ignore_order) { if (!e1 || !e2) return false; if (odict_entry_type(e1) != odict_entry_type(e2)) return false; switch (odict_entry_type(e1)) { case ODICT_OBJECT: return odict_compare(odict_entry_object(e1), odict_entry_object(e2), ignore_order); case ODICT_ARRAY: return odict_compare(odict_entry_array(e1), odict_entry_array(e2), ignore_order); case ODICT_INT: if (odict_entry_int(e1) == odict_entry_int(e2)) return true; break; case ODICT_DOUBLE: if (cmp_double(odict_entry_dbl(e1), odict_entry_dbl(e2))) return true; break; case ODICT_STRING: if ( 0 == str_cmp(odict_entry_str(e1), odict_entry_str(e2))) return true; break; case ODICT_BOOL: if (odict_entry_boolean(e1) == odict_entry_boolean(e2)) return true; break; case ODICT_NULL: /* no check */ return true; default: return false; } return false; } /* return TRUE if equal */ bool odict_compare(const struct odict *dict1, const struct odict *dict2, bool ignore_order) { struct le *le1, *le2; if (!dict1 || !dict2) return false; if (odict_count(dict1, true) != odict_count(dict2, true)) return false; for (le1 = dict1->lst.head, le2 = dict2->lst.head; le1 && le2; le1 = le1->next, le2 = le2->next) { const struct odict_entry *e1 = le1->data; const struct odict_entry *e2; if (ignore_order) e2 = odict_lookup(dict2, odict_entry_key(e1)); else e2 = le2->data; if (0 != str_cmp(odict_entry_key(e1), odict_entry_key(e2))) return false; if (!odict_value_compare(e1, e2, ignore_order)) return false; } return true; /* equal */ } ================================================ FILE: src/odict/odict.h ================================================ struct odict_entry { struct le le, he; char *key; union { struct odict *odict; /* ODICT_OBJECT / ODICT_ARRAY */ char *str; /* ODICT_STRING */ int64_t integer; /* ODICT_INT */ double dbl; /* ODICT_DOUBLE */ bool boolean; /* ODICT_BOOL */ } u; enum odict_type type; }; ================================================ FILE: src/odict/type.c ================================================ /** * @file type.c Ordered Dictionary -- value types * * Copyright (C) 2010 - 2015 Creytiv.com */ #include "re_types.h" #include "re_fmt.h" #include "re_mem.h" #include "re_list.h" #include "re_hash.h" #include "re_odict.h" bool odict_type_iscontainer(enum odict_type type) { switch (type) { case ODICT_OBJECT: case ODICT_ARRAY: return true; default: return false; } } bool odict_type_isreal(enum odict_type type) { switch (type) { case ODICT_STRING: case ODICT_INT: case ODICT_DOUBLE: case ODICT_BOOL: case ODICT_NULL: return true; default: return false; } } const char *odict_type_name(enum odict_type type) { switch (type) { case ODICT_OBJECT: return "Object"; case ODICT_ARRAY: return "Array"; case ODICT_STRING: return "String"; case ODICT_INT: return "Integer"; case ODICT_DOUBLE: return "Double"; case ODICT_BOOL: return "Boolean"; case ODICT_NULL: return "Null"; default: return "???"; } } ================================================ FILE: src/pcp/README ================================================ PCP README: ---------- Port Control Protocol (PCP) as of RFC 6887 other PCP implementations: https://github.com/libpcp/pcp ================================================ FILE: src/pcp/msg.c ================================================ /** * @file pcp/msg.c PCP messages * * Copyright (C) 2010 - 2016 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include "pcp.h" static int pcp_map_decode(struct pcp_map *map, struct mbuf *mb) { uint16_t port; int err; if (!map || !mb) return EINVAL; if (mbuf_get_left(mb) < PCP_MAP_SZ) return EBADMSG; (void)mbuf_read_mem(mb, map->nonce, sizeof(map->nonce)); map->proto = mbuf_read_u8(mb); mbuf_advance(mb, 3); map->int_port = ntohs(mbuf_read_u16(mb)); port = ntohs(mbuf_read_u16(mb)); err = pcp_ipaddr_decode(mb, &map->ext_addr); sa_set_port(&map->ext_addr, port); return err; } static int pcp_peer_decode(struct pcp_peer *peer, struct mbuf *mb) { uint16_t port; int err = 0; if (!peer || !mb) return EINVAL; if (mbuf_get_left(mb) < PCP_PEER_SZ) return EBADMSG; /* note: the MAP and PEER opcodes are quite similar */ err = pcp_map_decode(&peer->map, mb); if (err) return err; port = ntohs(mbuf_read_u16(mb)); mbuf_advance(mb, 2); err |= pcp_ipaddr_decode(mb, &peer->remote_addr); sa_set_port(&peer->remote_addr, port); return err; } static void destructor(void *arg) { struct pcp_msg *msg = arg; list_flush(&msg->optionl); } static int pcp_header_encode_request(struct mbuf *mb, enum pcp_opcode opcode, uint32_t req_lifetime, const struct sa *int_addr) { int err = 0; if (!mb || !int_addr) return EINVAL; err |= mbuf_write_u8(mb, PCP_VERSION); err |= mbuf_write_u8(mb, opcode); err |= mbuf_write_u16(mb, 0x0000); err |= mbuf_write_u32(mb, htonl(req_lifetime)); err |= pcp_ipaddr_encode(mb, int_addr); return err; } static int pcp_header_decode(struct pcp_hdr *hdr, struct mbuf *mb) { uint8_t b; if (!hdr || !mb) return EINVAL; if (mbuf_get_left(mb) < PCP_HDR_SZ) return EBADMSG; hdr->version = mbuf_read_u8(mb); if (hdr->version != PCP_VERSION) { (void)re_fprintf(stderr, "pcp: unknown version %u\n", hdr->version); return EPROTO; } b = mbuf_read_u8(mb); hdr->resp = b>>7; hdr->opcode = b & 0x7f; (void)mbuf_read_u8(mb); b = mbuf_read_u8(mb); if (hdr->resp) hdr->result = b; hdr->lifetime = ntohl(mbuf_read_u32(mb)); if (hdr->resp) { hdr->epoch = ntohl(mbuf_read_u32(mb)); mbuf_advance(mb, 12); } else { /* Request */ (void)pcp_ipaddr_decode(mb, &hdr->cli_addr); } return 0; } int pcp_msg_req_vencode(struct mbuf *mb, enum pcp_opcode opcode, uint32_t lifetime, const struct sa *cli_addr, const void *payload, uint32_t optionc, va_list ap) { uint32_t i; int err; if (!mb || !cli_addr) return EINVAL; err = pcp_header_encode_request(mb, opcode, lifetime, cli_addr); if (err) return err; if (payload) { err = pcp_payload_encode(mb, opcode, payload); if (err) return err; } /* encode options */ for (i=0; i PCP_MAX_PACKET || len&3) return EBADMSG; msg = mem_zalloc(sizeof(*msg), destructor); if (!msg) return ENOMEM; pos = mb->pos; err = pcp_header_decode(&msg->hdr, mb); if (err) goto out; switch (msg->hdr.opcode) { case PCP_MAP: err = pcp_map_decode(&msg->pld.map, mb); break; case PCP_PEER: err = pcp_peer_decode(&msg->pld.peer, mb); break; default: break; } if (err) goto out; /* Decode PCP Options */ while (mbuf_get_left(mb) >= 4) { struct pcp_option *opt; err = pcp_option_decode(&opt, mb); if (err) goto out; list_append(&msg->optionl, &opt->le, opt); } out: if (err) { mb->pos = pos; mem_deref(msg); } else *msgp = msg; return err; } struct pcp_option *pcp_msg_option(const struct pcp_msg *msg, enum pcp_option_code code) { struct le *le = msg ? list_head(&msg->optionl) : NULL; while (le) { struct pcp_option *opt = le->data; le = le->next; if (opt->code == code) return opt; } return NULL; } struct pcp_option *pcp_msg_option_apply(const struct pcp_msg *msg, pcp_option_h *h, void *arg) { struct le *le = msg ? list_head(&msg->optionl) : NULL; while (le) { struct pcp_option *opt = le->data; le = le->next; if (h && h(opt, arg)) return opt; } return NULL; } static bool option_print(const struct pcp_option *opt, void *arg) { return 0 != pcp_option_print(arg, opt); } int pcp_msg_printhdr(struct re_printf *pf, const struct pcp_msg *msg) { int err; if (!msg) return 0; err = re_hprintf(pf, "%s %s %usec", msg->hdr.resp ? "Response" : "Request", pcp_opcode_name(msg->hdr.opcode), msg->hdr.lifetime); if (msg->hdr.resp) { err |= re_hprintf(pf, " result=%s, epoch_time=%u sec", pcp_result_name(msg->hdr.result), msg->hdr.epoch); } else { err |= re_hprintf(pf, " client_addr=%j", &msg->hdr.cli_addr); } return err; } static int pcp_map_print(struct re_printf *pf, const struct pcp_map *map) { if (!map) return 0; return re_hprintf(pf, " nonce = %w\n protocol = %s\n" " int_port = %u\n ext_addr = %J\n", map->nonce, sizeof(map->nonce), pcp_proto_name(map->proto), map->int_port, &map->ext_addr); } int pcp_msg_print(struct re_printf *pf, const struct pcp_msg *msg) { int err; if (!msg) return 0; err = pcp_msg_printhdr(pf, msg); err |= re_hprintf(pf, "\n"); switch (msg->hdr.opcode) { case PCP_MAP: err |= pcp_map_print(pf, &msg->pld.map); break; case PCP_PEER: err |= pcp_map_print(pf, &msg->pld.peer.map); err |= re_hprintf(pf, " remote_peer = %J\n", &msg->pld.peer.remote_addr); break; } if (err) return err; if (pcp_msg_option_apply(msg, option_print, pf)) return ENOMEM; return 0; } /** * Get the payload from a PCP message * * @param msg PCP message * * @return either "struct pcp_map" or "struct pcp_peer" */ const void *pcp_msg_payload(const struct pcp_msg *msg) { if (!msg) return NULL; switch (msg->hdr.opcode) { case PCP_MAP: return &msg->pld.map; case PCP_PEER: return &msg->pld.peer; default: return NULL; } } ================================================ FILE: src/pcp/option.c ================================================ /** * @file option.c PCP options * * Copyright (C) 2010 - 2016 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include "pcp.h" static void destructor(void *arg) { struct pcp_option *opt = arg; list_unlink(&opt->le); switch (opt->code) { case PCP_OPTION_DESCRIPTION: mem_deref(opt->u.description); break; default: break; } } int pcp_option_encode(struct mbuf *mb, enum pcp_option_code code, const void *v) { const struct sa *sa = v; const struct pcp_option_filter *filt = v; size_t start, len; int err = 0; if (!mb) return EINVAL; mb->pos += 4; start = mb->pos; switch (code) { case PCP_OPTION_THIRD_PARTY: if (!sa) return EINVAL; err |= pcp_ipaddr_encode(mb, sa); break; case PCP_OPTION_PREFER_FAILURE: /* no payload */ break; case PCP_OPTION_FILTER: if (!filt) return EINVAL; err |= mbuf_write_u8(mb, 0x00); err |= mbuf_write_u8(mb, filt->prefix_length); err |= mbuf_write_u16(mb, htons(sa_port(&filt->remote_peer))); err |= pcp_ipaddr_encode(mb, &filt->remote_peer); break; case PCP_OPTION_DESCRIPTION: if (!v) return EINVAL; err |= mbuf_write_str(mb, v); break; default: (void)re_fprintf(stderr, "pcp: unsupported option %d\n", code); return EINVAL; } /* header */ len = mb->pos - start; mb->pos = start - 4; err |= mbuf_write_u8(mb, code); err |= mbuf_write_u8(mb, 0x00); err |= mbuf_write_u16(mb, htons((uint16_t)len)); mb->pos += len; /* padding */ while ((mb->pos - start) & 0x03) err |= mbuf_write_u8(mb, 0x00); return err; } int pcp_option_decode(struct pcp_option **optp, struct mbuf *mb) { struct pcp_option *opt; size_t start, len; uint16_t port; int err = 0; if (!optp || !mb) return EINVAL; if (mbuf_get_left(mb) < 4) return EBADMSG; opt = mem_zalloc(sizeof(*opt), destructor); if (!opt) return ENOMEM; opt->code = mbuf_read_u8(mb); (void)mbuf_read_u8(mb); len = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < len) goto badmsg; start = mb->pos; switch (opt->code) { case PCP_OPTION_THIRD_PARTY: if (len < 16) goto badmsg; err = pcp_ipaddr_decode(mb, &opt->u.third_party); break; case PCP_OPTION_PREFER_FAILURE: /* no payload */ break; case PCP_OPTION_FILTER: if (len < 20) goto badmsg; (void)mbuf_read_u8(mb); opt->u.filter.prefix_length = mbuf_read_u8(mb); port = ntohs(mbuf_read_u16(mb)); err = pcp_ipaddr_decode(mb, &opt->u.filter.remote_peer); sa_set_port(&opt->u.filter.remote_peer, port); break; case PCP_OPTION_DESCRIPTION: err = mbuf_strdup(mb, &opt->u.description, len); break; default: mb->pos += len; (void)re_printf("pcp: ignore option code %d (len=%zu)\n", opt->code, len); break; } if (err) goto error; /* padding */ while (((mb->pos - start) & 0x03) && mbuf_get_left(mb)) ++mb->pos; *optp = opt; return 0; badmsg: err = EBADMSG; error: mem_deref(opt); return err; } static const char *pcp_option_name(enum pcp_option_code code) { switch (code) { case PCP_OPTION_THIRD_PARTY: return "THIRD_PARTY"; case PCP_OPTION_PREFER_FAILURE: return "PREFER_FAILURE"; case PCP_OPTION_FILTER: return "FILTER"; case PCP_OPTION_DESCRIPTION: return "DESCRIPTION"; default: return "?"; } } int pcp_option_print(struct re_printf *pf, const struct pcp_option *opt) { int err; if (!opt) return 0; err = re_hprintf(pf, " %-25s", pcp_option_name(opt->code)); switch (opt->code) { case PCP_OPTION_THIRD_PARTY: err |= re_hprintf(pf, "address=%j", &opt->u.third_party); break; case PCP_OPTION_PREFER_FAILURE: break; case PCP_OPTION_FILTER: err |= re_hprintf(pf, "prefix_length=%u, remote_peer=%J", opt->u.filter.prefix_length, &opt->u.filter.remote_peer); break; case PCP_OPTION_DESCRIPTION: err |= re_hprintf(pf, "'%s'", opt->u.description); break; default: err |= re_hprintf(pf, "???"); break; } err |= re_hprintf(pf, "\n"); return err; } ================================================ FILE: src/pcp/payload.c ================================================ /** * @file payload.c PCP payload encoding and decoding * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include "pcp.h" static int pcp_write_port(struct mbuf *mb, const struct sa *sa) { uint16_t port_be; if (!mb || !sa) return EINVAL; switch (sa->u.sa.sa_family) { case AF_INET: port_be = sa->u.in.sin_port; break; case AF_INET6: port_be = sa->u.in6.sin6_port; break; default: return EAFNOSUPPORT; } return mbuf_write_u16(mb, port_be); } static int pcp_map_encode(struct mbuf *mb, const struct pcp_map *map) { int err = 0; if (!mb || !map) return EINVAL; err |= mbuf_write_mem(mb, map->nonce, sizeof(map->nonce)); err |= mbuf_write_u8(mb, map->proto); err |= mbuf_fill(mb, 0x00, 3); err |= mbuf_write_u16(mb, htons(map->int_port)); err |= pcp_write_port(mb, &map->ext_addr); err |= pcp_ipaddr_encode(mb, &map->ext_addr); return err; } static int pcp_peer_encode(struct mbuf *mb, const struct pcp_peer *peer) { int err; if (!mb || !peer) return EINVAL; /* Protocol MUST NOT be zero. * Internal port MUST NOT be zero. */ if (!peer->map.proto || !peer->map.int_port) return EPROTO; /* note: the MAP and PEER opcodes are quite similar */ err = pcp_map_encode(mb, &peer->map); if (err) return err; err = pcp_write_port(mb, &peer->remote_addr); err |= mbuf_write_u16(mb, 0x0000); err |= pcp_ipaddr_encode(mb, &peer->remote_addr); return err; } int pcp_payload_encode(struct mbuf *mb, enum pcp_opcode opcode, const union pcp_payload *pld) { int err; if (!mb || !pld) return EINVAL; switch (opcode) { case PCP_MAP: err = pcp_map_encode(mb, &pld->map); break; case PCP_PEER: err = pcp_peer_encode(mb, &pld->peer); break; default: re_fprintf(stderr, "pcp: dont know how to encode payload" " for opcode %d\n", opcode); err = EPROTO; break; } return err; } ================================================ FILE: src/pcp/pcp.c ================================================ /** * @file pcp/pcp.c PCP protocol details * * Copyright (C) 2010 - 2016 Alfred E. Heggestad */ #include #include #include #include #include #include #include "pcp.h" static const uint8_t pattern[12] = {0,0,0,0,0,0,0,0,0,0,0xff,0xff}; int pcp_ipaddr_encode(struct mbuf *mb, const struct sa *sa) { int err = 0; if (!mb || !sa) return EINVAL; switch (sa_af(sa)) { case AF_INET: err |= mbuf_write_mem(mb, pattern, sizeof(pattern)); err |= mbuf_write_mem(mb, (void *)&sa->u.in.sin_addr.s_addr, 4); break; case AF_INET6: err |= mbuf_write_mem(mb, sa->u.in6.sin6_addr.s6_addr, 16); break; default: err = EAFNOSUPPORT; break; } return err; } int pcp_ipaddr_decode(struct mbuf *mb, struct sa *sa) { uint8_t *p; if (!mb || !sa) return EINVAL; if (mbuf_get_left(mb) < 16) return EBADMSG; p = mbuf_buf(mb); if (0 == memcmp(p, pattern, sizeof(pattern))) { sa_init(sa, AF_INET); memcpy(&sa->u.in.sin_addr, p + 12, 4); } else { sa_init(sa, AF_INET6); memcpy(sa->u.in6.sin6_addr.s6_addr, p, 16); } mb->pos += 16; return 0; } const char *pcp_result_name(enum pcp_result result) { switch (result) { case PCP_SUCCESS: return "SUCCESS"; case PCP_UNSUPP_VERSION: return "UNSUPP_VERSION"; case PCP_NOT_AUTHORIZED: return "NOT_AUTHORIZED"; case PCP_MALFORMED_REQUEST: return "MALFORMED_REQUEST"; case PCP_UNSUPP_OPCODE: return "UNSUPP_OPCODE"; case PCP_UNSUPP_OPTION: return "UNSUPP_OPTION"; case PCP_MALFORMED_OPTION: return "MALFORMED_OPTION"; case PCP_NETWORK_FAILURE: return "NETWORK_FAILURE"; case PCP_NO_RESOURCES: return "NO_RESOURCES"; case PCP_UNSUPP_PROTOCOL: return "UNSUPP_PROTOCOL"; case PCP_USER_EX_QUOTA: return "USER_EX_QUOTA"; case PCP_CANNOT_PROVIDE_EXTERNAL: return "CANNOT_PROVIDE_EXTERNAL"; case PCP_ADDRESS_MISMATCH: return "ADDRESS_MISMATCH"; case PCP_EXCESSIVE_REMOTE_PEERS: return "EXCESSIVE_REMOTE_PEERS"; default: return "?"; } } const char *pcp_opcode_name(enum pcp_opcode opcode) { switch (opcode) { case PCP_ANNOUNCE: return "ANNOUNCE"; case PCP_MAP: return "MAP"; case PCP_PEER: return "PEER"; default: return "?"; } } const char *pcp_proto_name(int proto) { switch (proto) { case IPPROTO_UDP: return "UDP"; case IPPROTO_TCP: return "TCP"; default: return "?"; } } ================================================ FILE: src/pcp/pcp.h ================================================ /** * @file pcp/pcp.h PCP protocol -- Internal interface * * Copyright (C) 2010 - 2016 Alfred E. Heggestad */ int pcp_payload_encode(struct mbuf *mb, enum pcp_opcode opcode, const union pcp_payload *pld); ================================================ FILE: src/pcp/reply.c ================================================ /** * @file pcp/reply.c PCP reply * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include "pcp.h" static int pcp_header_encode_response(struct mbuf *mb, enum pcp_opcode opcode, enum pcp_result result, uint32_t lifetime, uint32_t epoch_time) { int err = 0; if (!mb) return EINVAL; err |= mbuf_write_u8(mb, PCP_VERSION); err |= mbuf_write_u8(mb, 1<<7 | opcode); err |= mbuf_write_u8(mb, 0x00); err |= mbuf_write_u8(mb, result); err |= mbuf_write_u32(mb, htonl(lifetime)); err |= mbuf_write_u32(mb, htonl(epoch_time)); err |= mbuf_fill(mb, 0x00, 12); return err; } /** * Send a PCP response message * * @param us UDP Socket * @param dst Destination network address * @param req Buffer containing original PCP request (optional) * @param opcode PCP opcode * @param result PCP result for the response * @param lifetime Lifetime in [seconds] * @param epoch_time Server Epoch-time * @param payload PCP payload, e.g. struct pcp_map (optional) * * @return 0 if success, otherwise errorcode */ int pcp_reply(struct udp_sock *us, const struct sa *dst, struct mbuf *req, enum pcp_opcode opcode, enum pcp_result result, uint32_t lifetime, uint32_t epoch_time, const void *payload) { struct mbuf *mb; size_t start; int err; if (!us || !dst) return EINVAL; if (req) { /* the complete Request must be included in the Response */ mb = mem_ref(req); } else { mb = mbuf_alloc(128); if (!mb) return ENOMEM; } start = mb->pos; /* encode the response packet */ err = pcp_header_encode_response(mb, opcode, result, lifetime, epoch_time); if (err) goto out; if (payload) { err = pcp_payload_encode(mb, opcode, payload); if (err) goto out; } mb->pos = start; err = udp_send(us, dst, mb); out: mem_deref(mb); return err; } ================================================ FILE: src/pcp/request.c ================================================ /** * @file pcp/request.c PCP request * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include "pcp.h" /* * Defines a PCP client request * * the application must keep a reference to this object and the * object must be deleted by the application. the response handler * might be called multiple times. */ struct pcp_request { struct pcp_conf conf; struct sa srv; struct udp_sock *us; struct mbuf *mb; struct tmr tmr; struct tmr tmr_dur; struct tmr tmr_refresh; enum pcp_opcode opcode; union pcp_payload payload; uint32_t lifetime; bool granted; unsigned txc; double RT; pcp_resp_h *resph; void *arg; }; /* * RT: Retransmission timeout * IRT: Initial retransmission time, SHOULD be 3 seconds * MRC: Maximum retransmission count, SHOULD be 0 (no maximum) * MRT: Maximum retransmission time, SHOULD be 1024 seconds * MRD: Maximum retransmission duration, SHOULD be 0 (no maximum) * RAND: Randomization factor */ static const struct pcp_conf default_conf = { 3, 0, 1024, 0 }; static int start_sending(struct pcp_request *req); /* random number between -0.1 and +0.1 */ static inline double RAND(void) { return (1.0 * rand_u16() / 32768 - 1.0) / 10.0; } static double RT_init(const struct pcp_conf *conf) { return (1.0 + RAND()) * conf->irt; } static double RT_next(const struct pcp_conf *conf, double RTprev) { return (1.0 + RAND()) * min (2 * RTprev, conf->mrt); } static void destructor(void *arg) { struct pcp_request *req = arg; /* Destroy the mapping if it was granted */ if (req->granted && req->lifetime && req->mb) { /* set the lifetime to zero */ req->mb->pos = 4; mbuf_write_u32(req->mb, 0); req->mb->pos = 0; (void)udp_send(req->us, &req->srv, req->mb); } tmr_cancel(&req->tmr); tmr_cancel(&req->tmr_dur); tmr_cancel(&req->tmr_refresh); mem_deref(req->us); mem_deref(req->mb); } static void completed(struct pcp_request *req, int err, struct pcp_msg *msg) { pcp_resp_h *resph = req->resph; void *arg = req->arg; tmr_cancel(&req->tmr); tmr_cancel(&req->tmr_dur); /* if the request failed, we only called the response handler once and never again */ if (err || !msg || msg->hdr.result != PCP_SUCCESS ) { req->resph = NULL; } if (resph) resph(err, msg, arg); } static void refresh_timeout(void *arg) { struct pcp_request *req = arg; /* todo: update request with new EXT-ADDR from server */ (void)start_sending(req); } static void timeout(void *arg) { struct pcp_request *req = arg; int err; req->txc++; if (req->conf.mrc > 0 && req->txc > req->conf.mrc) { completed(req, ETIMEDOUT, NULL); return; } req->mb->pos = 0; err = udp_send(req->us, &req->srv, req->mb); if (err) { completed(req, err, NULL); return; } req->RT = RT_next(&req->conf, req->RT); tmr_start(&req->tmr, (uint64_t)req->RT * 1000, timeout, req); } static void timeout_duration(void *arg) { struct pcp_request *req = arg; completed(req, ETIMEDOUT, NULL); } static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct pcp_request *req = arg; struct pcp_msg *msg; int err; if (!sa_cmp(src, &req->srv, SA_ALL)) return; err = pcp_msg_decode(&msg, mb); if (err) return; if (!msg->hdr.resp) { (void)re_fprintf(stderr, "pcp: ignoring PCP request\n"); goto out; } if (msg->hdr.opcode != req->opcode) goto out; /* compare opcode-specific data */ switch (msg->hdr.opcode) { case PCP_MAP: case PCP_PEER: if (0 != memcmp(msg->pld.map.nonce, req->payload.map.nonce, PCP_NONCE_SZ)) { (void)re_fprintf(stderr, "ignoring unknown nonce\n"); goto out; } req->payload.map.ext_addr = msg->pld.map.ext_addr; break; default: break; } req->lifetime = msg->hdr.lifetime; req->granted = (msg->hdr.result == PCP_SUCCESS); /* todo: * * Once a PCP client has successfully received a response from a PCP * server on that interface, it resets RT to a value randomly selected * in the range 1/2 to 5/8 of the mapping lifetime, as described in * Section 11.2.1, "Renewing a Mapping", and sends subsequent PCP * requests for that mapping to that same server. */ if (req->granted && req->lifetime) { uint32_t v = req->lifetime * 3/4; tmr_start(&req->tmr_refresh, v * 1000, refresh_timeout, req); } completed(req, 0, msg); out: mem_deref(msg); } static int start_sending(struct pcp_request *req) { int err; req->txc = 1; req->mb->pos = 0; err = udp_send(req->us, &req->srv, req->mb); if (err) return err; req->RT = RT_init(&req->conf); tmr_start(&req->tmr, (uint64_t)req->RT * 1000, timeout, req); if (req->conf.mrd) { tmr_start(&req->tmr_dur, req->conf.mrd * 1000, timeout_duration, req); } return 0; } static int pcp_vrequest(struct pcp_request **reqp, const struct pcp_conf *conf, const struct sa *srv, enum pcp_opcode opcode, uint32_t lifetime, const void *payload, pcp_resp_h *resph, void *arg, uint32_t optionc, va_list ap) { const union pcp_payload *up = payload; struct pcp_request *req; struct sa laddr; int err; if (!reqp || !srv) return EINVAL; sa_init(&laddr, sa_af(srv)); req = mem_zalloc(sizeof(*req), destructor); if (!req) return ENOMEM; req->conf = conf ? *conf : default_conf; req->opcode = opcode; req->srv = *srv; req->resph = resph; req->arg = arg; req->lifetime = lifetime; if (up) req->payload = *up; err = udp_listen(&req->us, &laddr, udp_recv, req); if (err) goto out; /* * see RFC 6887 section 16.4 */ err = udp_connect(req->us, srv); if (err) goto out; err = udp_local_get(req->us, &laddr); if (err) goto out; req->mb = mbuf_alloc(128); if (!req->mb) { err = ENOMEM; goto out; } err = pcp_msg_req_vencode(req->mb, opcode, lifetime, &laddr, up, optionc, ap); if (err) goto out; err = start_sending(req); out: if (err) mem_deref(req); else *reqp = req; return err; } int pcp_request(struct pcp_request **reqp, const struct pcp_conf *conf, const struct sa *srv, enum pcp_opcode opcode, uint32_t lifetime, const void *payload, pcp_resp_h *resph, void *arg, uint32_t optionc, ...) { va_list ap; int err; va_start(ap, optionc); err = pcp_vrequest(reqp, conf, srv, opcode, lifetime, payload, resph, arg, optionc, ap); va_end(ap); return err; } void pcp_force_refresh(struct pcp_request *req) { if (!req) return; tmr_cancel(&req->tmr); tmr_cancel(&req->tmr_dur); tmr_start(&req->tmr_refresh, rand_u16() % 2000, refresh_timeout, req); } ================================================ FILE: src/rtmp/README.md ================================================ RTMP module ----------- This module implements Real Time Messaging Protocol (RTMP) [1]. Functional overview: ------------------- ``` RTMP Specification v1.0 .......... YES RTMP with TCP transport .......... YES RTMPS (RTMP over TLS) ............ NO RTMPE (RTMP over Adobe Encryption) NO RTMPT (RTMP over HTTP) ........... NO RTMFP (RTMP over UDP) ............ NO Transport: Client ........................... YES Server ........................... YES IPv4 ............................. YES IPv6 ............................. YES DNS Resolving A/AAAA ............. YES RTMP Components: RTMP Handshake ................... YES RTMP Header encoding and decoding. YES RTMP Chunking .................... YES RTMP Dechunking .................. YES AMF0 (Action Message Format) ..... YES AMF3 (Action Message Format) ..... NO Send and receive audio/video ..... YES Regular and extended timestamp ... YES Multiple streams ................. YES ``` TODO: ---- - [x] improve AMF encoding API - [x] implement AMF transaction matching - [x] add support for Data Message - [x] add support for AMF Strict Array (type 10) - [ ] add support for TLS encryption - [x] add support for extended timestamp Protocol stack: -------------- .-------. .-------. .-------. | AMF | | Audio | | Video | '-------' '-------' '-------' | | | +----------+----------' | .-------. | RTMP | '-------' | | .-------. | TCP | '-------' Message Sequence: ---------------- ``` Client Server |----------------- TCP Connect -------------->| | | | | | | |<-------------- 3-way Handshake ------------>| | | | | | | |----------- Command Message(connect) ------->| chunkid=3, streamid=0, tid=1 | | |<------- Window Acknowledgement Size --------| chunkid=2, streamid=0 | | |<----------- Set Peer Bandwidth -------------| chunkid=2, streamid=0 | | |-------- Window Acknowledgement Size ------->| | | |<------ User Control Message(StreamBegin) ---| chunkid=2, streamid=0 | | |<------------ Command Message ---------------| chunkid=3, streamid=0, tid=1 | (_result- connect response) | ``` Interop: ------- - Wowza Streaming Engine 4.7.1 - Youtube service - FFmpeg's RTMP module References: ---------- [1] http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf [2] https://wwwimages2.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf [3] https://en.wikipedia.org/wiki/Action_Message_Format [4] https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf0-file-format-specification.pdf ================================================ FILE: src/rtmp/amf.c ================================================ /** * @file rtmp/amf.c Real Time Messaging Protocol (RTMP) -- AMF Commands * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" int rtmp_command_header_encode(struct mbuf *mb, const char *name, uint64_t tid) { int err; if (!mb || !name) return EINVAL; err = rtmp_amf_encode_string(mb, name); err |= rtmp_amf_encode_number(mb, (double)tid); return err; } int rtmp_amf_command(const struct rtmp_conn *conn, uint32_t stream_id, const char *command, unsigned body_propc, ...) { struct mbuf *mb; va_list ap; int err; if (!conn || !command) return EINVAL; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = rtmp_amf_encode_string(mb, command); if (err) goto out; if (body_propc) { va_start(ap, body_propc); err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT, body_propc, &ap); va_end(ap); if (err) goto out; } err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN, RTMP_TYPE_AMF0, stream_id, mb->buf, mb->end); if (err) goto out; out: mem_deref(mb); return err; } int rtmp_amf_reply(struct rtmp_conn *conn, uint32_t stream_id, bool success, const struct odict *req, unsigned body_propc, ...) { struct mbuf *mb; va_list ap; uint64_t tid; int err; if (!conn || !req) return EINVAL; if (!odict_get_number(req, &tid, "1")) return EPROTO; if (tid == 0) return EPROTO; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = rtmp_command_header_encode(mb, success ? "_result" : "_error", tid); if (err) goto out; if (body_propc) { va_start(ap, body_propc); err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT, body_propc, &ap); va_end(ap); if (err) goto out; } err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN, RTMP_TYPE_AMF0, stream_id, mb->buf, mb->end); if (err) goto out; out: mem_deref(mb); return err; } int rtmp_amf_data(const struct rtmp_conn *conn, uint32_t stream_id, const char *command, unsigned body_propc, ...) { struct mbuf *mb; va_list ap; int err; if (!conn || !command) return EINVAL; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = rtmp_amf_encode_string(mb, command); if (err) goto out; if (body_propc) { va_start(ap, body_propc); err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT, body_propc, &ap); va_end(ap); if (err) goto out; } err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN, RTMP_TYPE_DATA, stream_id, mb->buf, mb->end); if (err) goto out; out: mem_deref(mb); return err; } ================================================ FILE: src/rtmp/amf_dec.c ================================================ /** * @file rtmp/amf_dec.c Real Time Messaging Protocol (RTMP) -- AMF Decoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" enum { AMF_HASH_SIZE = 32 }; static int amf_decode_value(struct odict *dict, const char *key, struct mbuf *mb); static int amf_decode_object(struct odict *dict, struct mbuf *mb) { char *key = NULL; uint16_t len; int err = 0; while (mbuf_get_left(mb) > 0) { if (mbuf_get_left(mb) < 2) return ENODATA; len = ntohs(mbuf_read_u16(mb)); if (len == 0) { uint8_t val; if (mbuf_get_left(mb) < 1) return ENODATA; val = mbuf_read_u8(mb); if (val == RTMP_AMF_TYPE_OBJECT_END) return 0; else return EBADMSG; } if (mbuf_get_left(mb) < len) return ENODATA; err = mbuf_strdup(mb, &key, len); if (err) return err; err = amf_decode_value(dict, key, mb); key = mem_deref(key); if (err) return err; } return 0; } static int amf_decode_value(struct odict *dict, const char *key, struct mbuf *mb) { union { uint64_t i; double f; } num; struct odict *object = NULL; char *str = NULL; uint32_t i, array_len; uint8_t type; uint16_t len; bool boolean; int err = 0; if (mbuf_get_left(mb) < 1) return ENODATA; type = mbuf_read_u8(mb); switch (type) { case RTMP_AMF_TYPE_NUMBER: if (mbuf_get_left(mb) < 8) return ENODATA; num.i = sys_ntohll(mbuf_read_u64(mb)); err = odict_entry_add(dict, key, ODICT_DOUBLE, num.f); break; case RTMP_AMF_TYPE_BOOLEAN: if (mbuf_get_left(mb) < 1) return ENODATA; boolean = !!mbuf_read_u8(mb); err = odict_entry_add(dict, key, ODICT_BOOL, boolean); break; case RTMP_AMF_TYPE_STRING: if (mbuf_get_left(mb) < 2) return ENODATA; len = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < len) return ENODATA; err = mbuf_strdup(mb, &str, len); if (err) return err; err = odict_entry_add(dict, key, ODICT_STRING, str); mem_deref(str); break; case RTMP_AMF_TYPE_NULL: err = odict_entry_add(dict, key, ODICT_NULL); break; case RTMP_AMF_TYPE_ECMA_ARRAY: if (mbuf_get_left(mb) < 4) return ENODATA; array_len = ntohl(mbuf_read_u32(mb)); (void)array_len; /* ignore array length */ /* fallthrough */ case RTMP_AMF_TYPE_OBJECT: err = odict_alloc(&object, 32); if (err) return err; err = amf_decode_object(object, mb); if (err) { mem_deref(object); return err; } err = odict_entry_add(dict, key, ODICT_OBJECT, object); mem_deref(object); break; case RTMP_AMF_TYPE_STRICT_ARRAY: if (mbuf_get_left(mb) < 4) return ENODATA; array_len = ntohl(mbuf_read_u32(mb)); if (!array_len || array_len > 65536) return EPROTO; err = odict_alloc(&object, 32); if (err) return err; for (i=0; i 0) { char key[16]; re_snprintf(key, sizeof(key), "%u", ix++); /* note: key is the numerical index */ err = amf_decode_value(msg, key, mb); if (err) goto out; } out: if (err) mem_deref(msg); else *msgp = msg; return err; } ================================================ FILE: src/rtmp/amf_enc.c ================================================ /** * @file rtmp/amf_enc.c Real Time Messaging Protocol (RTMP) -- AMF Encoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" static int rtmp_amf_encode_key(struct mbuf *mb, const char *key) { size_t len; int err; len = str_len(key); if (len > 65535) return EOVERFLOW; err = mbuf_write_u16(mb, htons((uint16_t)len)); err |= mbuf_write_str(mb, key); return err; } static int rtmp_amf_encode_object_start(struct mbuf *mb) { return mbuf_write_u8(mb, RTMP_AMF_TYPE_OBJECT); } static int rtmp_amf_encode_array_start(struct mbuf *mb, uint8_t type, uint32_t length) { int err; err = mbuf_write_u8(mb, type); err |= mbuf_write_u32(mb, htonl(length)); return err; } static int rtmp_amf_encode_object_end(struct mbuf *mb) { int err; err = mbuf_write_u16(mb, 0); err |= mbuf_write_u8(mb, RTMP_AMF_TYPE_OBJECT_END); return err; } static bool container_has_key(enum rtmp_amf_type type) { switch (type) { case RTMP_AMF_TYPE_OBJECT: return true; case RTMP_AMF_TYPE_ECMA_ARRAY: return true; case RTMP_AMF_TYPE_STRICT_ARRAY: return false; default: return false; } } int rtmp_amf_encode_number(struct mbuf *mb, double val) { const union { uint64_t i; double f; } num = { .f = val }; int err; if (!mb) return EINVAL; err = mbuf_write_u8(mb, RTMP_AMF_TYPE_NUMBER); err |= mbuf_write_u64(mb, sys_htonll(num.i)); return err; } int rtmp_amf_encode_boolean(struct mbuf *mb, bool boolean) { int err; if (!mb) return EINVAL; err = mbuf_write_u8(mb, RTMP_AMF_TYPE_BOOLEAN); err |= mbuf_write_u8(mb, !!boolean); return err; } int rtmp_amf_encode_string(struct mbuf *mb, const char *str) { size_t len; int err; if (!mb || !str) return EINVAL; len = str_len(str); if (len > 65535) return EOVERFLOW; err = mbuf_write_u8(mb, RTMP_AMF_TYPE_STRING); err |= mbuf_write_u16(mb, htons((uint16_t)len)); err |= mbuf_write_str(mb, str); return err; } int rtmp_amf_encode_null(struct mbuf *mb) { if (!mb) return EINVAL; return mbuf_write_u8(mb, RTMP_AMF_TYPE_NULL); } /* * NUMBER double * BOOLEAN bool * STRING const char * * OBJECT const char *key sub-count * NULL NULL * ARRAY const char *key sub-count */ int rtmp_amf_vencode_object(struct mbuf *mb, enum rtmp_amf_type container, unsigned propc, va_list *ap) { bool encode_key; unsigned i; int err = 0; if (!mb || !propc || !ap) return EINVAL; encode_key = container_has_key(container); switch (container) { case RTMP_AMF_TYPE_OBJECT: err = rtmp_amf_encode_object_start(mb); break; case RTMP_AMF_TYPE_ECMA_ARRAY: case RTMP_AMF_TYPE_STRICT_ARRAY: err = rtmp_amf_encode_array_start(mb, container, propc); break; case RTMP_AMF_TYPE_ROOT: break; default: return ENOTSUP; } if (err) return err; for (i=0; i #include #include #include #include #include #include #include #include #include #include "rtmp.h" /* * Stateless RTMP chunker */ int rtmp_chunker(unsigned format, uint32_t chunk_id, uint32_t timestamp, uint32_t timestamp_delta, uint8_t msg_type_id, uint32_t msg_stream_id, const uint8_t *payload, size_t payload_len, size_t max_chunk_sz, struct tcp_conn *tc) { const uint8_t *pend = payload + payload_len; struct rtmp_header hdr; struct mbuf *mb; size_t chunk_sz; int err; if (!payload || !payload_len || !max_chunk_sz || !tc) return EINVAL; mb = mbuf_alloc(payload_len + 256); if (!mb) return ENOMEM; memset(&hdr, 0, sizeof(hdr)); hdr.format = format; hdr.chunk_id = chunk_id; hdr.timestamp = timestamp; hdr.timestamp_delta = timestamp_delta; hdr.length = (uint32_t)payload_len; hdr.type_id = msg_type_id; hdr.stream_id = msg_stream_id; chunk_sz = min(payload_len, max_chunk_sz); err = rtmp_header_encode(mb, &hdr); err |= mbuf_write_mem(mb, payload, chunk_sz); if (err) goto out; payload += chunk_sz; hdr.format = 3; while (payload < pend) { const size_t len = pend - payload; chunk_sz = min(len, max_chunk_sz); err = rtmp_header_encode(mb, &hdr); err |= mbuf_write_mem(mb, payload, chunk_sz); if (err) goto out; payload += chunk_sz; } mb->pos = 0; err = tcp_send(tc, mb); if (err) goto out; out: mem_deref(mb); return err; } ================================================ FILE: src/rtmp/conn.c ================================================ /** * @file rtmp/conn.c Real Time Messaging Protocol (RTMP) -- NetConnection * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" enum { WINDOW_ACK_SIZE = 2500000 }; static int req_connect(struct rtmp_conn *conn); static void conn_destructor(void *data) { struct rtmp_conn *conn = data; list_flush(&conn->ctransl); list_flush(&conn->streaml); mem_deref(conn->dnsq6); mem_deref(conn->dnsq4); mem_deref(conn->dnsc); mem_deref(conn->sc); mem_deref(conn->tc); mem_deref(conn->mb); mem_deref(conn->dechunk); mem_deref(conn->uri); mem_deref(conn->app); mem_deref(conn->host); mem_deref(conn->stream); } static int handle_amf_command(struct rtmp_conn *conn, uint32_t stream_id, struct mbuf *mb) { struct odict *msg = NULL; const char *name; int err; err = rtmp_amf_decode(&msg, mb); if (err) return err; name = odict_string(msg, "0"); if (conn->is_client && (0 == str_casecmp(name, "_result") || 0 == str_casecmp(name, "_error"))) { /* forward response to transaction layer */ rtmp_ctrans_response(&conn->ctransl, msg); } else { struct rtmp_stream *strm; if (stream_id == 0) { if (conn->cmdh) conn->cmdh(msg, conn->arg); } else { strm = rtmp_stream_find(conn, stream_id); if (strm) { if (strm->cmdh) strm->cmdh(msg, strm->arg); } } } mem_deref(msg); return 0; } static int handle_user_control_msg(struct rtmp_conn *conn, struct mbuf *mb) { struct rtmp_stream *strm; enum rtmp_event_type event; uint32_t value; int err; if (mbuf_get_left(mb) < 6) return EBADMSG; event = ntohs(mbuf_read_u16(mb)); value = ntohl(mbuf_read_u32(mb)); switch (event) { case RTMP_EVENT_STREAM_BEGIN: case RTMP_EVENT_STREAM_EOF: case RTMP_EVENT_STREAM_DRY: case RTMP_EVENT_STREAM_IS_RECORDED: case RTMP_EVENT_SET_BUFFER_LENGTH: if (value != RTMP_CONTROL_STREAM_ID) { strm = rtmp_stream_find(conn, value); if (strm && strm->ctrlh) strm->ctrlh(event, mb, strm->arg); } break; case RTMP_EVENT_PING_REQUEST: err = rtmp_control(conn, RTMP_TYPE_USER_CONTROL_MSG, RTMP_EVENT_PING_RESPONSE, value); if (err) return err; break; default: break; } return 0; } static int handle_data_message(struct rtmp_conn *conn, uint32_t stream_id, struct mbuf *mb) { struct rtmp_stream *strm; struct odict *msg; int err; err = rtmp_amf_decode(&msg, mb); if (err) return err; strm = rtmp_stream_find(conn, stream_id); if (strm && strm->datah) strm->datah(msg, strm->arg); mem_deref(msg); return 0; } static int rtmp_dechunk_handler(const struct rtmp_header *hdr, struct mbuf *mb, void *arg) { struct rtmp_conn *conn = arg; struct rtmp_stream *strm; uint32_t val; uint32_t was; uint8_t limit; int err = 0; switch (hdr->type_id) { case RTMP_TYPE_SET_CHUNK_SIZE: if (mbuf_get_left(mb) < 4) return EBADMSG; val = ntohl(mbuf_read_u32(mb)); val = val & 0x7fffffff; rtmp_dechunker_set_chunksize(conn->dechunk, val); break; case RTMP_TYPE_ACKNOWLEDGEMENT: if (mbuf_get_left(mb) < 4) return EBADMSG; val = ntohl(mbuf_read_u32(mb)); (void)val; break; case RTMP_TYPE_AMF0: err = handle_amf_command(conn, hdr->stream_id, mb); break; case RTMP_TYPE_WINDOW_ACK_SIZE: if (mbuf_get_left(mb) < 4) return EBADMSG; was = ntohl(mbuf_read_u32(mb)); if (was != 0) conn->window_ack_size = was; break; case RTMP_TYPE_SET_PEER_BANDWIDTH: if (mbuf_get_left(mb) < 5) return EBADMSG; was = ntohl(mbuf_read_u32(mb)); limit = mbuf_read_u8(mb); (void)limit; if (was != 0) conn->window_ack_size = was; err = rtmp_control(conn, RTMP_TYPE_WINDOW_ACK_SIZE, (uint32_t)WINDOW_ACK_SIZE); break; case RTMP_TYPE_USER_CONTROL_MSG: err = handle_user_control_msg(conn, mb); break; /* XXX: common code for audio+video */ case RTMP_TYPE_AUDIO: strm = rtmp_stream_find(conn, hdr->stream_id); if (strm) { if (strm->auh) { strm->auh(hdr->timestamp, mb->buf, mb->end, strm->arg); } } break; case RTMP_TYPE_VIDEO: strm = rtmp_stream_find(conn, hdr->stream_id); if (strm) { if (strm->vidh) { strm->vidh(hdr->timestamp, mb->buf, mb->end, strm->arg); } } break; case RTMP_TYPE_DATA: err = handle_data_message(conn, hdr->stream_id, mb); break; default: break; } return err; } static struct rtmp_conn *rtmp_conn_alloc(bool is_client, rtmp_estab_h *estabh, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg) { struct rtmp_conn *conn; int err; conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return NULL; conn->is_client = is_client; conn->state = RTMP_STATE_UNINITIALIZED; conn->send_chunk_size = RTMP_DEFAULT_CHUNKSIZE; conn->window_ack_size = WINDOW_ACK_SIZE; err = rtmp_dechunker_alloc(&conn->dechunk, RTMP_DEFAULT_CHUNKSIZE, rtmp_dechunk_handler, conn); if (err) goto out; /* must be above 2 */ conn->chunk_id_counter = RTMP_CHUNK_ID_CONN + 1; conn->estabh = estabh; conn->cmdh = cmdh; conn->closeh = closeh; conn->arg = arg; out: if (err) return mem_deref(conn); return conn; } static inline void set_state(struct rtmp_conn *conn, enum rtmp_handshake_state state) { conn->state = state; } static int send_packet(struct rtmp_conn *conn, const uint8_t *pkt, size_t len) { struct mbuf *mb; int err; if (!conn || !pkt || !len) return EINVAL; mb = mbuf_alloc(len); if (!mb) return ENOMEM; (void)mbuf_write_mem(mb, pkt, len); mb->pos = 0; err = tcp_send(conn->tc, mb); if (err) goto out; out: mem_deref(mb); return err; } static int handshake_start(struct rtmp_conn *conn) { uint8_t sig[1+RTMP_HANDSHAKE_SIZE]; int err; sig[0] = RTMP_PROTOCOL_VERSION; sig[1] = 0; sig[2] = 0; sig[3] = 0; sig[4] = 0; sig[5] = VER_MAJOR; sig[6] = VER_MINOR; sig[7] = VER_PATCH; sig[8] = 0; rand_bytes(sig + 9, sizeof(sig) - 9); err = send_packet(conn, sig, sizeof(sig)); if (err) return err; set_state(conn, RTMP_STATE_VERSION_SENT); return 0; } static void conn_close(struct rtmp_conn *conn, int err) { rtmp_close_h *closeh; conn->sc = mem_deref(conn->sc); conn->tc = mem_deref(conn->tc); conn->dnsq6 = mem_deref(conn->dnsq6); conn->dnsq4 = mem_deref(conn->dnsq4); closeh = conn->closeh; if (closeh) { conn->closeh = NULL; closeh(err, conn->arg); } } static void tcp_estab_handler(void *arg) { struct rtmp_conn *conn = arg; int err = 0; if (conn->is_client) { err = handshake_start(conn); } if (err) conn_close(conn, err); } /* Send AMF0 Command or Data */ int rtmp_send_amf_command(const struct rtmp_conn *conn, unsigned format, uint32_t chunk_id, uint8_t type_id, uint32_t msg_stream_id, const uint8_t *cmd, size_t len) { if (!conn || !cmd || !len) return EINVAL; return rtmp_chunker(format, chunk_id, 0, 0, type_id, msg_stream_id, cmd, len, conn->send_chunk_size, conn->tc); } static void connect_resp_handler(bool success, const struct odict *msg, void *arg) { struct rtmp_conn *conn = arg; rtmp_estab_h *estabh; (void)msg; if (!success) { conn_close(conn, EPROTO); return; } conn->connected = true; estabh = conn->estabh; if (estabh) { conn->estabh = NULL; estabh(conn->arg); } } static int send_connect(struct rtmp_conn *conn) { const int ac = 0x0400; /* AAC */ const int vc = 0x0080; /* H264 */ return rtmp_amf_request(conn, RTMP_CONTROL_STREAM_ID, "connect", connect_resp_handler, conn, 1, RTMP_AMF_TYPE_OBJECT, 8, RTMP_AMF_TYPE_STRING, "app", conn->app, RTMP_AMF_TYPE_STRING, "flashVer", "FMLE/3.0", RTMP_AMF_TYPE_STRING, "tcUrl", conn->uri, RTMP_AMF_TYPE_BOOLEAN, "fpad", false, RTMP_AMF_TYPE_NUMBER, "capabilities", 15.0, RTMP_AMF_TYPE_NUMBER, "audioCodecs", (double)ac, RTMP_AMF_TYPE_NUMBER, "videoCodecs", (double)vc, RTMP_AMF_TYPE_NUMBER, "videoFunction", 1.0); } static int client_handle_packet(struct rtmp_conn *conn, struct mbuf *mb) { uint8_t s0; uint8_t s1[RTMP_HANDSHAKE_SIZE]; int err = 0; switch (conn->state) { case RTMP_STATE_VERSION_SENT: if (mbuf_get_left(mb) < (1+RTMP_HANDSHAKE_SIZE)) return ENODATA; s0 = mbuf_read_u8(mb); if (s0 != RTMP_PROTOCOL_VERSION) return EPROTO; (void)mbuf_read_mem(mb, s1, sizeof(s1)); err = send_packet(conn, s1, sizeof(s1)); if (err) return err; set_state(conn, RTMP_STATE_ACK_SENT); break; case RTMP_STATE_ACK_SENT: if (mbuf_get_left(mb) < RTMP_HANDSHAKE_SIZE) return ENODATA; /* S2 (ignored) */ mbuf_advance(mb, RTMP_HANDSHAKE_SIZE); conn->send_chunk_size = 4096; err = rtmp_control(conn, RTMP_TYPE_SET_CHUNK_SIZE, conn->send_chunk_size); if (err) return err; err = send_connect(conn); if (err) return err; set_state(conn, RTMP_STATE_HANDSHAKE_DONE); break; case RTMP_STATE_HANDSHAKE_DONE: err = rtmp_dechunker_receive(conn->dechunk, mb); if (err) return err; break; default: return EPROTO; } return 0; } static int server_handle_packet(struct rtmp_conn *conn, struct mbuf *mb) { uint8_t c0; uint8_t c1[RTMP_HANDSHAKE_SIZE]; int err = 0; switch (conn->state) { case RTMP_STATE_UNINITIALIZED: if (mbuf_get_left(mb) < 1) return ENODATA; c0 = mbuf_read_u8(mb); if (c0 != RTMP_PROTOCOL_VERSION) return EPROTO; /* Send S0 + S1 */ err = handshake_start(conn); if (err) return err; break; case RTMP_STATE_VERSION_SENT: if (mbuf_get_left(mb) < RTMP_HANDSHAKE_SIZE) return ENODATA; (void)mbuf_read_mem(mb, c1, sizeof(c1)); /* Copy C1 to S2 */ err = send_packet(conn, c1, sizeof(c1)); if (err) return err; set_state(conn, RTMP_STATE_ACK_SENT); break; case RTMP_STATE_ACK_SENT: if (mbuf_get_left(mb) < RTMP_HANDSHAKE_SIZE) return ENODATA; /* C2 (ignored) */ mbuf_advance(mb, RTMP_HANDSHAKE_SIZE); conn->send_chunk_size = 4096; err = rtmp_control(conn, RTMP_TYPE_SET_CHUNK_SIZE, conn->send_chunk_size); if (err) return err; set_state(conn, RTMP_STATE_HANDSHAKE_DONE); break; case RTMP_STATE_HANDSHAKE_DONE: err = rtmp_dechunker_receive(conn->dechunk, mb); if (err) return err; break; default: return EPROTO; } return 0; } static void tcp_recv_handler(struct mbuf *mb_pkt, void *arg) { struct rtmp_conn *conn = arg; int err = 0; conn->total_bytes += mbuf_get_left(mb_pkt); /* re-assembly of fragments */ if (conn->mb) { const size_t len = mbuf_get_left(mb_pkt), pos = conn->mb->pos; if ((mbuf_get_left(conn->mb) + len) > RTMP_MESSAGE_LEN_MAX) { err = EOVERFLOW; goto out; } conn->mb->pos = conn->mb->end; err = mbuf_write_mem(conn->mb, mbuf_buf(mb_pkt), mbuf_get_left(mb_pkt)); if (err) goto out; conn->mb->pos = pos; } else { conn->mb = mem_ref(mb_pkt); } while (mbuf_get_left(conn->mb) > 0) { size_t pos; uint32_t nrefs; pos = conn->mb->pos; mem_ref(conn); if (conn->is_client) err = client_handle_packet(conn, conn->mb); else err = server_handle_packet(conn, conn->mb); nrefs = mem_nrefs(conn); mem_deref(conn); if (nrefs == 1) return; if (!conn->tc) return; if (err) { /* rewind */ conn->mb->pos = pos; if (err == ENODATA) err = 0; break; } if (conn->mb->pos >= conn->mb->end) { conn->mb = mem_deref(conn->mb); break; } } if (err) goto out; if (conn->total_bytes >= (conn->last_ack + conn->window_ack_size)) { conn->last_ack = conn->total_bytes; err = rtmp_control(conn, RTMP_TYPE_ACKNOWLEDGEMENT, (uint32_t)conn->total_bytes); if (err) goto out; } out: if (err) conn_close(conn, err); } static void tcp_close_handler(int err, void *arg) { struct rtmp_conn *conn = arg; if (conn->is_client && !conn->connected && conn->srvc > 0) { err = req_connect(conn); if (!err) return; } conn_close(conn, err); } static int req_connect(struct rtmp_conn *conn) { const struct sa *addr; int err = EINVAL; while (conn->srvc > 0) { --conn->srvc; addr = &conn->srvv[conn->srvc]; conn->send_chunk_size = RTMP_DEFAULT_CHUNKSIZE; conn->window_ack_size = WINDOW_ACK_SIZE; conn->state = RTMP_STATE_UNINITIALIZED; conn->last_ack = 0; conn->total_bytes = 0; conn->mb = mem_deref(conn->mb); conn->sc = mem_deref(conn->sc); conn->tc = mem_deref(conn->tc); rtmp_dechunker_set_chunksize(conn->dechunk, RTMP_DEFAULT_CHUNKSIZE); err = tcp_connect(&conn->tc, addr, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, conn); #ifdef USE_TLS if (conn->tls && !err) { err = tls_start_tcp(&conn->sc, conn->tls, conn->tc, 0); if (!err) err = tls_set_verify_server(conn->sc, conn->host); } #endif if (!err) break; } return err; } static bool rr_handler(struct dnsrr *rr, void *arg) { struct rtmp_conn *conn = arg; if (conn->srvc >= RE_ARRAY_SIZE(conn->srvv)) return true; switch (rr->type) { case DNS_TYPE_A: sa_set_in(&conn->srvv[conn->srvc++], rr->rdata.a.addr, conn->port); break; case DNS_TYPE_AAAA: sa_set_in6(&conn->srvv[conn->srvc++], rr->rdata.aaaa.addr, conn->port); break; } return false; } static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct rtmp_conn *conn = arg; (void)hdr; (void)authl; (void)addl; dns_rrlist_apply2(ansl, conn->host, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN, true, rr_handler, conn); /* wait for other (A/AAAA) query to complete */ if (conn->dnsq4 || conn->dnsq6) return; if (conn->srvc == 0) { err = err ? err : EDESTADDRREQ; goto out; } err = req_connect(conn); if (err) goto out; return; out: conn_close(conn, err); } /** * Connect to an RTMP server * * @param connp Pointer to allocated RTMP connection object * @param dnsc DNS Client for resolving FQDN uris * @param uri RTMP uri to connect to * @param tls TLS Context (optional) * @param estabh Established handler * @param cmdh Incoming command handler * @param closeh Close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode * * Example URIs: * * rtmp://a.rtmp.youtube.com/live2/my-stream * rtmp://[::1]/vod/mp4:sample.mp4 */ int rtmp_connect(struct rtmp_conn **connp, struct dnsc *dnsc, const char *uri, struct tls *tls, rtmp_estab_h *estabh, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg) { struct rtmp_conn *conn; struct pl pl_scheme; struct pl pl_hostport; struct pl pl_host; struct pl pl_port; struct pl pl_path; struct pl pl_app; struct pl pl_stream; const char *tok; uint16_t defport; int err; if (!connp || !uri) return EINVAL; if (re_regex(uri, strlen(uri), "[a-z]+://[^/]+/[^]+", &pl_scheme, &pl_hostport, &pl_path)) return EINVAL; tok = pl_strrchr(&pl_path, '/'); if (!tok) return EINVAL; pl_app.p = pl_path.p; pl_app.l = tok - pl_path.p; pl_stream.p = tok + 1; pl_stream.l = pl_path.p + pl_path.l - pl_stream.p; if (!pl_strcasecmp(&pl_scheme, "rtmp")) { tls = NULL; defport = RTMP_PORT; } #ifdef USE_TLS else if (!pl_strcasecmp(&pl_scheme, "rtmps")) { if (!tls) return EINVAL; defport = 443; } #endif else return ENOTSUP; if (uri_decode_hostport(&pl_hostport, &pl_host, &pl_port)) return EINVAL; conn = rtmp_conn_alloc(true, estabh, cmdh, closeh, arg); if (!conn) return ENOMEM; conn->port = pl_isset(&pl_port) ? pl_u32(&pl_port) : defport; conn->tls = tls; err = pl_strdup(&conn->app, &pl_app); err |= pl_strdup(&conn->stream, &pl_stream); err |= pl_strdup(&conn->host, &pl_host); err |= str_dup(&conn->uri, uri); if (err) goto out; if (0 == sa_set(&conn->srvv[0], &pl_host, conn->port)) { conn->srvc = 1; err = req_connect(conn); if (err) goto out; } else { struct sa tmp; if (!dnsc) { err = EINVAL; goto out; } conn->dnsc = mem_ref(dnsc); err = dnsc_query(&conn->dnsq4, dnsc, conn->host, DNS_TYPE_A, DNS_CLASS_IN, true, query_handler, conn); if (err) goto out; if (0 == net_default_source_addr_get(AF_INET6, &tmp)) { err = dnsc_query(&conn->dnsq6, dnsc, conn->host, DNS_TYPE_AAAA, DNS_CLASS_IN, true, query_handler, conn); if (err) goto out; } } out: if (err) mem_deref(conn); else *connp = conn; return err; } /** * Accept an incoming TCP connection creating an RTMP Server connection * * @param connp Pointer to allocated RTMP connection object * @param ts TCP socket with pending connection * @param tls TLS Context (optional) * @param cmdh Incoming command handler * @param closeh Close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int rtmp_accept(struct rtmp_conn **connp, struct tcp_sock *ts, struct tls *tls, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg) { struct rtmp_conn *conn; int err; if (!connp || !ts) return EINVAL; conn = rtmp_conn_alloc(false, NULL, cmdh, closeh, arg); if (!conn) return ENOMEM; err = tcp_accept(&conn->tc, ts, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, conn); if (err) goto out; if (tls) { #ifdef USE_TLS err = tls_start_tcp(&conn->sc, tls, conn->tc, 0); if (err) goto out; #endif } out: if (err) mem_deref(conn); else *connp = conn; return err; } int rtmp_conn_send_msg(const struct rtmp_conn *conn, unsigned format, uint32_t chunk_id, uint32_t timestamp, uint32_t timestamp_delta, uint8_t msg_type_id, uint32_t msg_stream_id, const uint8_t *payload, size_t payload_len) { if (!conn || !payload || !payload_len) return EINVAL; return rtmp_chunker(format, chunk_id, timestamp, timestamp_delta, msg_type_id, msg_stream_id, payload, payload_len, conn->send_chunk_size, conn->tc); } unsigned rtmp_conn_assign_chunkid(struct rtmp_conn *conn) { if (!conn) return 0; return ++conn->chunk_id_counter; } uint64_t rtmp_conn_assign_tid(struct rtmp_conn *conn) { if (!conn) return 0; return ++conn->tid_counter; } /** * Get the underlying TCP connection from an RTMP connection * * @param conn RTMP Connection * * @return TCP-Connection */ struct tcp_conn *rtmp_conn_tcpconn(const struct rtmp_conn *conn) { return conn ? conn->tc : NULL; } /** * Get the RTMP connection stream name from rtmp_connect * * @param conn RTMP Connection * * @return RTMP Stream name or NULL */ const char *rtmp_conn_stream(const struct rtmp_conn *conn) { return conn ? conn->stream : NULL; } /** * Set callback handlers for the RTMP connection * * @param conn RTMP connection * @param cmdh Incoming command handler * @param closeh Close handler * @param arg Handler argument */ void rtmp_set_handlers(struct rtmp_conn *conn, rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg) { if (!conn) return; conn->cmdh = cmdh; conn->closeh = closeh; conn->arg = arg; } static const char *rtmp_handshake_name(enum rtmp_handshake_state state) { switch (state) { case RTMP_STATE_UNINITIALIZED: return "UNINITIALIZED"; case RTMP_STATE_VERSION_SENT: return "VERSION_SENT"; case RTMP_STATE_ACK_SENT: return "ACK_SENT"; case RTMP_STATE_HANDSHAKE_DONE: return "HANDSHAKE_DONE"; default: return "?"; } } int rtmp_conn_debug(struct re_printf *pf, const struct rtmp_conn *conn) { int err = 0; if (!conn) return 0; err |= re_hprintf(pf, "role: %s\n", conn->is_client ? "Client" : "Server"); err |= re_hprintf(pf, "state: %s\n", rtmp_handshake_name(conn->state)); err |= re_hprintf(pf, "connected: %d\n", conn->connected); err |= re_hprintf(pf, "chunk_size: send=%u\n", conn->send_chunk_size); err |= re_hprintf(pf, "bytes: %zu\n", conn->total_bytes); err |= re_hprintf(pf, "streams: %u\n", list_count(&conn->streaml)); if (conn->is_client) { err |= re_hprintf(pf, "uri: %s\n", conn->uri); err |= re_hprintf(pf, "app: %s\n", conn->app); err |= re_hprintf(pf, "stream: %s\n", conn->stream); } err |= re_hprintf(pf, "%H\n", rtmp_dechunker_debug, conn->dechunk); return err; } ================================================ FILE: src/rtmp/control.c ================================================ /** * @file rtmp/control.c Real Time Messaging Protocol (RTMP) -- Control * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "rtmp.h" /** * Send an RTMP control message * * @param conn RTMP connection * @param type RTMP Packet type * @param ... Optional packet arguments * * @return 0 if success, otherwise errorcode */ int rtmp_control(const struct rtmp_conn *conn, enum rtmp_packet_type type, ...) { struct mbuf *mb; uint32_t u32; uint16_t event; va_list ap; int err = 0; if (!conn) return EINVAL; mb = mbuf_alloc(8); if (!mb) return ENOMEM; va_start(ap, type); switch (type) { case RTMP_TYPE_SET_CHUNK_SIZE: case RTMP_TYPE_WINDOW_ACK_SIZE: case RTMP_TYPE_ACKNOWLEDGEMENT: u32 = va_arg(ap, uint32_t); err = mbuf_write_u32(mb, htonl(u32)); break; case RTMP_TYPE_USER_CONTROL_MSG: event = va_arg(ap, unsigned); err = mbuf_write_u16(mb, htons(event)); err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t))); break; case RTMP_TYPE_SET_PEER_BANDWIDTH: err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t))); err |= mbuf_write_u8(mb, va_arg(ap, unsigned)); break; default: err = ENOTSUP; break; } va_end(ap); if (err) goto out; err = rtmp_conn_send_msg(conn, 0, RTMP_CHUNK_ID_CONTROL, 0, 0, type, RTMP_CONTROL_STREAM_ID, mb->buf, mb->end); if (err) goto out; out: mem_deref(mb); return err; } /** * Get the event name as a string * * @param event RTMP Event type * * @return Name of the event as a string */ const char *rtmp_event_name(enum rtmp_event_type event) { switch (event) { case RTMP_EVENT_STREAM_BEGIN: return "StreamBegin"; case RTMP_EVENT_STREAM_EOF: return "StreamEOF"; case RTMP_EVENT_STREAM_DRY: return "StreamDry"; case RTMP_EVENT_SET_BUFFER_LENGTH: return "SetBufferLength"; case RTMP_EVENT_STREAM_IS_RECORDED: return "StreamIsRecorded"; case RTMP_EVENT_PING_REQUEST: return "PingRequest"; case RTMP_EVENT_PING_RESPONSE: return "PingResponse"; default: return "?"; } } ================================================ FILE: src/rtmp/ctrans.c ================================================ /** * @file rtmp/ctrans.c Real Time Messaging Protocol -- AMF Client Transactions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" struct rtmp_ctrans { struct le le; uint64_t tid; rtmp_resp_h *resph; void *arg; }; static void ctrans_destructor(void *data) { struct rtmp_ctrans *ct = data; list_unlink(&ct->le); } static struct rtmp_ctrans *rtmp_ctrans_find(const struct list *ctransl, uint64_t tid) { struct le *le; for (le = list_head(ctransl); le; le = le->next) { struct rtmp_ctrans *ct = le->data; if (tid == ct->tid) return ct; } return NULL; } int rtmp_amf_request(struct rtmp_conn *conn, uint32_t stream_id, const char *command, rtmp_resp_h *resph, void *arg, unsigned body_propc, ...) { struct rtmp_ctrans *ct = NULL; struct mbuf *mb; va_list ap; int err; if (!conn || !command || !resph) return EINVAL; mb = mbuf_alloc(512); if (!mb) return ENOMEM; ct = mem_zalloc(sizeof(*ct), ctrans_destructor); if (!ct) { err = ENOMEM; goto out; } ct->tid = rtmp_conn_assign_tid(conn); ct->resph = resph; ct->arg = arg; err = rtmp_command_header_encode(mb, command, ct->tid); if (err) goto out; if (body_propc) { va_start(ap, body_propc); err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT, body_propc, &ap); va_end(ap); if (err) goto out; } err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN, RTMP_TYPE_AMF0, stream_id, mb->buf, mb->end); if (err) goto out; list_append(&conn->ctransl, &ct->le, ct); out: mem_deref(mb); if (err) mem_deref(ct); return err; } int rtmp_ctrans_response(const struct list *ctransl, const struct odict *msg) { struct rtmp_ctrans *ct; uint64_t tid; bool success; rtmp_resp_h *resph; void *arg; if (!ctransl || !msg) return EINVAL; success = (0 == str_casecmp(odict_string(msg, "0"), "_result")); if (!odict_get_number(msg, &tid, "1")) return EPROTO; ct = rtmp_ctrans_find(ctransl, tid); if (!ct) return ENOENT; resph = ct->resph; arg = ct->arg; mem_deref(ct); resph(success, msg, arg); return 0; } ================================================ FILE: src/rtmp/dechunk.c ================================================ /** * @file rtmp/dechunk.c Real Time Messaging Protocol (RTMP) -- Dechunking * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "rtmp.h" enum { MAX_CHUNKS = 64, }; struct rtmp_chunk { struct le le; struct rtmp_header hdr; struct mbuf *mb; }; /** Defines the RTMP Dechunker */ struct rtmp_dechunker { struct list chunkl; /* struct rtmp_chunk */ size_t chunk_sz; rtmp_dechunk_h *chunkh; void *arg; }; static void destructor(void *data) { struct rtmp_dechunker *rd = data; list_flush(&rd->chunkl); } static void chunk_destructor(void *data) { struct rtmp_chunk *chunk = data; list_unlink(&chunk->le); mem_deref(chunk->mb); } static struct rtmp_chunk *create_chunk(struct list *chunkl, const struct rtmp_header *hdr) { struct rtmp_chunk *chunk; chunk = mem_zalloc(sizeof(*chunk), chunk_destructor); if (!chunk) return NULL; chunk->hdr = *hdr; list_append(chunkl, &chunk->le, chunk); return chunk; } static struct rtmp_chunk *find_chunk(const struct list *chunkl, uint32_t chunk_id) { struct le *le; for (le = list_head(chunkl); le; le = le->next) { struct rtmp_chunk *chunk = le->data; if (chunk_id == chunk->hdr.chunk_id) return chunk; } return NULL; } /* * Stateful RTMP de-chunker for receiving complete messages */ int rtmp_dechunker_alloc(struct rtmp_dechunker **rdp, size_t chunk_sz, rtmp_dechunk_h *chunkh, void *arg) { struct rtmp_dechunker *rd; if (!rdp || !chunk_sz || !chunkh) return EINVAL; rd = mem_zalloc(sizeof(*rd), destructor); if (!rd) return ENOMEM; rd->chunk_sz = chunk_sz; rd->chunkh = chunkh; rd->arg = arg; *rdp = rd; return 0; } int rtmp_dechunker_receive(struct rtmp_dechunker *rd, struct mbuf *mb) { struct rtmp_header hdr; struct rtmp_chunk *chunk; size_t chunk_sz, left, msg_len; int err; if (!rd || !mb) return EINVAL; err = rtmp_header_decode(&hdr, mb); if (err) return err; /* find preceding chunk, from chunk id */ chunk = find_chunk(&rd->chunkl, hdr.chunk_id); if (!chunk) { /* only type 0 can create a new chunk stream */ if (hdr.format == 0) { if (list_count(&rd->chunkl) > MAX_CHUNKS) return EOVERFLOW; chunk = create_chunk(&rd->chunkl, &hdr); if (!chunk) return ENOMEM; } else return ENOENT; } switch (hdr.format) { case 0: case 1: case 2: if (hdr.format == 0) { /* copy the whole header */ chunk->hdr = hdr; } else if (hdr.format == 1) { chunk->hdr.timestamp_delta = hdr.timestamp_delta; chunk->hdr.length = hdr.length; chunk->hdr.type_id = hdr.type_id; } else if (hdr.format == 2) { chunk->hdr.timestamp_delta = hdr.timestamp_delta; } msg_len = chunk->hdr.length; chunk_sz = min(msg_len, rd->chunk_sz); if (mbuf_get_left(mb) < chunk_sz) return ENODATA; mem_deref(chunk->mb); chunk->mb = mbuf_alloc(msg_len); if (!chunk->mb) return ENOMEM; err = mbuf_read_mem(mb, chunk->mb->buf, chunk_sz); if (err) return err; chunk->mb->pos = chunk_sz; chunk->mb->end = chunk_sz; chunk->hdr.format = hdr.format; chunk->hdr.ext_ts = hdr.ext_ts; if (hdr.format == 1 || hdr.format == 2) chunk->hdr.timestamp += hdr.timestamp_delta; break; case 3: if (chunk->hdr.ext_ts) { uint32_t ext_ts; if (mbuf_get_left(mb) < 4) return ENODATA; ext_ts = ntohl(mbuf_read_u32(mb)); if (chunk->hdr.format == 0) chunk->hdr.timestamp = ext_ts; else chunk->hdr.timestamp_delta = ext_ts; } if (!chunk->mb) { chunk->mb = mbuf_alloc(chunk->hdr.length); if (!chunk->mb) return ENOMEM; if (chunk->hdr.format == 0) { chunk->hdr.timestamp_delta = chunk->hdr.timestamp; } chunk->hdr.timestamp += chunk->hdr.timestamp_delta; } left = mbuf_get_space(chunk->mb); chunk_sz = min(left, rd->chunk_sz); if (mbuf_get_left(mb) < chunk_sz) return ENODATA; err = mbuf_read_mem(mb, mbuf_buf(chunk->mb), chunk_sz); if (err) return err; chunk->mb->pos += chunk_sz; chunk->mb->end += chunk_sz; break; default: return EPROTO; } if (chunk->mb->pos >= chunk->mb->size) { struct mbuf *buf; chunk->mb->pos = 0; buf = chunk->mb; chunk->mb = NULL; err = rd->chunkh(&chunk->hdr, buf, rd->arg); mem_deref(buf); } return err; } void rtmp_dechunker_set_chunksize(struct rtmp_dechunker *rd, size_t chunk_sz) { if (!rd || !chunk_sz) return; rd->chunk_sz = chunk_sz; } int rtmp_dechunker_debug(struct re_printf *pf, const struct rtmp_dechunker *rd) { struct le *le; int err; if (!rd) return 0; err = re_hprintf(pf, "Dechunker Debug:\n"); err |= re_hprintf(pf, "chunk list: (%u)\n", list_count(&rd->chunkl)); for (le = rd->chunkl.head; le; le = le->next) { const struct rtmp_chunk *msg = le->data; err |= re_hprintf(pf, ".. %H\n", rtmp_header_print, &msg->hdr); } err |= re_hprintf(pf, "\n"); return err; } ================================================ FILE: src/rtmp/hdr.c ================================================ /** * @file rtmp/hdr.c Real Time Messaging Protocol (RTMP) -- Headers * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" enum { RTMP_CHUNK_ID_MIN = 3, RTMP_CHUNK_ID_MAX = 65599, /* 65535 + 64 */ RTMP_CHUNK_OFFSET = 64, TIMESTAMP_24MAX = 0x00ffffff, }; static int mbuf_write_u24_hton(struct mbuf *mb, uint32_t u24) { int err = 0; err |= mbuf_write_u8(mb, u24 >> 16); err |= mbuf_write_u8(mb, u24 >> 8); err |= mbuf_write_u8(mb, u24 >> 0); return err; } static uint32_t mbuf_read_u24_ntoh(struct mbuf *mb) { uint32_t u24; u24 = (uint32_t)mbuf_read_u8(mb) << 16; u24 |= (uint32_t)mbuf_read_u8(mb) << 8; u24 |= (uint32_t)mbuf_read_u8(mb) << 0; return u24; } static int encode_basic_hdr(struct mbuf *mb, unsigned fmt, uint32_t chunk_id) { uint8_t v; int err = 0; if (chunk_id >= 320) { const uint16_t cs_id = chunk_id - RTMP_CHUNK_OFFSET; v = fmt<<6 | 1; err |= mbuf_write_u8(mb, v); err |= mbuf_write_u16(mb, htons(cs_id)); } else if (chunk_id >= RTMP_CHUNK_OFFSET) { const uint8_t cs_id = chunk_id - RTMP_CHUNK_OFFSET; v = fmt<<6 | 0; err |= mbuf_write_u8(mb, v); err |= mbuf_write_u8(mb, cs_id); } else { v = fmt<<6 | chunk_id; err |= mbuf_write_u8(mb, v); } return err; } static int decode_basic_hdr(struct rtmp_header *hdr, struct mbuf *mb) { uint8_t cs_id; uint8_t v; if (mbuf_get_left(mb) < 1) return ENODATA; v = mbuf_read_u8(mb); hdr->format = v>>6; cs_id = v & 0x3f; switch (cs_id) { case 0: if (mbuf_get_left(mb) < 1) return ENODATA; hdr->chunk_id = mbuf_read_u8(mb) + RTMP_CHUNK_OFFSET; break; case 1: if (mbuf_get_left(mb) < 2) return ENODATA; hdr->chunk_id = ntohs(mbuf_read_u16(mb)) + RTMP_CHUNK_OFFSET; break; default: hdr->chunk_id = cs_id; break; } return 0; } static uint32_t ts_24(uint32_t ts) { return ts >= TIMESTAMP_24MAX ? TIMESTAMP_24MAX : ts; } static uint32_t ts_ext(uint32_t ts) { return ts >= TIMESTAMP_24MAX ? ts : 0; } int rtmp_header_encode(struct mbuf *mb, struct rtmp_header *hdr) { int err = 0; if (!mb || !hdr) return EINVAL; err = encode_basic_hdr(mb, hdr->format, hdr->chunk_id); if (err) return err; switch (hdr->format) { case 0: hdr->timestamp_ext = ts_ext(hdr->timestamp); err |= mbuf_write_u24_hton(mb, ts_24(hdr->timestamp)); err |= mbuf_write_u24_hton(mb, hdr->length); err |= mbuf_write_u8(mb, hdr->type_id); err |= mbuf_write_u32(mb, sys_htoll(hdr->stream_id)); break; case 1: hdr->timestamp_ext = ts_ext(hdr->timestamp_delta); err |= mbuf_write_u24_hton(mb, ts_24(hdr->timestamp_delta)); err |= mbuf_write_u24_hton(mb, hdr->length); err |= mbuf_write_u8(mb, hdr->type_id); break; case 2: hdr->timestamp_ext = ts_ext(hdr->timestamp_delta); err |= mbuf_write_u24_hton(mb, ts_24(hdr->timestamp_delta)); break; case 3: break; } if (hdr->timestamp_ext) { err |= mbuf_write_u32(mb, htonl(hdr->timestamp_ext)); } return err; } int rtmp_header_decode(struct rtmp_header *hdr, struct mbuf *mb) { uint32_t *timestamp_ext = NULL; int err; if (!hdr || !mb) return EINVAL; memset(hdr, 0, sizeof(*hdr)); err = decode_basic_hdr(hdr, mb); if (err) return err; switch (hdr->format) { case 0: if (mbuf_get_left(mb) < 11) return ENODATA; hdr->timestamp = mbuf_read_u24_ntoh(mb); hdr->length = mbuf_read_u24_ntoh(mb); hdr->type_id = mbuf_read_u8(mb); hdr->stream_id = sys_ltohl(mbuf_read_u32(mb)); break; case 1: if (mbuf_get_left(mb) < 7) return ENODATA; hdr->timestamp_delta = mbuf_read_u24_ntoh(mb); hdr->length = mbuf_read_u24_ntoh(mb); hdr->type_id = mbuf_read_u8(mb); break; case 2: if (mbuf_get_left(mb) < 3) return ENODATA; hdr->timestamp_delta = mbuf_read_u24_ntoh(mb); break; case 3: /* no payload */ break; } if (hdr->timestamp == TIMESTAMP_24MAX) timestamp_ext = &hdr->timestamp; else if (hdr->timestamp_delta == TIMESTAMP_24MAX) timestamp_ext = &hdr->timestamp_delta; if (timestamp_ext) { if (mbuf_get_left(mb) < 4) return ENODATA; *timestamp_ext = ntohl(mbuf_read_u32(mb)); hdr->ext_ts = true; } return 0; } int rtmp_header_print(struct re_printf *pf, const struct rtmp_header *hdr) { if (!hdr) return 0; return re_hprintf(pf, "fmt %u, chunk %u, " "timestamp %5u, ts_delta %2u," " len %3u, type %2u (%-14s) stream_id %u", hdr->format, hdr->chunk_id, hdr->timestamp, hdr->timestamp_delta, hdr->length, hdr->type_id, rtmp_packet_type_name(hdr->type_id), hdr->stream_id); } const char *rtmp_packet_type_name(enum rtmp_packet_type type) { switch (type) { case RTMP_TYPE_SET_CHUNK_SIZE: return "Set Chunk Size"; case RTMP_TYPE_ACKNOWLEDGEMENT: return "Acknowledgement"; case RTMP_TYPE_USER_CONTROL_MSG: return "User Control Message"; case RTMP_TYPE_WINDOW_ACK_SIZE: return "Window Acknowledgement Size"; case RTMP_TYPE_SET_PEER_BANDWIDTH:return "Set Peer Bandwidth"; case RTMP_TYPE_AUDIO: return "Audio Message"; case RTMP_TYPE_VIDEO: return "Video Message"; case RTMP_TYPE_DATA: return "Data Message"; case RTMP_TYPE_AMF0: return "AMF"; default: return "?"; } } ================================================ FILE: src/rtmp/rtmp.h ================================================ /** * @file rtmp.h Real Time Messaging Protocol (RTMP) -- Internal API * * Copyright (C) 2010 Creytiv.com */ enum { RTMP_PROTOCOL_VERSION = 3, RTMP_DEFAULT_CHUNKSIZE = 128, RTMP_HANDSHAKE_SIZE = 1536, RTMP_MESSAGE_LEN_MAX = 524288, }; /* Chunk IDs */ enum { RTMP_CHUNK_ID_CONTROL = 2, RTMP_CHUNK_ID_CONN = 3, }; /** Defines the RTMP Handshake State */ enum rtmp_handshake_state { RTMP_STATE_UNINITIALIZED = 0, RTMP_STATE_VERSION_SENT, RTMP_STATE_ACK_SENT, RTMP_STATE_HANDSHAKE_DONE }; /** * Defines an RTMP Connection */ struct rtmp_conn { struct list streaml; struct rtmp_dechunker *dechunk; struct tcp_conn *tc; struct tls_conn *sc; struct mbuf *mb; /* TCP reassembly buffer */ enum rtmp_handshake_state state; size_t total_bytes; size_t last_ack; uint32_t window_ack_size; uint32_t send_chunk_size; unsigned chunk_id_counter; bool is_client; bool connected; rtmp_estab_h *estabh; rtmp_command_h *cmdh; rtmp_close_h *closeh; void *arg; /* client specific: */ struct dnsc *dnsc; struct dns_query *dnsq4; struct dns_query *dnsq6; struct list ctransl; struct sa srvv[16]; struct tls *tls; unsigned srvc; uint64_t tid_counter; uint16_t port; char *app; char *uri; char *stream; char *host; }; /** * Defines an RTMP Stream */ struct rtmp_stream { struct le le; const struct rtmp_conn *conn; /**< Pointer to parent connection */ bool created; uint32_t stream_id; unsigned chunk_id_audio; unsigned chunk_id_video; unsigned chunk_id_data; rtmp_audio_h *auh; rtmp_video_h *vidh; rtmp_command_h *datah; rtmp_command_h *cmdh; rtmp_resp_h *resph; rtmp_control_h *ctrlh; void *arg; }; struct rtmp_header { unsigned format:2; /* type 0-3 */ uint32_t chunk_id; /* from 3-65599 */ uint32_t timestamp; /* 24-bit or 32-bit */ uint32_t timestamp_delta; /* 24-bit */ uint32_t timestamp_ext; uint32_t length; /* 24-bit */ uint8_t type_id; /* enum rtmp_packet_type */ uint32_t stream_id; bool ext_ts; }; /* Command */ int rtmp_command_header_encode(struct mbuf *mb, const char *name, uint64_t tid); /* Connection */ int rtmp_conn_send_msg(const struct rtmp_conn *conn, unsigned format, uint32_t chunk_id, uint32_t timestamp, uint32_t timestamp_delta, uint8_t msg_type_id, uint32_t msg_stream_id, const uint8_t *payload, size_t payload_len); int rtmp_send_amf_command(const struct rtmp_conn *conn, unsigned format, uint32_t chunk_id, uint8_t type_id, uint32_t msg_stream_id, const uint8_t *cmd, size_t len); unsigned rtmp_conn_assign_chunkid(struct rtmp_conn *conn); uint64_t rtmp_conn_assign_tid(struct rtmp_conn *conn); /* Client Transaction */ struct rtmp_ctrans; int rtmp_ctrans_response(const struct list *ctransl, const struct odict *msg); /* * RTMP Chunk */ int rtmp_chunker(unsigned format, uint32_t chunk_id, uint32_t timestamp, uint32_t timestamp_delta, uint8_t msg_type_id, uint32_t msg_stream_id, const uint8_t *payload, size_t payload_len, size_t max_chunk_sz, struct tcp_conn *tc); /* * RTMP Header */ int rtmp_header_encode(struct mbuf *mb, struct rtmp_header *hdr); int rtmp_header_decode(struct rtmp_header *hdr, struct mbuf *mb); int rtmp_header_print(struct re_printf *pf, const struct rtmp_header *hdr); const char *rtmp_packet_type_name(enum rtmp_packet_type type); /* * RTMP De-chunker */ struct rtmp_dechunker; typedef int (rtmp_dechunk_h)(const struct rtmp_header *hdr, struct mbuf *mb, void *arg); int rtmp_dechunker_alloc(struct rtmp_dechunker **rdp, size_t chunk_sz, rtmp_dechunk_h *chunkh, void *arg); int rtmp_dechunker_receive(struct rtmp_dechunker *rd, struct mbuf *mb); void rtmp_dechunker_set_chunksize(struct rtmp_dechunker *rd, size_t chunk_sz); int rtmp_dechunker_debug(struct re_printf *pf, const struct rtmp_dechunker *rd); /* * AMF (Action Message Format) */ int rtmp_amf_encode_number(struct mbuf *mb, double val); int rtmp_amf_encode_boolean(struct mbuf *mb, bool boolean); int rtmp_amf_encode_string(struct mbuf *mb, const char *str); int rtmp_amf_encode_null(struct mbuf *mb); int rtmp_amf_vencode_object(struct mbuf *mb, enum rtmp_amf_type container, unsigned propc, va_list *ap); int rtmp_amf_decode(struct odict **msgp, struct mbuf *mb); ================================================ FILE: src/rtmp/stream.c ================================================ /** * @file rtmp/stream.c Real Time Messaging Protocol (RTMP) -- NetStream * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "rtmp.h" static void destructor(void *data) { struct rtmp_stream *strm = data; list_unlink(&strm->le); if (strm->created) { rtmp_amf_command(strm->conn, 0, "deleteStream", 3, RTMP_AMF_TYPE_NUMBER, 0.0, RTMP_AMF_TYPE_NULL, RTMP_AMF_TYPE_NUMBER, (double)strm->stream_id); } } /** * Allocate a new RTMP Stream object * * @param strmp Pointer to allocated RTMP Stream * @param conn RTMP Connection * @param stream_id Stream id * @param cmdh Command handler * @param ctrlh Control handler * @param auh Audio handler * @param vidh Video handler * @param datah Data handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int rtmp_stream_alloc(struct rtmp_stream **strmp, struct rtmp_conn *conn, uint32_t stream_id, rtmp_command_h *cmdh, rtmp_control_h *ctrlh, rtmp_audio_h *auh, rtmp_video_h *vidh, rtmp_command_h *datah, void *arg) { struct rtmp_stream *strm; if (!strmp || !conn) return EINVAL; strm = mem_zalloc(sizeof(*strm), destructor); if (!strm) return ENOMEM; strm->conn = conn; strm->stream_id = stream_id; strm->cmdh = cmdh; strm->ctrlh = ctrlh; strm->auh = auh; strm->vidh = vidh; strm->datah = datah; strm->arg = arg; strm->chunk_id_audio = rtmp_conn_assign_chunkid(conn); strm->chunk_id_video = rtmp_conn_assign_chunkid(conn); strm->chunk_id_data = rtmp_conn_assign_chunkid(conn); list_append(&conn->streaml, &strm->le, strm); *strmp = strm; return 0; } static void createstream_handler(bool success, const struct odict *msg, void *arg) { struct rtmp_stream *strm = arg; uint64_t num; if (!success) goto out; if (!odict_get_number(msg, &num, "3")) { success = false; goto out; } strm->stream_id = (uint32_t)num; if (strm->stream_id == 0) { success = false; goto out; } strm->created = true; out: if (strm->resph) strm->resph(success, msg, strm->arg); } /** * Create a new RTMP Stream by sending "createStream" to the RTMP Server. * * @param strmp Pointer to allocated RTMP Stream * @param conn RTMP Connection * @param resph RTMP Response handler * @param cmdh Command handler * @param ctrlh Control handler * @param auh Audio handler * @param vidh Video handler * @param datah Data handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int rtmp_stream_create(struct rtmp_stream **strmp, struct rtmp_conn *conn, rtmp_resp_h *resph, rtmp_command_h *cmdh, rtmp_control_h *ctrlh, rtmp_audio_h *auh, rtmp_video_h *vidh, rtmp_command_h *datah, void *arg) { struct rtmp_stream *strm; int err; if (!strmp || !conn) return EINVAL; err = rtmp_stream_alloc(&strm, conn, (uint32_t)-1, cmdh, ctrlh, auh, vidh, datah, arg); if (err) return err; strm->resph = resph; err = rtmp_amf_request(conn, 0, "createStream", createstream_handler, strm, 1, RTMP_AMF_TYPE_NULL); if (err) goto out; out: if (err) mem_deref(strm); else *strmp = strm; return err; } /** * Start playing an RTMP Stream by sending "play" to the RTMP Server * * @param strm RTMP Stream * @param name Stream name * * @return 0 if success, otherwise errorcode */ int rtmp_play(struct rtmp_stream *strm, const char *name) { if (!strm || !name) return EINVAL; return rtmp_amf_command(strm->conn, strm->stream_id, "play", 4, RTMP_AMF_TYPE_NUMBER, 0.0, RTMP_AMF_TYPE_NULL, RTMP_AMF_TYPE_STRING, name, RTMP_AMF_TYPE_NUMBER, -2000.0); } /** * Start publishing an RTMP Stream by sending "publish" to the RTMP Server * * @param strm RTMP Stream * @param name Stream name * * @return 0 if success, otherwise errorcode */ int rtmp_publish(struct rtmp_stream *strm, const char *name) { if (!strm || !name) return EINVAL; return rtmp_amf_command(strm->conn, strm->stream_id, "publish", 4, RTMP_AMF_TYPE_NUMBER, 0.0, RTMP_AMF_TYPE_NULL, RTMP_AMF_TYPE_STRING, name, RTMP_AMF_TYPE_STRING, "live"); } /** * Send metadata on the stream to the RTMP Server * * @param strm RTMP Stream * * @return 0 if success, otherwise errorcode */ int rtmp_meta(struct rtmp_stream *strm) { if (!strm) return EINVAL; return rtmp_amf_data(strm->conn, strm->stream_id, "@setDataFrame", 2, RTMP_AMF_TYPE_STRING, "onMetaData", RTMP_AMF_TYPE_ECMA_ARRAY, 2, RTMP_AMF_TYPE_NUMBER, "audiocodecid", 10.0, RTMP_AMF_TYPE_NUMBER, "videocodecid", 7.0); } /** * Send audio packet on the RTMP Stream * * @param strm RTMP Stream * @param timestamp Timestamp in [milliseconds] * @param pld Audio payload * @param len Payload length * * @return 0 if success, otherwise errorcode */ int rtmp_send_audio(struct rtmp_stream *strm, uint32_t timestamp, const uint8_t *pld, size_t len) { uint32_t chunk_id; if (!strm || !pld || !len) return EINVAL; chunk_id = strm->chunk_id_audio; return rtmp_conn_send_msg(strm->conn, 0, chunk_id, timestamp, 0, RTMP_TYPE_AUDIO, strm->stream_id, pld, len); } /** * Send video packet on the RTMP Stream * * @param strm RTMP Stream * @param timestamp Timestamp in [milliseconds] * @param pld Video payload * @param len Payload length * * @return 0 if success, otherwise errorcode */ int rtmp_send_video(struct rtmp_stream *strm, uint32_t timestamp, const uint8_t *pld, size_t len) { uint32_t chunk_id; if (!strm || !pld || !len) return EINVAL; chunk_id = strm->chunk_id_video; return rtmp_conn_send_msg(strm->conn, 0, chunk_id, timestamp, 0, RTMP_TYPE_VIDEO, strm->stream_id, pld, len); } /** * Find an RTMP Stream by stream id * * @param conn RTMP Connection * @param stream_id Stream id * * @return RTMP Stream if found, or NULL if not found */ struct rtmp_stream *rtmp_stream_find(const struct rtmp_conn *conn, uint32_t stream_id) { struct le *le; if (!conn) return NULL; for (le = list_head(&conn->streaml); le; le = le->next) { struct rtmp_stream *strm = le->data; if (stream_id == strm->stream_id) return strm; } return NULL; } ================================================ FILE: src/rtp/fb.c ================================================ /** * @file fb.c Real-time Transport Control Protocol (RTCP)-Based Feedback * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "rtcp.h" #define DEBUG_MODULE "rtcp_pb" #define DEBUG_LEVEL 5 #include enum { GNACK_SIZE = 4, FIR_SIZE = 8, SLI_SIZE = 4 }; /* Encode functions */ /** * Encode an RTCP Generic NACK (GNACK) message * * @param mb Buffer to encode into * @param pid Packet ID * @param blp Bitmask of following lost packets (BLP) * * @return 0 for success, otherwise errorcode */ int rtcp_rtpfb_gnack_encode(struct mbuf *mb, uint16_t pid, uint16_t blp) { int err; err = mbuf_write_u16(mb, htons(pid)); err |= mbuf_write_u16(mb, htons(blp)); return err; } /** * Encode an RTCP Transport-wide congestion control Feedback Message * * @param mb Buffer to encode into * @param twcc Transport-wide CC message * * @return 0 for success, otherwise errorcode */ int rtcp_rtpfb_twcc_encode(struct mbuf *mb, struct twcc *twcc) { int err; uint32_t reftime_fbcount = twcc->fbcount; /* 8 bit */ reftime_fbcount |= twcc->reftime << 8; /* 24 bit */ err = mbuf_write_u16(mb, htons(twcc->seq)); err |= mbuf_write_u16(mb, htons(twcc->count)); err |= mbuf_write_u32(mb, htonl(reftime_fbcount)); err |= mbuf_write_mem(mb, mbuf_buf(twcc->chunks), mbuf_get_left(twcc->chunks)); err |= mbuf_write_mem(mb, mbuf_buf(twcc->deltas), mbuf_get_left(twcc->deltas)); return err; } /* Decode functions */ /** * Decode an RTCP Transport-wide congestion control Feedback Message * * @param mb Buffer to decode * @param msg transport-cc struct to decode into * @param n length of the RTCP packet in 32bit words minus one * * @return 0 for success, otherwise errorcode */ int rtcp_rtpfb_twcc_decode(struct mbuf *mb, struct twcc *msg, int n) { size_t j, sz; if (!msg) return EINVAL; if (mbuf_get_left(mb) < 8) return EBADMSG; msg->seq = ntohs(mbuf_read_u16(mb)); msg->count = ntohs(mbuf_read_u16(mb)); if (msg->count == 0 || msg->count > 32768) return EBADMSG; msg->reftime = ntohl(mbuf_read_u32(mb)); msg->fbcount = msg->reftime & 0xff; msg->reftime >>= 8; msg->chunks = mbuf_alloc_ref(mb); if (!msg->chunks) return ENOMEM; msg->chunks->end = msg->chunks->pos; sz = 0; for (size_t i = msg->count; i > 0;) { uint16_t chunk; if (mbuf_get_left(mb) < 2) return EBADMSG; chunk = ntohs(mbuf_read_u16(mb)); msg->chunks->end += 2; if (chunk & 0x8000) { /* status vector chunk */ if (chunk & 0x4000) { for (j = 0; j < i && j < 7; j++) sz += chunk >> (2 * (7 - 1 - j)) & 0x03; } else { for (j = 0; j < i && j < 14; j++) sz += (chunk >> (14 - 1 - j)) & 0x01; } } else { /* run length chunk */ for (j = 0; j < i && j < (chunk & 0x1fffu); j++) sz += (chunk >> 13) & 0x03; } i -= j; } if (mbuf_get_left(mb) < sz) return EBADMSG; msg->deltas = mbuf_alloc_ref(mb); if (!msg->deltas) return ENOMEM; msg->deltas->end = msg->deltas->pos + sz; sz = n * sizeof(uint32_t) - 8 - mbuf_get_left(msg->chunks); if (mbuf_get_left(mb) < sz) return EBADMSG; mbuf_advance(mb, sz); return 0; } /** * Decode an RTCP Transport Layer Feedback Message * * @param mb Buffer to decode * @param msg RTCP Message to decode into * * @return 0 for success, otherwise errorcode */ int rtcp_rtpfb_decode(struct mbuf *mb, struct rtcp_msg *msg) { size_t i, sz; int err; if (!msg) return EINVAL; switch (msg->hdr.count) { case RTCP_RTPFB_GNACK: sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.gnackv); msg->r.fb.fci.gnackv = mem_alloc(sz, NULL); if (!msg->r.fb.fci.gnackv) return ENOMEM; if (mbuf_get_left(mb) < msg->r.fb.n * GNACK_SIZE) return EBADMSG; for (i=0; ir.fb.n; i++) { msg->r.fb.fci.gnackv[i].pid = ntohs(mbuf_read_u16(mb)); msg->r.fb.fci.gnackv[i].blp = ntohs(mbuf_read_u16(mb)); } break; case RTCP_RTPFB_TWCC: if (mbuf_get_left(mb) < 8) return EBADMSG; msg->r.fb.fci.twccv = mem_zalloc(sizeof(*msg->r.fb.fci.twccv), NULL); if (!msg->r.fb.fci.twccv) return ENOMEM; err = rtcp_rtpfb_twcc_decode(mb, msg->r.fb.fci.twccv, msg->r.fb.n); if (err) return err; break; default: DEBUG_NOTICE("unknown RTPFB fmt %d\n", msg->hdr.count); break; } return 0; } /** * Decode an RTCP Payload-Specific Feedback Message * * @param mb Buffer to decode * @param msg RTCP Message to decode into * * @return 0 for success, otherwise errorcode */ int rtcp_psfb_decode(struct mbuf *mb, struct rtcp_msg *msg) { size_t i, sz; if (!msg) return EINVAL; switch (msg->hdr.count) { case RTCP_PSFB_PLI: /* no params */ break; case RTCP_PSFB_SLI: sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.sliv); msg->r.fb.fci.sliv = mem_alloc(sz, NULL); if (!msg->r.fb.fci.sliv) return ENOMEM; if (mbuf_get_left(mb) < msg->r.fb.n * SLI_SIZE) return EBADMSG; for (i=0; ir.fb.n; i++) { const uint32_t v = ntohl(mbuf_read_u32(mb)); msg->r.fb.fci.sliv[i].first = v>>19 & 0x1fff; msg->r.fb.fci.sliv[i].number = v>> 6 & 0x1fff; msg->r.fb.fci.sliv[i].picid = v>> 0 & 0x003f; } break; case RTCP_PSFB_AFB: sz = msg->r.fb.n * 4; if (mbuf_get_left(mb) < sz) return EBADMSG; msg->r.fb.fci.afb = mbuf_alloc_ref(mb); if (!msg->r.fb.fci.afb) return ENOMEM; msg->r.fb.fci.afb->end = msg->r.fb.fci.afb->pos + sz; mbuf_advance(mb, sz); break; case RTCP_PSFB_FIR: msg->r.fb.n /= 2u; /* each FCI entry size is 2 32-bit words */ sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.firv); msg->r.fb.fci.firv = mem_alloc(sz, NULL); if (!msg->r.fb.fci.firv) return ENOMEM; if (mbuf_get_left(mb) < msg->r.fb.n * FIR_SIZE) return EBADMSG; for (i=0; ir.fb.n; i++) { msg->r.fb.fci.firv[i].ssrc = ntohl(mbuf_read_u32(mb)); msg->r.fb.fci.firv[i].seq_n = mbuf_read_u8(mb); mbuf_advance(mb, 3); } break; default: DEBUG_NOTICE("unknown PSFB fmt %d\n", msg->hdr.count); break; } return 0; } ================================================ FILE: src/rtp/member.c ================================================ /** * @file member.c Real-time Transport Control Protocol member * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "rtcp.h" static void destructor(void *data) { struct rtp_member *mbr = data; hash_unlink(&mbr->le); mem_deref(mbr->s); } struct rtp_member *rtp_member_add(struct hash *ht, uint32_t src) { struct rtp_member *mbr; mbr = mem_zalloc(sizeof(*mbr), destructor); if (!mbr) return NULL; hash_append(ht, src, &mbr->le, mbr); mbr->src = src; return mbr; } static bool hash_cmp_handler(struct le *le, void *arg) { const struct rtp_member *mbr = le->data; return mbr->src == *(uint32_t *)arg; } struct rtp_member *rtp_member_find(struct hash *ht, uint32_t src) { return list_ledata(hash_lookup(ht, src, hash_cmp_handler, &src)); } ================================================ FILE: src/rtp/ntp.c ================================================ /** * @file ntp.c NTP Routines * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include "rtcp.h" /* * Unix time: seconds relative to 0h January 1, 1970 * NTP time: seconds relative to 0h UTC on 1 January 1900 */ /* Number of seconds from 1900 to 1970 */ #define UNIX_NTP_OFFSET 0x83aa7e80 /** * Convert from Unix time to NTP time * * @param ntp NTP time to convert to (output) * @param tv Unix time to convert from (input) */ void unix2ntp(struct rtp_ntp_time *ntp, const struct timeval *tv) { ntp->hi = (uint32_t)(tv->tv_sec + UNIX_NTP_OFFSET); ntp->lo = (uint32_t)((double)tv->tv_usec*(double)(1LL<<32)*1.0e-6); } /** * Obtain the current wallclock time in NTP and jiffies formats * * @param ntp NTP time * @param jfs_rt Microseconds since UNIX epoch. Optional, may be NULL. */ void ntp_time_get(struct rtp_ntp_time *ntp, uint64_t *jfs_rt) { #if defined(WIN32) /* timeval::tv_sec on Windows is 32-bit, and it doesn't * define suseconds_t */ typedef long tv_sec_t; typedef long tv_usec_t; #else typedef time_t tv_sec_t; typedef suseconds_t tv_usec_t; #endif struct timeval tv; uint64_t jfs = tmr_jiffies_rt_usec(); if (jfs_rt) *jfs_rt = jfs; tv.tv_sec = (tv_sec_t)(jfs / 1000000u); tv.tv_usec = (tv_usec_t)(jfs % 1000000u); unix2ntp(ntp, &tv); } /** * Convert NTP time to middle 32-bits (compact representation) * * @param ntp NTP time * * @return NTP time in compact representation */ uint32_t ntp_compact(const struct rtp_ntp_time *ntp) { return ntp ? ((ntp->hi & 0xffff) << 16 | (ntp->lo >> 16)) : 0; } /** * Convert NTP compact representation to microseconds * * @param ntpc NTP time in compact representation * * @return NTP time in microseconds */ uint64_t ntp_compact2us(uint32_t ntpc) { const uint32_t hi = (ntpc >> 16) & 0xffff; const uint32_t lo = (ntpc & 0xffff) << 16; return (1000000ULL * hi) + ((1000000ULL * lo) >> 32); } ================================================ FILE: src/rtp/pkt.c ================================================ /** * @file pkt.c RTCP Packet handling * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "rtcp.h" #define DEBUG_MODULE "rtcp_pkt" #define DEBUG_LEVEL 5 #include static void rtcp_destructor(void *data) { struct rtcp_msg *msg = data; size_t i, j; switch (msg->hdr.pt) { case RTCP_SR: mem_deref(msg->r.sr.rrv); break; case RTCP_RR: mem_deref(msg->r.rr.rrv); break; case RTCP_SDES: if (!msg->r.sdesv) break; for (i=0; ihdr.count; i++) { struct rtcp_sdes *sdes = &msg->r.sdesv[i]; for (j=0; jn; j++) { mem_deref(sdes->itemv[j].data); } mem_deref(sdes->itemv); } mem_deref(msg->r.sdesv); break; case RTCP_BYE: mem_deref(msg->r.bye.srcv); mem_deref(msg->r.bye.reason); break; case RTCP_APP: mem_deref(msg->r.app.data); break; case RTCP_RTPFB: if (msg->hdr.count == RTCP_RTPFB_TWCC && msg->r.fb.fci.twccv) { mem_deref(msg->r.fb.fci.twccv->chunks); mem_deref(msg->r.fb.fci.twccv->deltas); } /*@fallthrough@*/ case RTCP_PSFB: mem_deref(msg->r.fb.fci.p); break; default: /* nothing allocated */ break; } } /** * Encode the RTCP Header * * @param mb Buffer to encode into * @param count Number of sub-elements * @param type RTCP Packet type * @param length Packet length in words * * @return 0 for success, otherwise errorcode */ int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type, uint16_t length) { int err; if (!mb) return EINVAL; err = mbuf_write_u8(mb, RTCP_VERSION<<6 | count); err |= mbuf_write_u8(mb, type); err |= mbuf_write_u16(mb, htons(length)); return err; } /** * Decode the RTCP Header * * @param mb Buffer to decode from * @param hdr RTCP Header to decode into * * @return 0 for success, otherwise errorcode */ int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr) { uint8_t b; if (!hdr) return EINVAL; if (mbuf_get_left(mb) < RTCP_HDR_SIZE) return EBADMSG; b = mbuf_read_u8(mb); hdr->pt = mbuf_read_u8(mb); hdr->length = ntohs(mbuf_read_u16(mb)); hdr->version = (b >> 6) & 0x3; hdr->p = (b >> 5) & 0x1; hdr->count = (b >> 0) & 0x1f; return 0; } int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count, va_list ap) { size_t i, pos; uint16_t len; const uint8_t *data; size_t data_len; const uint32_t *srcv; const char *reason; rtcp_encode_h *ench; void *arg; int err = 0; if (!mb) return EINVAL; pos = mb->pos; /* Skip header - encoded last */ mb->pos = mb->end = (mb->pos + RTCP_HDR_SIZE); switch (type) { case RTCP_SR: for (i=0; i<6; i++) err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t))); ench = va_arg(ap, rtcp_encode_h *); arg = va_arg(ap, void *); if (ench) err |= ench(mb, arg); break; case RTCP_RR: err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t))); ench = va_arg(ap, rtcp_encode_h *); arg = va_arg(ap, void *); if (ench) err |= ench(mb, arg); break; case RTCP_SDES: ench = va_arg(ap, rtcp_encode_h *); arg = va_arg(ap, void *); if (ench) err |= ench(mb, arg); break; case RTCP_BYE: srcv = va_arg(ap, uint32_t *); reason = va_arg(ap, char *); for (i=0; iend - pos) & 0x3) err |= mbuf_write_u8(mb, 0x00); if (err) return err; /* Encode RTCP Header */ mb->pos = pos; len = (uint16_t)((mb->end - pos - RTCP_HDR_SIZE)/sizeof(uint32_t)); err = rtcp_hdr_encode(mb, count, type, len); if (err) return err; mb->pos = mb->end; return 0; } /** * Encode an RTCP Packet into a buffer * * @param mb Buffer to encode into * @param type RTCP Packet type * @param count Packet-specific count * @param ... Variable arguments, type specific * * Variable arguments for each RTCP type: * * \verbatim SR SSRC of sender NTP Timestamp (MSW) NTP Timestamp (LSW) RTP Timestamp Sender packet count Sender octet count Encode handler for report block Handler argument RR SSRC of sender Encode handler for report block Handler argument SDES Encode handler for SDES chunk Handler argument BYE SSRCs (vector) Reason string (optional) APP SSRC/CSRC name (ASCII) Data Data length FIR SSRC NACK SSRC FSN BLP RTPFB SSRC packet SSRC media Encode handler Handler argument PSFB SSRC packet SSRC media Encode handler Handler argument \endverbatim * * @return 0 for success, otherwise errorcode */ int rtcp_encode(struct mbuf *mb, enum rtcp_type type, uint32_t count, ...) { va_list ap; int err; va_start(ap, count); err = rtcp_vencode(mb, type, count, ap); va_end(ap); return err; } /** * Decode one RTCP message from a buffer * * @param msgp Pointer to allocated RTCP Message * @param mb Buffer to decode from * * @return 0 for success, otherwise errorcode */ int rtcp_decode(struct rtcp_msg **msgp, struct mbuf *mb) { struct rtcp_msg *msg = NULL; size_t start, i, sz, count, rem; int err; if (!msgp) return EINVAL; if (mbuf_get_left(mb) < RTCP_HDR_SIZE) return EBADMSG; msg = mem_zalloc(sizeof(*msg), rtcp_destructor); if (!msg) return ENOMEM; start = mb->pos; /* decode and check header */ err = rtcp_hdr_decode(mb, &msg->hdr); if (err) goto out; if (msg->hdr.version != RTCP_VERSION) goto badmsg; /* check length and remaining */ rem = msg->hdr.length * sizeof(uint32_t); if (mbuf_get_left(mb) < rem) goto badmsg; count = msg->hdr.count; switch (msg->hdr.pt) { case RTCP_SR: if (mbuf_get_left(mb) < (RTCP_SRC_SIZE + RTCP_SR_SIZE)) goto badmsg; msg->r.sr.ssrc = ntohl(mbuf_read_u32(mb)); msg->r.sr.ntp_sec = ntohl(mbuf_read_u32(mb)); msg->r.sr.ntp_frac = ntohl(mbuf_read_u32(mb)); msg->r.sr.rtp_ts = ntohl(mbuf_read_u32(mb)); msg->r.sr.psent = ntohl(mbuf_read_u32(mb)); msg->r.sr.osent = ntohl(mbuf_read_u32(mb)); err = rtcp_rr_alloc(&msg->r.sr.rrv, count); if (err) goto out; for (i=0; ir.sr.rrv[i]); break; case RTCP_RR: if (mbuf_get_left(mb) < RTCP_SRC_SIZE) goto badmsg; msg->r.rr.ssrc = ntohl(mbuf_read_u32(mb)); err = rtcp_rr_alloc(&msg->r.rr.rrv, count); if (err) goto out; for (i=0; ir.rr.rrv[i]); break; case RTCP_SDES: if (count == 0) break; sz = count * sizeof(*msg->r.sdesv); msg->r.sdesv = mem_zalloc(sz, NULL); if (!msg->r.sdesv) { err = ENOMEM; goto out; } for (i=0; ihdr.count && !err; i++) err = rtcp_sdes_decode(mb, &msg->r.sdesv[i]); break; case RTCP_BYE: sz = count * sizeof(*msg->r.bye.srcv); msg->r.bye.srcv = mem_alloc(sz, NULL); if (!msg->r.bye.srcv) { err = ENOMEM; goto out; } if (mbuf_get_left(mb) < sz) goto badmsg; for (i=0; ir.bye.srcv[i] = ntohl(mbuf_read_u32(mb)); /* decode reason (optional) */ if (rem > count*sizeof(uint32_t)) { const size_t len = mbuf_read_u8(mb); if (mbuf_get_left(mb) < len) goto badmsg; err = mbuf_strdup(mb, &msg->r.bye.reason, len); } break; case RTCP_APP: if (mbuf_get_left(mb) < RTCP_APP_SIZE) goto badmsg; msg->r.app.src = ntohl(mbuf_read_u32(mb)); (void)mbuf_read_mem(mb, (uint8_t *)msg->r.app.name, sizeof(msg->r.app.name)); if (rem > RTCP_APP_SIZE) { msg->r.app.data_len = rem - RTCP_APP_SIZE; msg->r.app.data = mem_alloc(msg->r.app.data_len, NULL); if (!msg->r.app.data) { err = ENOMEM; goto out; } if (mbuf_get_left(mb) < msg->r.app.data_len) goto badmsg; (void)mbuf_read_mem(mb, msg->r.app.data, msg->r.app.data_len); } break; case RTCP_FIR: if (mbuf_get_left(mb) < RTCP_FIR_SIZE) goto badmsg; msg->r.fir.ssrc = ntohl(mbuf_read_u32(mb)); break; case RTCP_NACK: if (mbuf_get_left(mb) < RTCP_NACK_SIZE) goto badmsg; msg->r.nack.ssrc = ntohl(mbuf_read_u32(mb)); msg->r.nack.fsn = ntohs(mbuf_read_u16(mb)); msg->r.nack.blp = ntohs(mbuf_read_u16(mb)); break; case RTCP_RTPFB: if (mbuf_get_left(mb) < RTCP_FB_SIZE) goto badmsg; if (msg->hdr.length < 2) goto badmsg; msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb)); msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb)); msg->r.fb.n = msg->hdr.length - 2; err = rtcp_rtpfb_decode(mb, msg); break; case RTCP_PSFB: if (mbuf_get_left(mb) < RTCP_FB_SIZE) goto badmsg; if (msg->hdr.length < 2) goto badmsg; msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb)); msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb)); msg->r.fb.n = msg->hdr.length - 2; err = rtcp_psfb_decode(mb, msg); break; case RTCP_XR: if (mbuf_get_left(mb) < RTCP_HEADROOM) goto badmsg; msg->r.xr.ssrc = ntohl(mbuf_read_u32(mb)); msg->r.xr.bt = mbuf_read_u8(mb); /* reserve */ mbuf_read_u8(mb); msg->r.xr.block_len = ntohs(mbuf_read_u16(mb)); if (msg->r.xr.bt == RTCP_XR_RRTR) { if (msg->r.xr.block_len != 2) goto badmsg; msg->r.xr.rb.rrtrb.ntp_msw = ntohl(mbuf_read_u32(mb)); msg->r.xr.rb.rrtrb.ntp_lsw = ntohl(mbuf_read_u32(mb)); } else if (msg->r.xr.bt == RTCP_XR_DLRR) { if (msg->r.xr.block_len != 3) goto badmsg; msg->r.xr.rb.dlrrb.ssrc = ntohl(mbuf_read_u32(mb)); msg->r.xr.rb.dlrrb.lrr = ntohl(mbuf_read_u32(mb)); msg->r.xr.rb.dlrrb.dlrr = ntohl(mbuf_read_u32(mb)); } break; default: /* unknown message type */ mbuf_advance(mb, rem); break; } if (err) goto out; /* slurp padding */ while ((mb->pos - start) & 0x3 && mbuf_get_left(mb)) ++mb->pos; out: if (err) mem_deref(msg); else *msgp = msg; return err; badmsg: mem_deref(msg); return EBADMSG; } ================================================ FILE: src/rtp/rr.c ================================================ /** * @file rtp/rr.c RTCP Reception report * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "rtcp.h" int rtcp_rr_alloc(struct rtcp_rr **rrp, size_t count) { struct rtcp_rr *rr; if (!rrp) return EINVAL; rr = mem_alloc(count * sizeof(*rr), NULL); if (!rr) return ENOMEM; *rrp = rr; return 0; } int rtcp_rr_encode(struct mbuf *mb, const struct rtcp_rr *rr) { int err; if (!mb || !rr) return EINVAL; err = mbuf_write_u32(mb, htonl(rr->ssrc)); err |= mbuf_write_u32(mb, htonl((uint32_t)rr->fraction<<24 | (rr->lost & 0x00ffffff))); err |= mbuf_write_u32(mb, htonl(rr->last_seq)); err |= mbuf_write_u32(mb, htonl(rr->jitter)); err |= mbuf_write_u32(mb, htonl(rr->lsr)); err |= mbuf_write_u32(mb, htonl(rr->dlsr)); return err; } int rtcp_rr_decode(struct mbuf *mb, struct rtcp_rr *rr) { uint32_t w; if (!rr) return EINVAL; if (mbuf_get_left(mb) < RTCP_RR_SIZE) return EBADMSG; rr->ssrc = ntohl(mbuf_read_u32(mb)); w = ntohl(mbuf_read_u32(mb)); rr->fraction = w>>24; rr->lost = w & 0x00ffffffU; rr->last_seq = ntohl(mbuf_read_u32(mb)); rr->jitter = ntohl(mbuf_read_u32(mb)); rr->lsr = ntohl(mbuf_read_u32(mb)); rr->dlsr = ntohl(mbuf_read_u32(mb)); return 0; } ================================================ FILE: src/rtp/rtcp.c ================================================ /** * @file rtcp.c Real-time Transport Control Protocol * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "rtcp.h" static int rtcp_quick_send(struct rtp_sock *rs, enum rtcp_type type, uint32_t count, ...) { struct mbuf *mb; va_list ap; int err; mb = mbuf_alloc(32); if (!mb) return ENOMEM; mb->pos = RTCP_HEADROOM; err = rtcp_make_sr(rs, mb); err |= rtcp_make_sdes_cname(rs, mb); if (err) goto out; va_start(ap, count); err = rtcp_vencode(mb, type, count, ap); va_end(ap); mb->pos = RTCP_HEADROOM; if (!err) err = rtcp_send(rs, mb); if (!err) rtcp_schedule_report(rs); out: mem_deref(mb); return err; } /** * Send an RTCP Application-Defined (APP) packet * * @param rs RTP Socket * @param name Ascii name (4 octets) * @param data Application-dependent data * @param len Number of bytes of data * * @return 0 for success, otherwise errorcode */ int rtcp_send_app(struct rtp_sock *rs, const char name[4], const uint8_t *data, size_t len) { return rtcp_quick_send(rs, RTCP_APP, 0, rtp_sess_ssrc(rs), name, data, len); } /** * Send a Full INTRA-frame Request (FIR) packet * * @param rs RTP Socket * @param ssrc Synchronization source identifier for the sender of this packet * * @return 0 for success, otherwise errorcode */ int rtcp_send_fir(struct rtp_sock *rs, uint32_t ssrc) { return rtcp_quick_send(rs, RTCP_FIR, 0, ssrc); } /** * Send an RTCP NACK packet * * @param rs RTP Socket * @param fsn First Sequence Number lost * @param blp Bitmask of lost packets * * @return 0 for success, otherwise errorcode */ int rtcp_send_nack(struct rtp_sock *rs, uint16_t fsn, uint16_t blp) { return rtcp_quick_send(rs, RTCP_NACK, 0, rtp_sess_ssrc(rs), fsn, blp); } static int encode_gnack(struct mbuf *mb, void *arg) { struct gnack *fci = arg; return rtcp_rtpfb_gnack_encode(mb, fci->pid, fci->blp); } /** * Send an RTCP Generic NACK packet (RFC 4585 6.2.1) * * @param rs RTP Socket * @param ssrc SSRC of the target encoder * @param fsn First Sequence Number lost * @param blp Bitmask of lost packets * * @return 0 for success, otherwise errorcode */ int rtcp_send_gnack(struct rtp_sock *rs, uint32_t ssrc, uint16_t fsn, uint16_t blp) { struct gnack fci = {fsn, blp}; return rtcp_quick_send(rs, RTCP_RTPFB, RTCP_RTPFB_GNACK, rtp_sess_ssrc(rs), ssrc, &encode_gnack, &fci); } static int encode_twcc(struct mbuf *mb, void *arg) { struct twcc *twcc = arg; return rtcp_rtpfb_twcc_encode(mb, twcc); } /** * Send an RTCP Transport-wide congestion control Feedback Message * * @param rs RTP Socket * @param ssrc SSRC of the target encoder * @param twcc Transport-wide CC message * * @return 0 for success, otherwise errorcode */ int rtcp_send_twcc(struct rtp_sock *rs, uint32_t ssrc, struct twcc *twcc) { return rtcp_quick_send(rs, RTCP_RTPFB, RTCP_RTPFB_TWCC, rtp_sess_ssrc(rs), ssrc, &encode_twcc, twcc); } /** * Send an RTCP Picture Loss Indication (PLI) packet * * @param rs RTP Socket * @param fb_ssrc Feedback SSRC * * @return 0 for success, otherwise errorcode */ int rtcp_send_pli(struct rtp_sock *rs, uint32_t fb_ssrc) { return rtcp_quick_send(rs, RTCP_PSFB, RTCP_PSFB_PLI, rtp_sess_ssrc(rs), fb_ssrc, NULL, NULL); } static int encode_fir_rfc5104_fci(struct mbuf *mb, void *arg) { struct fir_rfc5104 *fci = arg; int err = mbuf_write_u32(mb, htonl(fci->ssrc)); err |= mbuf_write_u8(mb, fci->seq_n); err |= mbuf_write_u8(mb, 0); err |= mbuf_write_u8(mb, 0); err |= mbuf_write_u8(mb, 0); return err; } /** * Send an RTCP Full INTRA-frame Request (FIR) packet according to RFC 5104 * * @param rs RTP Socket * @param ssrc SSRC of the target encoder * @param fir_seqn FIR sequence number * * @return 0 for success, otherwise errorcode */ int rtcp_send_fir_rfc5104(struct rtp_sock *rs, uint32_t ssrc, uint8_t fir_seqn) { struct fir_rfc5104 fci = { ssrc, fir_seqn }; return rtcp_quick_send(rs, RTCP_PSFB, RTCP_PSFB_FIR, rtp_sess_ssrc(rs), (uint32_t)0u, &encode_fir_rfc5104_fci, &fci); } /** * Get the name of an RTCP type * * @param type RTCP type * * @return String with RTCP name */ const char *rtcp_type_name(enum rtcp_type type) { switch (type) { case RTCP_FIR: return "FIR"; case RTCP_NACK: return "NACK"; case RTCP_SR: return "SR"; case RTCP_RR: return "RR"; case RTCP_SDES: return "SDES"; case RTCP_BYE: return "BYE"; case RTCP_APP: return "APP"; case RTCP_RTPFB: return "RTPFB"; case RTCP_PSFB: return "PSFB"; case RTCP_XR: return "XR"; case RTCP_AVB: return "AVB"; default: return "?"; } } /** * Get the name of an RTCP SDES type * * @param sdes RTCP SDES type * * @return String with RTCP SDES name */ const char *rtcp_sdes_name(enum rtcp_sdes_type sdes) { switch (sdes) { case RTCP_SDES_END: return "END"; case RTCP_SDES_CNAME: return "CNAME"; case RTCP_SDES_NAME: return "NAME"; case RTCP_SDES_EMAIL: return "EMAIL"; case RTCP_SDES_PHONE: return "PHONE"; case RTCP_SDES_LOC: return "LOC"; case RTCP_SDES_TOOL: return "TOOL"; case RTCP_SDES_NOTE: return "NOTE"; case RTCP_SDES_PRIV: return "PRIV"; default: return "?"; } } /** * Print an RTCP Message * * @param pf Print handler for debug output * @param msg RTCP Message * * @return 0 if success, otherwise errorcode */ int rtcp_msg_print(struct re_printf *pf, const struct rtcp_msg *msg) { size_t i, j; int err; if (!msg) return 0; err = re_hprintf(pf, "%8s pad=%d count=%-2d pt=%-3d len=%u ", rtcp_type_name((enum rtcp_type)msg->hdr.pt), msg->hdr.p, msg->hdr.count, msg->hdr.pt, msg->hdr.length); if (err) return err; switch (msg->hdr.pt) { case RTCP_SR: err = re_hprintf(pf, "%08x %u %u %u %u %u", msg->r.sr.ssrc, msg->r.sr.ntp_sec, msg->r.sr.ntp_frac, msg->r.sr.rtp_ts, msg->r.sr.psent, msg->r.sr.osent); for (i=0; ihdr.count && !err; i++) { const struct rtcp_rr *rr = &msg->r.sr.rrv[i]; err = re_hprintf(pf, " {%08x %u %d %u %u %u %u}", rr->ssrc, rr->fraction, rr->lost, rr->last_seq, rr->jitter, rr->lsr, rr->dlsr); } break; case RTCP_RR: err = re_hprintf(pf, "%08x", msg->r.rr.ssrc); for (i=0; ihdr.count && !err; i++) { const struct rtcp_rr *rr = &msg->r.rr.rrv[i]; err = re_hprintf(pf, " {0x%08x %u %d %u %u %u %u}", rr->ssrc, rr->fraction, rr->lost, rr->last_seq, rr->jitter, rr->lsr, rr->dlsr); } break; case RTCP_SDES: for (i=0; ihdr.count; i++) { const struct rtcp_sdes *sdes = &msg->r.sdesv[i]; err = re_hprintf(pf, " {0x%08x n=%u", sdes->src, sdes->n); for (j=0; jn && !err; j++) { const struct rtcp_sdes_item *item; item = &sdes->itemv[j]; err = re_hprintf(pf, " <%s:%b>", rtcp_sdes_name(item->type), item->data, (size_t)item->length); } err |= re_hprintf(pf, "}"); } break; case RTCP_BYE: err = re_hprintf(pf, "%u srcs:", msg->hdr.count); for (i=0; ihdr.count && !err; i++) { err = re_hprintf(pf, " %08x", msg->r.bye.srcv[i]); } err |= re_hprintf(pf, " '%s'", msg->r.bye.reason); break; case RTCP_APP: err = re_hprintf(pf, "src=%08x '%b' data=%zu", msg->r.app.src, msg->r.app.name, sizeof(msg->r.app.name), msg->r.app.data_len); break; case RTCP_FIR: err = re_hprintf(pf, "ssrc=%08x", msg->r.fir.ssrc); break; case RTCP_NACK: err = re_hprintf(pf, "ssrc=%08x fsn=%04x blp=%04x", msg->r.nack.ssrc, msg->r.nack.fsn, msg->r.nack.blp); break; case RTCP_RTPFB: err = re_hprintf(pf, "pkt=%08x med=%08x n=%u", msg->r.fb.ssrc_packet, msg->r.fb.ssrc_media, msg->r.fb.n); if (msg->hdr.count == RTCP_RTPFB_GNACK) { err |= re_hprintf(pf, " GNACK"); for (i=0; ir.fb.n; i++) { err |= re_hprintf(pf, " {%04x %04x}", msg->r.fb.fci.gnackv[i].pid, msg->r.fb.fci.gnackv[i].blp); } } else if (msg->hdr.count == RTCP_RTPFB_TWCC) { const struct twcc *twcc = msg->r.fb.fci.twccv; err |= re_hprintf(pf, " TWCC" " base_seq=%u" " pkt_status_count=%u" " ref_time=%u" " fb_pkt_count=%u" , twcc->seq, twcc->count, twcc->reftime, twcc->fbcount); } break; case RTCP_PSFB: err = re_hprintf(pf, "pkt=%08x med=%08x n=%u", msg->r.fb.ssrc_packet, msg->r.fb.ssrc_media, msg->r.fb.n); if (msg->hdr.count == RTCP_PSFB_SLI) { err |= re_hprintf(pf, " SLI"); for (i=0; ir.fb.n; i++) { err |= re_hprintf(pf, " {%04x %04x %02x}", msg->r.fb.fci.sliv[i].first, msg->r.fb.fci.sliv[i].number, msg->r.fb.fci.sliv[i].picid); } } else if (msg->hdr.count == RTCP_PSFB_AFB) { err |= re_hprintf(pf, " AFB %u bytes", msg->r.fb.n * 4); } else if (msg->hdr.count == RTCP_PSFB_FIR) { err |= re_hprintf(pf, " FIR (RFC5104)"); for (i=0; ir.fb.n; i++) { err |= re_hprintf(pf, " {ssrc=%08x seq_n=%02x}", msg->r.fb.fci.firv[i].ssrc, msg->r.fb.fci.firv[i].seq_n); } } break; default: err = re_hprintf(pf, "", msg->hdr.length); break; } err |= re_hprintf(pf, "\n"); return err; } /** * Check if packet is RTCP packet, used for de-multiplexing * * @param mb Mbuffer with packet * * @return True if RTCP packet, otherwise false */ bool rtp_is_rtcp_packet(const struct mbuf *mb) { uint8_t pt; if (mbuf_get_left(mb) < 2) return false; pt = mbuf_buf(mb)[1] & 0x7f; return rtp_pt_is_rtcp(pt); } ================================================ FILE: src/rtp/rtcp.h ================================================ /** * @file rtcp.h Internal interface to RTCP * * Copyright (C) 2010 Creytiv.com */ /** RTCP protocol values */ enum { RTCP_HDR_SIZE = 4, /**< Size of common RTCP header */ RTCP_SRC_SIZE = 4, /**< Size of Source field */ RTCP_SR_SIZE = 20, /**< Size of Sender Information */ RTCP_RR_SIZE = 24, /**< Size of Report Block */ RTCP_APP_SIZE = 8, /**< Size of Application packet */ RTCP_FIR_SIZE = 4, /**< Size of FIR packet */ RTCP_NACK_SIZE = 8, /**< Size of NACK packet */ RTCP_FB_SIZE = 8, /**< Size of Feedback packets */ RTCP_MAX_SDES = 255, /**< Maximum text length for SDES */ RTCP_HEADROOM = 4, /**< Headroom in RTCP packets */ }; struct hash; /** RTP Member */ struct rtp_member { struct le le; /**< Hash-table element */ struct rtp_source *s; /**< RTP source state */ uint32_t src; /**< Source - used for hash-table lookup */ int cum_lost; /**< Cumulative number of packets lost */ uint32_t jit; /**< Jitter in [us] */ uint32_t rtt; /**< Round-trip time in [us] */ }; /* Member */ struct rtp_member *rtp_member_add(struct hash *ht, uint32_t src); struct rtp_member *rtp_member_find(struct hash *ht, uint32_t src); /* RR (Reception report) */ int rtcp_rr_alloc(struct rtcp_rr **rrp, size_t count); int rtcp_rr_encode(struct mbuf *mb, const struct rtcp_rr *rr); int rtcp_rr_decode(struct mbuf *mb, struct rtcp_rr *rr); /* SR (Sender report) */ int rtcp_make_sr(const struct rtp_sock *rs, struct mbuf *mb); /* SDES (Source Description) */ int rtcp_sdes_decode(struct mbuf *mb, struct rtcp_sdes *sdes); int rtcp_make_sdes_cname(const struct rtp_sock *rs, struct mbuf *mb); /* RTCP Feedback */ int rtcp_rtpfb_gnack_encode(struct mbuf *mb, uint16_t pid, uint16_t blp); int rtcp_rtpfb_twcc_encode(struct mbuf *mb, struct twcc *twcc); int rtcp_rtpfb_twcc_decode(struct mbuf *mb, struct twcc *msg, int n); int rtcp_rtpfb_decode(struct mbuf *mb, struct rtcp_msg *msg); int rtcp_psfb_decode(struct mbuf *mb, struct rtcp_msg *msg); /** NTP Time */ struct timeval; void unix2ntp(struct rtp_ntp_time *ntp, const struct timeval *tv); void ntp_time_get(struct rtp_ntp_time *ntp, uint64_t* jfs_rt); uint32_t ntp_compact(const struct rtp_ntp_time *ntp); uint64_t ntp_compact2us(uint32_t ntpc); /* RTP Socket */ struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs); /* RTCP message */ typedef int (rtcp_encode_h)(struct mbuf *mb, void *arg); int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type, uint16_t length); int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr); int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count, va_list ap); /* RTCP Session */ struct rtcp_sess; int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs); int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname); int rtcp_send(struct rtp_sock *rs, struct mbuf *mb); void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg); void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts, uint64_t jfs_rt, size_t payload_size); void rtcp_sess_rx_rtp(struct rtcp_sess *sess, struct rtp_header *hdr, size_t payload_size, const struct sa *peer); void rtcp_schedule_report(const struct rtp_sock *rs); ================================================ FILE: src/rtp/rtp.c ================================================ /** * @file rtp.c Real-time Transport Protocol * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "rtcp.h" #define DEBUG_MODULE "rtp" #define DEBUG_LEVEL 5 #include /** Defines an RTP Socket */ struct rtp_sock { /** Encode data */ struct { uint16_t seq; /**< Sequence number */ uint32_t ssrc; /**< Synchronizing source */ } enc; void *sock_rtp; /**< RTP Socket */ void *sock_rtcp; /**< RTCP Socket */ struct sa local; /**< Local RTP Address */ struct sa rtcp_peer; /**< RTCP address of Peer */ rtp_recv_h *recvh; /**< RTP Receive handler */ rtcp_recv_h *rtcph; /**< RTCP Receive handler */ void *arg; /**< Handler argument */ struct rtcp_sess *rtcp; /**< RTCP Session */ RE_ATOMIC bool rtcp_mux; /**< RTP/RTCP multiplexing */ }; /** * Encode the RTP header into a buffer * * @param mb Buffer to encode into * @param hdr RTP Header to be encoded * * @return 0 if success, otherwise errorcode */ int rtp_hdr_encode(struct mbuf *mb, const struct rtp_header *hdr) { uint8_t buf[2]; int err, i; if (!mb || !hdr) return EINVAL; buf[0] = (hdr->ver & 0x02) << 6; buf[0] |= (hdr->pad & 0x01) << 5; buf[0] |= (hdr->ext & 0x01) << 4; buf[0] |= (hdr->cc & 0x0f) << 0; buf[1] = (hdr->m & 0x01) << 7; buf[1] |= (hdr->pt & 0x7f) << 0; err = mbuf_write_mem(mb, buf, sizeof(buf)); err |= mbuf_write_u16(mb, htons(hdr->seq)); err |= mbuf_write_u32(mb, htonl(hdr->ts)); err |= mbuf_write_u32(mb, htonl(hdr->ssrc)); for (i=0; icc; i++) { err |= mbuf_write_u32(mb, htonl(hdr->csrc[i])); } return err; } /** * Decode an RTP header from a buffer * * @param hdr RTP Header to decode into * @param mb Buffer to decode from * * @return 0 if success, otherwise errorcode */ int rtp_hdr_decode(struct rtp_header *hdr, struct mbuf *mb) { uint8_t buf[2]; int err, i; size_t header_len; if (!hdr || !mb) return EINVAL; if (mbuf_get_left(mb) < RTP_HEADER_SIZE) return EBADMSG; err = mbuf_read_mem(mb, buf, sizeof(buf)); if (err) return err; hdr->ver = (buf[0] >> 6) & 0x03; hdr->pad = (buf[0] >> 5) & 0x01; hdr->ext = (buf[0] >> 4) & 0x01; hdr->cc = (buf[0] >> 0) & 0x0f; hdr->m = (buf[1] >> 7) & 0x01; hdr->pt = (buf[1] >> 0) & 0x7f; hdr->seq = ntohs(mbuf_read_u16(mb)); hdr->ts = ntohl(mbuf_read_u32(mb)); hdr->ssrc = ntohl(mbuf_read_u32(mb)); header_len = hdr->cc*sizeof(uint32_t); if (mbuf_get_left(mb) < header_len) return EBADMSG; for (i=0; icc; i++) { hdr->csrc[i] = ntohl(mbuf_read_u32(mb)); } if (hdr->ext) { if (mbuf_get_left(mb) < 4) return EBADMSG; hdr->x.type = ntohs(mbuf_read_u16(mb)); hdr->x.len = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < hdr->x.len*sizeof(uint32_t)) return EBADMSG; mb->pos += hdr->x.len*sizeof(uint32_t); } return 0; } static void destructor(void *data) { struct rtp_sock *rs = data; udp_handler_set(rs->sock_rtp, NULL, NULL); udp_handler_set(rs->sock_rtcp, NULL, NULL); /* Destroy RTCP Session now */ mem_deref(rs->rtcp); mem_deref(rs->sock_rtp); mem_deref(rs->sock_rtcp); } static void rtcp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { struct rtp_sock *rs = arg; struct rtcp_msg *msg; #ifdef RE_RTP_PCAP re_text2pcap_trace("rtcp_recv", "RTCP", true, mb); #endif while (0 == rtcp_decode(&msg, mb)) { /* handle internally first */ rtcp_handler(rs->rtcp, msg); /* then relay to application */ if (rs->rtcph) rs->rtcph(src, msg, rs->arg); mem_deref(msg); } } static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { struct rtp_sock *rs = arg; struct rtp_header hdr; int err; /* Handle RTCP multiplexed on RTP-port */ if (re_atomic_rlx(&rs->rtcp_mux)) { uint8_t pt; if (mbuf_get_left(mb) < 2) return; pt = mbuf_buf(mb)[1] & 0x7f; if (rtp_pt_is_rtcp(pt)) { rtcp_recv_handler(src, mb, arg); return; } } #ifdef RE_RTP_PCAP re_text2pcap_trace("rtp_udp_recv", "RTP", true, mb); #endif err = rtp_decode(rs, mb, &hdr); if (err) return; if (rs->rtcp) rtcp_sess_rx_rtp(rs->rtcp, &hdr, mbuf_get_left(mb), src); if (rs->recvh) rs->recvh(src, &hdr, mb, rs->arg); } static int udp_range_listen(struct rtp_sock *rs, const struct sa *ip, uint16_t min_port, uint16_t max_port) { struct sa rtcp; int tries = 64; int err = 0; rs->local = rtcp = *ip; /* try hard */ while (tries--) { struct udp_sock *us_rtp, *us_rtcp; uint16_t port; port = (min_port + (rand_u16() % (max_port - min_port))); port &= 0xfffe; sa_set_port(&rs->local, port); err = udp_listen(&us_rtp, &rs->local, udp_recv_handler, rs); if (err) continue; sa_set_port(&rtcp, port + 1); err = udp_listen(&us_rtcp, &rtcp, rtcp_recv_handler, rs); if (err) { mem_deref(us_rtp); continue; } /* OK */ rs->sock_rtp = us_rtp; rs->sock_rtcp = us_rtcp; break; } return err; } /** * Allocate a new RTP socket * * @param rsp Pointer to returned RTP socket * * @return 0 for success, otherwise errorcode */ int rtp_alloc(struct rtp_sock **rsp) { struct rtp_sock *rs; if (!rsp) return EINVAL; rs = mem_zalloc(sizeof(*rs), destructor); if (!rs) return ENOMEM; sa_init(&rs->rtcp_peer, AF_UNSPEC); rs->enc.seq = rand_u16() & 0x7fff; rs->enc.ssrc = rand_u32(); *rsp = rs; return 0; } /** * Listen on an RTP/RTCP Socket * * @param rsp Pointer to returned RTP socket * @param proto Transport protocol * @param ip Local IP address * @param min_port Minimum port range * @param max_port Maximum port range * @param enable_rtcp True to enable RTCP Session * @param recvh RTP Receive handler * @param rtcph RTCP Receive handler * @param arg Handler argument * * @return 0 for success, otherwise errorcode */ int rtp_listen(struct rtp_sock **rsp, int proto, const struct sa *ip, uint16_t min_port, uint16_t max_port, bool enable_rtcp, rtp_recv_h *recvh, rtcp_recv_h *rtcph, void *arg) { struct rtp_sock *rs; int err; if (!ip || min_port >= max_port || !recvh) return EINVAL; err = rtp_alloc(&rs); if (err) return err; rs->recvh = recvh; rs->rtcph = rtcph; rs->arg = arg; /* Optional RTCP */ if (enable_rtcp) { err = rtcp_sess_alloc(&rs->rtcp, rs); if (err) goto out; } switch (proto) { case IPPROTO_UDP: err = udp_range_listen(rs, ip, min_port, max_port); break; default: err = EPROTONOSUPPORT; break; } out: if (err) mem_deref(rs); else *rsp = rs; return err; } /** * Listen on a single port for RTP (no RTCP). * * Opens a UDP socket and tries to bind to the given IP and port. * * @param rsp Pointer to returned RTP socket * @param ip Local IP address * @param port Local port * @param recvh RTP Receive handler * @param arg Handler argument * @return 0 for success, otherwise errorcode */ int rtp_listen_single(struct rtp_sock **rsp, const struct sa *ip, uint16_t port, rtp_recv_h *recvh, void *arg) { struct rtp_sock *rs; int err; if (!ip || !recvh) return EINVAL; err = rtp_alloc(&rs); if (err) return err; rs->recvh = recvh; rs->arg = arg; rs->local = *ip; sa_set_port(&rs->local, port); struct udp_sock *us; err = udp_listen(&us, &rs->local, udp_recv_handler, rs); if (err) goto out; rs->sock_rtp = us; out: if (err) mem_deref(rs); else *rsp = rs; return err; } /** * Open RTP Socket without bind. * * @param rsp Pointer to returned RTP socket * @param af Address family AF_INET or AF_INET6 * * @return 0 for success, otherwise errorcode */ int rtp_open(struct rtp_sock **rsp, int af) { struct rtp_sock *rs; struct udp_sock *us_rtp; int err; err = rtp_alloc(&rs); if (err) return err; us_rtp = NULL; err = udp_open(&us_rtp, af); rs->sock_rtp = us_rtp; if (err) mem_deref(rs); else *rsp = rs; return err; } /** * Encode a new RTP header with sequence into the beginning of the buffer * * @param rs RTP Socket * @param seq Sequence Number * @param ext Extension bit * @param marker Marker bit * @param pt Payload type * @param ts Timestamp * @param mb Memory buffer * * @return 0 for success, otherwise errorcode * * @note The buffer must have enough space for the RTP header */ int rtp_encode_seq(struct rtp_sock *rs, uint16_t seq, bool ext, bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb) { struct rtp_header hdr; if (!rs || pt&~0x7f || !mb) return EINVAL; hdr.ver = RTP_VERSION; hdr.pad = false; hdr.ext = ext; hdr.cc = 0; hdr.m = marker ? 1 : 0; hdr.pt = pt; hdr.seq = seq; hdr.ts = ts; hdr.ssrc = rs->enc.ssrc; return rtp_hdr_encode(mb, &hdr); } /** * Encode a new RTP header into the beginning of the buffer * * @param rs RTP Socket * @param ext Extension bit * @param marker Marker bit * @param pt Payload type * @param ts Timestamp * @param mb Memory buffer * * @return 0 for success, otherwise errorcode * * @note The buffer must have enough space for the RTP header */ int rtp_encode(struct rtp_sock *rs, bool ext, bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb) { struct rtp_header hdr; if (!rs || pt&~0x7f || !mb) return EINVAL; hdr.ver = RTP_VERSION; hdr.pad = false; hdr.ext = ext; hdr.cc = 0; hdr.m = marker ? 1 : 0; hdr.pt = pt; hdr.seq = ++rs->enc.seq; hdr.ts = ts; hdr.ssrc = rs->enc.ssrc; return rtp_hdr_encode(mb, &hdr); } /** * Decode an RTP packet and return decoded RTP header and payload * * @param rs RTP Socket * @param mb Memory buffer containing RTP packet * @param hdr RTP header (set on return) * * @return 0 for success, otherwise errorcode */ int rtp_decode(struct rtp_sock *rs, struct mbuf *mb, struct rtp_header *hdr) { int err; if (!rs || !mb || !hdr) return EINVAL; memset(hdr, 0, sizeof(*hdr)); err = rtp_hdr_decode(hdr, mb); if (err) return err; if (RTP_VERSION != hdr->ver) return EBADMSG; return 0; } /** * Send an RTP packet to a peer * * @param rs RTP Socket * @param dst Destination address * @param ext Extension bit * @param marker Marker bit * @param pt Payload type * @param ts Timestamp * @param jfs_rt Realtime time point in microseconds that correspond to @a ts * @param mb Payload buffer * * @return 0 for success, otherwise errorcode */ int rtp_send(struct rtp_sock *rs, const struct sa *dst, bool ext, bool marker, uint8_t pt, uint32_t ts, uint64_t jfs_rt, struct mbuf *mb) { size_t pos; int err; if (!rs || !mb) return EINVAL; if (mb->pos < RTP_HEADER_SIZE) { DEBUG_WARNING("rtp_send: buffer must have space for" " rtp header (pos=%u, end=%u)\n", mb->pos, mb->end); return EBADMSG; } mbuf_advance(mb, -RTP_HEADER_SIZE); pos = mb->pos; err = rtp_encode(rs, ext, marker, pt, ts, mb); if (err) return err; if (rs->rtcp) { rtcp_sess_tx_rtp(rs->rtcp, ts, jfs_rt, mbuf_get_left(mb)); } mb->pos = pos; #ifdef RE_RTP_PCAP re_text2pcap_trace("rtp_send", "RTP", false, mb); #endif return udp_send(rs->sock_rtp, dst, mb); } /** * Resend an RTP packet to a peer (no rtcp update) * * @param rs RTP Socket * @param seq Sequence Number * @param dst Destination address * @param ext Extension bit * @param marker Marker bit * @param pt Payload type * @param ts Timestamp * @param mb Payload buffer * * @return 0 for success, otherwise errorcode */ int rtp_resend(struct rtp_sock *rs, uint16_t seq, const struct sa *dst, bool ext, bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb) { size_t pos; int err; if (!rs || !mb) return EINVAL; if (mb->pos < RTP_HEADER_SIZE) { DEBUG_WARNING("rtp_resend: buffer must have space for" " rtp header (pos=%u, end=%u)\n", mb->pos, mb->end); return EBADMSG; } mbuf_advance(mb, -RTP_HEADER_SIZE); pos = mb->pos; err = rtp_encode_seq(rs, seq, ext, marker, pt, ts, mb); if (err) return err; mb->pos = pos; #ifdef RE_RTP_PCAP re_text2pcap_trace("rtp_resend", "RTP", false, mb); #endif return udp_send(rs->sock_rtp, dst, mb); } /** * Get the RTP transport socket from an RTP/RTCP Socket * * @param rs RTP Socket * * @return Transport socket for RTP */ void *rtp_sock(const struct rtp_sock *rs) { return rs ? rs->sock_rtp : NULL; } /** * Get the RTCP transport socket from an RTP/RTCP Socket * * @param rs RTP Socket * * @return Transport socket for RTCP */ void *rtcp_sock(const struct rtp_sock *rs) { return rs ? rs->sock_rtcp : NULL; } /** * Get the local RTP address for an RTP/RTCP Socket * * @param rs RTP Socket * * @return Local RTP address */ const struct sa *rtp_local(const struct rtp_sock *rs) { return rs ? &rs->local : NULL; } /** * Get the Synchronizing source for an RTP/RTCP Socket * * @param rs RTP Socket * * @return Synchronizing source */ uint32_t rtp_sess_ssrc(const struct rtp_sock *rs) { return rs ? rs->enc.ssrc : 0; } /** * Get the last Sequence number from an RTP/RTCP Socket * * @param rs RTP Socket * * @return Sequence number */ uint16_t rtp_sess_seq(const struct rtp_sock *rs) { return rs ? rs->enc.seq : 0; } /** * Get the RTCP-Session for an RTP/RTCP Socket * * @param rs RTP Socket * * @return RTCP-Session */ struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs) { return rs ? rs->rtcp : NULL; } /** * Start the RTCP Session * * @param rs RTP Socket * @param cname Canonical Name * @param peer IP-Address of RTCP Peer */ void rtcp_start(struct rtp_sock *rs, const char *cname, const struct sa *peer) { if (!rs) return; if (peer) rs->rtcp_peer = *peer; (void)rtcp_enable(rs->rtcp, peer != NULL, cname); } /** * Enable RTCP-multiplexing on RTP-port * * @param rs RTP Socket * @param enabled True to enable, false to disable */ void rtcp_enable_mux(struct rtp_sock *rs, bool enabled) { if (!rs) return; re_atomic_rlx_set(&rs->rtcp_mux, enabled); } /** * Send RTCP packet(s) to the Peer * * @param rs RTP Socket * @param mb Buffer containing the RTCP Packet(s) * * @return 0 for success, otherwise errorcode */ int rtcp_send(struct rtp_sock *rs, struct mbuf *mb) { void* sock; if (!rs) return EINVAL; sock = re_atomic_rlx(&rs->rtcp_mux) ? rs->sock_rtp : rs->sock_rtcp; if (!sock || !sa_isset(&rs->rtcp_peer, SA_ALL)) return EINVAL; #ifdef RE_RTP_PCAP re_text2pcap_trace("rtcp_send", "RTCP", false, mb); #endif return udp_send(sock, &rs->rtcp_peer, mb); } /** * Clear receive buffer of RTP Socket * * @param rs RTP Socket * * @return 0 for success, otherwise errorcode */ int rtp_clear(struct rtp_sock *rs) { if (!rs) return EINVAL; udp_flush(rs->sock_rtp); return 0; } /** * RTP Debug handler, use with fmt %H * * @param pf Print function * @param rs RTP Socket * * @return 0 if success, otherwise errorcode */ int rtp_debug(struct re_printf *pf, const struct rtp_sock *rs) { int err; if (!rs || !pf) return EINVAL; err = re_hprintf(pf, "RTP debug:\n"); err |= re_hprintf(pf, " Encode: seq=%u ssrc=0x%x\n", rs->enc.seq, rs->enc.ssrc); if (rs->rtcp) err |= rtcp_debug(pf, rs); return err; } ================================================ FILE: src/rtp/sdes.c ================================================ /** * @file sdes.c RTCP Source Description * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "rtcp.h" #define DEBUG_MODULE "rtcp_sdes" #define DEBUG_LEVEL 5 #include enum { RTCP_SDES_MIN_SIZE = 1, }; /** * Encode one SDES chunk into mbuffer * * @param mb Buffer to encode into * @param src First SSRC/CSRC * @param itemc Number of SDES items to encode * * @return 0 if success, otherwise errorcode */ int rtcp_sdes_encode(struct mbuf *mb, uint32_t src, uint32_t itemc, ...) { va_list ap; size_t start; int err = 0; if (!mb || !itemc) return EINVAL; va_start(ap, itemc); start = mb->pos; err = mbuf_write_u32(mb, htonl(src)); /* add all SDES items */ while (itemc-- && !err) { const uint8_t type = va_arg(ap, int); const char *v = va_arg(ap, const char *); size_t len; if (!v) continue; len = strlen(v); /* note: max 255 chars */ if (len > 255) { err = EINVAL; goto out; } err = mbuf_write_u8(mb, type); err |= mbuf_write_u8(mb, len & 0xff); err |= mbuf_write_mem(mb, (uint8_t *)v, len); } /* END padding */ err |= mbuf_write_u8(mb, RTCP_SDES_END); while ((mb->pos - start) & 0x3) err |= mbuf_write_u8(mb, RTCP_SDES_END); out: va_end(ap); return err; } /** * Decode SDES items from a buffer * * @param mb Buffer to decode from * @param sdes RTCP SDES to decode into * * @return 0 if success, otherwise errorcode */ int rtcp_sdes_decode(struct mbuf *mb, struct rtcp_sdes *sdes) { size_t start; if (!sdes) return EINVAL; if (mbuf_get_left(mb) < RTCP_SRC_SIZE) return EBADMSG; start = mb->pos; sdes->src = ntohl(mbuf_read_u32(mb)); /* Decode all SDES items */ while (mbuf_get_left(mb) >= RTCP_SDES_MIN_SIZE) { uint8_t type; struct rtcp_sdes_item *item; type = mbuf_read_u8(mb); if (type == RTCP_SDES_END) break; if (mbuf_get_left(mb) < 1) return EBADMSG; if (!sdes->itemv) { sdes->itemv = mem_alloc(sizeof(*sdes->itemv), NULL); if (!sdes->itemv) return ENOMEM; } else { const size_t sz = (sdes->n + 1) * sizeof(*sdes->itemv); struct rtcp_sdes_item *itemv; itemv = mem_realloc(sdes->itemv, sz); if (!itemv) return ENOMEM; sdes->itemv = itemv; } item = &sdes->itemv[sdes->n]; item->type = (enum rtcp_sdes_type)type; item->length = mbuf_read_u8(mb); if (mbuf_get_left(mb) < item->length) return EBADMSG; item->data = mem_alloc(item->length, NULL); if (!item->data) return ENOMEM; (void)mbuf_read_mem(mb, (uint8_t *)item->data, item->length); sdes->n++; } /* slurp padding */ while ((mb->pos - start) & 0x3 && mbuf_get_left(mb)) ++mb->pos; return 0; } ================================================ FILE: src/rtp/sess.c ================================================ /** * @file rtp/sess.c Real-time Transport Control Protocol Session * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_SYS_TIME_H #include #endif #include #ifdef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "rtcp.h" #define DEBUG_MODULE "rtcp_sess" #define DEBUG_LEVEL 5 #include /** RTP protocol values */ enum { RTCP_INTERVAL = 5000, /**< Interval in [ms] between sending reports */ MAX_MEMBERS = 8, }; /** RTP Transmit stats */ struct txstat { uint32_t psent; /**< Total number of RTP packets sent */ uint32_t osent; /**< Total number of RTP octets sent */ uint64_t jfs_rt_ref; /**< Realtime clock timer ticks at RTP timestamp * reference */ uint32_t ts_ref; /**< RTP timestamp reference (transmit) */ bool ts_synced; /**< RTP timestamp synchronization flag */ }; /** RTCP Session */ struct rtcp_sess { struct rtp_sock *rs; /**< RTP Socket */ struct hash *members; /**< Member table */ struct tmr tmr; /**< Event sender timer */ char *cname; /**< Canonical Name */ uint32_t memberc; /**< Number of members */ uint32_t senderc; /**< Number of senders */ uint32_t srate_tx; /**< Transmit sampling rate */ uint32_t srate_rx; /**< Receive sampling rate */ uint32_t interval; /**< RTCP interval in [ms] */ mtx_t *lock; /**< Lock for rtcp_sess */ /* stats */ struct txstat txstat; /**< Local transmit statistics */ }; /* Prototypes */ static void schedule(struct rtcp_sess *sess); static void sess_destructor(void *data) { struct rtcp_sess *sess = data; tmr_cancel(&sess->tmr); mem_deref(sess->cname); hash_flush(sess->members); mem_deref(sess->members); mem_deref(sess->lock); } static struct rtp_member *get_member(struct rtcp_sess *sess, uint32_t src) { struct rtp_member *mbr; mbr = rtp_member_find(sess->members, src); if (mbr) return mbr; if (sess->memberc >= MAX_MEMBERS) return NULL; mbr = rtp_member_add(sess->members, src); if (!mbr) return NULL; ++sess->memberc; return mbr; } /** * Calculate Round-Trip Time in [microseconds] * * @param rtt Calculated Rount-Trip time [us] * @param lsr Last SR packet from this source [compact NTP timestamp] * @param dlsr Delay since last SR packet [units of 1/65536 seconds] */ void rtcp_calc_rtt(uint32_t *rtt, uint32_t lsr, uint32_t dlsr) { struct rtp_ntp_time ntp_time; uint64_t a_us, lsr_us, dlsr_us; ntp_time_get(&ntp_time, NULL); a_us = ntp_compact2us(ntp_compact(&ntp_time)); lsr_us = ntp_compact2us(lsr); dlsr_us = 1000000ULL * dlsr / 65536; /* RTT delay is (A - LSR - DLSR) */ if (rtt) *rtt = MAX((int)(a_us - lsr_us - dlsr_us), 0); } /** Decode Reception Report block */ static void handle_rr_block(struct rtcp_sess *sess, struct rtp_member *mbr, const struct rtcp_rr *rr) { /* Lost */ mbr->cum_lost = rr->lost; /* Interarrival jitter */ if (sess->srate_tx) mbr->jit = 1000000 * rr->jitter / sess->srate_tx; /* round-trip propagation delay as (A - LSR - DLSR) */ if (rr->lsr && rr->dlsr) rtcp_calc_rtt(&mbr->rtt, rr->lsr, rr->dlsr); } /** Handle incoming RR (Receiver Report) packet */ static void handle_incoming_rr(struct rtcp_sess *sess, const struct rtcp_msg *msg) { struct rtp_member *mbr; uint32_t i; mbr = get_member(sess, msg->r.rr.ssrc); if (!mbr) return; for (i=0; ihdr.count; i++) handle_rr_block(sess, mbr, &msg->r.rr.rrv[i]); } /** Handle incoming SR (Sender Report) packet */ static void handle_incoming_sr(struct rtcp_sess *sess, const struct rtcp_msg *msg) { struct rtp_member *mbr; uint32_t i; mbr = get_member(sess, msg->r.sr.ssrc); if (!mbr) { DEBUG_WARNING("0x%08x: could not add member\n", msg->r.sr.ssrc); return; } if (mbr->s) { /* Save time when SR was received */ mbr->s->sr_recv = tmr_jiffies(); /* Save NTP timestamp from SR */ mbr->s->last_sr.hi = msg->r.sr.ntp_sec; mbr->s->last_sr.lo = msg->r.sr.ntp_frac; mbr->s->rtp_ts = msg->r.sr.rtp_ts; mbr->s->psent = msg->r.sr.psent; mbr->s->osent = msg->r.sr.osent; } for (i=0; ihdr.count; i++) handle_rr_block(sess, mbr, &msg->r.sr.rrv[i]); } static void handle_incoming_bye(struct rtcp_sess *sess, const struct rtcp_msg *msg) { uint32_t i; for (i=0; ihdr.count; i++) { struct rtp_member *mbr; mbr = rtp_member_find(sess->members, msg->r.bye.srcv[i]); if (mbr) { if (mbr->s) --sess->senderc; --sess->memberc; mem_deref(mbr); } } } void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg) { if (!sess || !msg) return; mtx_lock(sess->lock); switch (msg->hdr.pt) { case RTCP_SR: handle_incoming_sr(sess, msg); break; case RTCP_RR: handle_incoming_rr(sess, msg); break; case RTCP_BYE: handle_incoming_bye(sess, msg); break; default: break; } mtx_unlock(sess->lock); } int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs) { struct rtcp_sess *sess; int err; if (!sessp) return EINVAL; sess = mem_zalloc(sizeof(*sess), sess_destructor); if (!sess) return ENOMEM; sess->rs = rs; tmr_init(&sess->tmr); sess->interval = RTCP_INTERVAL; err = mutex_alloc(&sess->lock); if (err) goto out; err = hash_alloc(&sess->members, MAX_MEMBERS); if (err) goto out; out: if (err) mem_deref(sess); else *sessp = sess; return err; } /** * Set interval between sending reports on an RTCP Session * * @param rs RTP Socket * @param n RTCP interval in [ms] */ void rtcp_set_interval(struct rtp_sock *rs, uint32_t n) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; sess->interval = n; } /** * Set the Sampling-rate on an RTCP Session * * @param rs RTP Socket * @param srate_tx Transmit samplerate * @param srate_rx Receive samplerate */ void rtcp_set_srate(struct rtp_sock *rs, uint32_t srate_tx, uint32_t srate_rx) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; mtx_lock(sess->lock); sess->srate_tx = srate_tx; sess->srate_rx = srate_rx; mtx_unlock(sess->lock); } /** * Set the transmit Sampling-rate on an RTCP Session * * @param rs RTP Socket * @param srate_tx Transmit samplerate */ void rtcp_set_srate_tx(struct rtp_sock *rs, uint32_t srate_tx) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; mtx_lock(sess->lock); sess->srate_tx = srate_tx; mtx_unlock(sess->lock); } /** * Set the receive Sampling-rate on an RTCP Session * * @param rs RTP Socket * @param srate_rx Receive samplerate */ void rtcp_set_srate_rx(struct rtp_sock *rs, uint32_t srate_rx) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; mtx_lock(sess->lock); sess->srate_rx = srate_rx; mtx_unlock(sess->lock); } int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname) { int err; if (!sess) return EINVAL; mtx_lock(sess->lock); sess->cname = mem_deref(sess->cname); err = str_dup(&sess->cname, cname); mtx_unlock(sess->lock); if (err) return err; if (enabled) schedule(sess); else tmr_cancel(&sess->tmr); return 0; } /** Calculate LSR (middle 32 bits out of 64 in the NTP timestamp) */ static uint32_t calc_lsr(const struct rtp_ntp_time *last_sr) { return last_sr->hi ? ntp_compact(last_sr) : 0; } static uint32_t calc_dlsr(uint64_t sr_recv) { if (sr_recv) { const uint64_t diff = tmr_jiffies() - sr_recv; return (uint32_t)((65536 * diff) / 1000); } else { return 0; } } static bool sender_apply_handler(struct le *le, void *arg) { struct rtp_member *mbr = le->data; struct rtp_source *s = mbr->s; struct mbuf *mb = arg; struct rtcp_rr rr; if (!s) return false; /* Initialise the members */ rr.ssrc = mbr->src; rr.fraction = rtp_source_calc_fraction_lost(s); rr.lost = rtp_source_calc_lost(s); rr.last_seq = s->cycles | s->max_seq; rr.jitter = s->jitter >> 4; rr.lsr = calc_lsr(&s->last_sr); rr.dlsr = calc_dlsr(s->sr_recv); return 0 != rtcp_rr_encode(mb, &rr); } static int encode_handler(struct mbuf *mb, void *arg) { struct hash *members = arg; /* copy all report blocks */ if (hash_apply(members, sender_apply_handler, mb)) return ENOMEM; return 0; } /** Create a Sender Report */ static int mk_sr(struct rtcp_sess *sess, struct mbuf *mb) { struct txstat txstat; uint32_t srate_tx; int err; mtx_lock(sess->lock); txstat = sess->txstat; srate_tx = sess->srate_tx; sess->txstat.ts_synced = false; mtx_unlock(sess->lock); if (txstat.jfs_rt_ref) { struct rtp_ntp_time ntp; uint64_t jfs_rt, dur; uint32_t rtp_ts; ntp_time_get(&ntp, &jfs_rt); dur = jfs_rt - txstat.jfs_rt_ref; rtp_ts = (uint32_t)((uint64_t)txstat.ts_ref + dur * srate_tx / 1000000u); err = rtcp_encode(mb, RTCP_SR, sess->senderc, rtp_sess_ssrc(sess->rs), ntp.hi, ntp.lo, rtp_ts, txstat.psent, txstat.osent, encode_handler, sess->members); } else { /* No packets were sent yet, no NTP/RTP timestamps available, * generate receiver report */ err = rtcp_encode(mb, RTCP_RR, sess->senderc, rtp_sess_ssrc(sess->rs), encode_handler, sess->members); } return err; } int rtcp_make_sr(const struct rtp_sock *rs, struct mbuf *mb) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return EINVAL; return mk_sr(sess, mb); } static int sdes_encode_handler(struct mbuf *mb, void *arg) { struct rtcp_sess *sess = arg; int err; mtx_lock(sess->lock); err = rtcp_sdes_encode(mb, rtp_sess_ssrc(sess->rs), 1, RTCP_SDES_CNAME, sess->cname); mtx_unlock(sess->lock); return err; } static int mk_sdes(struct rtcp_sess *sess, struct mbuf *mb) { return rtcp_encode(mb, RTCP_SDES, 1, sdes_encode_handler, sess); } int rtcp_make_sdes_cname(const struct rtp_sock *rs, struct mbuf *mb) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return EINVAL; return mk_sdes(sess, mb); } static int send_rtcp_report(struct rtcp_sess *sess) { struct mbuf *mb; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; mb->pos = RTCP_HEADROOM; err = mk_sr(sess, mb); err |= mk_sdes(sess, mb); if (err) goto out; mb->pos = RTCP_HEADROOM; err = rtcp_send(sess->rs, mb); out: mem_deref(mb); return err; } int rtcp_send_bye_packet(struct rtp_sock *rs) { if (!rs) return EINVAL; struct rtcp_sess *sess = rtp_rtcp_sess(rs); const uint32_t ssrc = rtp_sess_ssrc(rs); struct mbuf *mb; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; mb->pos = RTCP_HEADROOM; err = rtcp_encode(mb, RTCP_BYE, 1, &ssrc, "Adjo"); err |= mk_sdes(sess, mb); if (err) goto out; mb->pos = RTCP_HEADROOM; err = rtcp_send(sess->rs, mb); out: mem_deref(mb); return err; } static void timeout(void *arg) { struct rtcp_sess *sess = arg; int err; err = send_rtcp_report(sess); if (err) { DEBUG_WARNING("Send RTCP report failed: %m\n", err); } schedule(sess); } static void schedule(struct rtcp_sess *sess) { tmr_start(&sess->tmr, sess->interval, timeout, sess); } void rtcp_schedule_report(const struct rtp_sock *rs) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; tmr_start(&sess->tmr, sess->interval, timeout, sess); } void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts, uint64_t jfs_rt, size_t payload_size) { if (!sess) return; mtx_lock(sess->lock); sess->txstat.osent += (uint32_t)payload_size; sess->txstat.psent += 1; if (!sess->txstat.ts_synced) { sess->txstat.jfs_rt_ref = jfs_rt; sess->txstat.ts_ref = ts; sess->txstat.ts_synced = true; } mtx_unlock(sess->lock); } void rtcp_sess_rx_rtp(struct rtcp_sess *sess, struct rtp_header *hdr, size_t payload_size, const struct sa *peer) { struct rtp_member *mbr; if (!sess) return; mtx_lock(sess->lock); mbr = get_member(sess, hdr->ssrc); if (!mbr) { DEBUG_NOTICE("could not add member: 0x%08x\n", hdr->ssrc); goto out; } if (!mbr->s) { mbr->s = mem_zalloc(sizeof(*mbr->s), NULL); if (!mbr->s) { DEBUG_NOTICE("could not add sender: 0x%08x\n", hdr->ssrc); goto out; } /* first packet - init sequence number */ rtp_source_init_seq(mbr->s, hdr->seq); /* probation not used */ sa_cpy(&mbr->s->rtp_peer, peer); ++sess->senderc; } if (!rtp_source_update_seq(mbr->s, hdr->seq)) { DEBUG_WARNING("rtp_source_update_seq() returned 0\n"); } if (sess->srate_rx) { /* Convert from wall-clock time to timestamp units */ hdr->ts_arrive = tmr_jiffies() * (sess->srate_rx / 1000); /* * Calculate jitter only when the timestamp is different than * last packet (see RTP FAQ * https://www.cs.columbia.edu/~hgs/rtp/faq.html#jitter). */ if (hdr->ts != mbr->s->last_rtp_ts) rtp_source_calc_jitter(mbr->s, hdr->ts, (uint32_t)hdr->ts_arrive); } mbr->s->last_rtp_ts = hdr->ts; mbr->s->rtp_rx_bytes += payload_size; out: mtx_unlock(sess->lock); } /** * Get the RTCP Statistics for a source * * @param rs RTP Socket * @param ssrc Synchronization source * @param stats RTCP Statistics, set on return * * @return 0 if success, otherwise errorcode */ int rtcp_stats(struct rtp_sock *rs, uint32_t ssrc, struct rtcp_stats *stats) { const struct rtcp_sess *sess = rtp_rtcp_sess(rs); struct rtp_member *mbr; int err = 0; if (!sess || !stats) return EINVAL; mtx_lock(sess->lock); mbr = rtp_member_find(sess->members, ssrc); if (!mbr) { err = ENOENT; goto out; } stats->tx.sent = sess->txstat.psent; stats->tx.lost = mbr->cum_lost; stats->tx.jit = mbr->jit; stats->rtt = mbr->rtt; if (!mbr->s) { memset(&stats->rx, 0, sizeof(stats->rx)); goto out; } stats->rx.sent = mbr->s->received; stats->rx.lost = rtp_source_calc_lost(mbr->s); stats->rx.jit = sess->srate_rx ? 1000000 * (mbr->s->jitter>>4) / sess->srate_rx : 0; out: mtx_unlock(sess->lock); return err; } static bool debug_handler(struct le *le, void *arg) { const struct rtp_member *mbr = le->data; struct mbuf *mb = arg; int err; err = mbuf_printf(mb, " member 0x%08x: lost=%d Jitter=%.1fms" " RTT=%.1fms\n", mbr->src, mbr->cum_lost, (double)mbr->jit/1000, (double)mbr->rtt/1000); if (mbr->s) { err |= mbuf_printf(mb, " IP=%J psent=%u rcvd=%u\n", &mbr->s->rtp_peer, mbr->s->psent, mbr->s->received); } return err != 0; } /** * RTCP Debug handler, use with fmt %H * * @param pf Print function * @param rs RTP Socket * * @return 0 if success, otherwise errorcode */ int rtcp_debug(struct re_printf *pf, const struct rtp_sock *rs) { const struct rtcp_sess *sess = rtp_rtcp_sess(rs); struct mbuf *mb; int err = 0; if (!sess) return 0; mb = mbuf_alloc(64); if (!mb) return ENOMEM; err |= mbuf_printf(mb, "----- RTCP Session: -----\n"); mtx_lock(sess->lock); err |= mbuf_printf(mb, " cname=%s SSRC=0x%08x/%u rx=%uHz\n", sess->cname, rtp_sess_ssrc(sess->rs), rtp_sess_ssrc(sess->rs), sess->srate_rx); hash_apply(sess->members, debug_handler, mb); err |= mbuf_printf(mb, " TX: packets=%u, octets=%u\n", sess->txstat.psent, sess->txstat.osent); mtx_unlock(sess->lock); if (err) goto out; err = re_hprintf(pf, "%b", mb->buf, mb->pos); out: mem_deref(mb); return err; } ================================================ FILE: src/rtp/source.c ================================================ /** * @file source.c Real-time Transport Control Protocol source * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "rtcp.h" enum { RTP_SEQ_MOD = 1<<16, }; void rtp_source_init_seq(struct rtp_source *s, uint16_t seq) { if (!s) return; s->base_seq = seq; s->max_seq = seq; s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */ s->cycles = 0; s->received = 0; s->received_prior = 0; s->expected_prior = 0; /* other initialization */ } /* * See RFC 3550 - A.1 RTP Data Header Validity Checks */ int rtp_source_update_seq(struct rtp_source *s, uint16_t seq) { uint16_t udelta = seq - s->max_seq; const int MAX_DROPOUT = 3000; const int MAX_MISORDER = 100; const int MIN_SEQUENTIAL = 2; /* * Source is not valid until MIN_SEQUENTIAL packets with * sequential sequence numbers have been received. */ if (s->probation) { /* packet is in sequence */ if (seq == s->max_seq + 1) { s->probation--; s->max_seq = seq; if (s->probation == 0) { rtp_source_init_seq(s, seq); s->received++; return 1; } } else { s->probation = MIN_SEQUENTIAL - 1; s->max_seq = seq; } return 0; } else if (udelta < MAX_DROPOUT) { /* in order, with permissible gap */ if (seq < s->max_seq) { /* * Sequence number wrapped - count another 64K cycle. */ s->cycles += RTP_SEQ_MOD; } s->max_seq = seq; } else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) { /* the sequence number made a very large jump */ if (seq == s->bad_seq) { /* * 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_source_init_seq(s, seq); } else { s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1); return 0; } } else { /* duplicate or reordered packet */ } s->received++; return 1; } /* RFC 3550 A.8 * * The inputs are: * * rtp_ts: the timestamp from the incoming RTP packet * arrival: the current time in the same units. */ void rtp_source_calc_jitter(struct rtp_source *s, uint32_t rtp_ts, uint32_t arrival) { const int transit = arrival - rtp_ts; int d = transit - s->transit; if (!s->transit) { s->transit = transit; return; } s->transit = transit; if (d < 0) d = -d; s->jitter += d - ((s->jitter + 8) >> 4); } /* A.3 */ int rtp_source_calc_lost(const struct rtp_source *s) { int extended_max = s->cycles + s->max_seq; int expected = extended_max - s->base_seq + 1; int lost; lost = expected - s->received; /* Clamp at 24 bits */ if (lost > 0x7fffff) lost = 0x7fffff; else if (lost < -0x7fffff) lost = -0x7fffff; return lost; } /* A.3 */ uint8_t rtp_source_calc_fraction_lost(struct rtp_source *s) { int extended_max = s->cycles + s->max_seq; int expected = extended_max - s->base_seq + 1; int expected_interval = expected - s->expected_prior; int received_interval; int lost_interval; uint8_t fraction; s->expected_prior = expected; received_interval = s->received - s->received_prior; s->received_prior = s->received; lost_interval = expected_interval - received_interval; if (expected_interval == 0 || lost_interval <= 0) fraction = 0; else fraction = (lost_interval << 8) / expected_interval; return fraction; } ================================================ FILE: src/rtpext/rtpext.c ================================================ /** * @file rtpext.c RTP Header Extensions * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include #include #include #define DEBUG_MODULE "rtpext" #define DEBUG_LEVEL 5 #include /* * RFC 8285 A General Mechanism for RTP Header Extensions * * - One-Byte Header: Supported * - Two-Byte Header: Supported * * https://datatracker.ietf.org/doc/html/rfc8285 */ /** * Encode the One-Byte header for all RTP extensions * * @param mb Buffer to encode into * @param num_bytes Total size for all RTP extensions * * @return 0 if success, otherwise errorcode */ int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes) { int err = 0; if (!mb || !num_bytes) return EINVAL; if (num_bytes & 0x3) { DEBUG_WARNING("hdr_encode: num_bytes (%zu) must be multiple" " of 4\n", num_bytes); return EINVAL; } err |= mbuf_write_u16(mb, htons(RTPEXT_TYPE_MAGIC)); err |= mbuf_write_u16(mb, htons((uint16_t)(num_bytes / 4))); return err; } /** * Encode the Two-Byte header for all RTP extensions * * @param mb Buffer to encode into * @param num_bytes Total size for all RTP extensions (multiple of 4) * * @return 0 if success, otherwise errorcode */ int rtpext_hdr_encode_long(struct mbuf *mb, size_t num_bytes) { int err = 0; if (!mb || !num_bytes) return EINVAL; if (num_bytes & 0x3) { DEBUG_WARNING("hdr_encode: num_bytes (%zu) must be multiple" " of 4\n", num_bytes); return EINVAL; } err |= mbuf_write_u16(mb, htons(RTPEXT_TYPE_MAGIC_LONG)); err |= mbuf_write_u16(mb, htons((uint16_t)(num_bytes / 4))); return err; } /** * Encode an RTP header extension with One-Byte header * * @param mb Buffer to encode into * @param id Identifier * @param len Length of data field * @param data Data bytes * * @return 0 if success, otherwise errorcode */ int rtpext_encode(struct mbuf *mb, uint8_t id, size_t len, const uint8_t *data) { size_t start; int err; if (!mb || !data) return EINVAL; if (id < RTPEXT_ID_MIN || id > RTPEXT_ID_MAX) return EINVAL; if (len < RTPEXT_LEN_MIN || len > RTPEXT_LEN_MAX) return EINVAL; start = mb->pos; err = mbuf_write_u8(mb, (uint8_t)(id << 4 | (len-1))); err |= mbuf_write_mem(mb, data, len); if (err) return err; /* padding */ while ((mb->pos - start) & 0x03) err |= mbuf_write_u8(mb, 0x00); return err; } /** * Decode an RTP header extension with One-Byte header * * @param ext RTP Extension object * @param mb Buffer to decode from * * @return 0 if success, otherwise errorcode */ int rtpext_decode(struct rtpext *ext, struct mbuf *mb) { uint8_t v; int err; if (!ext || !mb) return EINVAL; if (mbuf_get_left(mb) < 1) return EBADMSG; memset(ext, 0, sizeof(*ext)); v = mbuf_read_u8(mb); ext->id = v >> 4; ext->len = (v & 0x0f) + 1; if (ext->id < RTPEXT_ID_MIN || ext->id > RTPEXT_ID_MAX) { DEBUG_WARNING("decode: invalid ID %u\n", ext->id); return EBADMSG; } if (ext->len > mbuf_get_left(mb)) { DEBUG_WARNING("decode: short read\n"); return ENODATA; } err = mbuf_read_mem(mb, ext->data, ext->len); if (err) return err; /* skip padding */ while (mbuf_get_left(mb)) { uint8_t pad = mbuf_buf(mb)[0]; if (pad != 0x00) break; mbuf_advance(mb, 1); } return 0; } /** * Encode an RTP header extension with Two-Byte header * * @param mb Buffer to encode into * @param id Identifier * @param len Length of data field * @param data Data bytes * * @return 0 if success, otherwise errorcode */ int rtpext_encode_long(struct mbuf *mb, uint8_t id, uint8_t len, const uint8_t *data) { if (!mb) return EINVAL; int err = mbuf_write_u8(mb, id); err |= mbuf_write_u8(mb, len); if (data && len) err |= mbuf_write_mem(mb, data, len); return err; } /** * Decode an RTP header extension with Two-Byte header * * @param ext RTP Extension object * @param mb Buffer to decode from * * @return 0 if success, otherwise errorcode */ int rtpext_decode_long(struct rtpext *ext, struct mbuf *mb) { if (!ext || !mb) return EINVAL; if (mbuf_get_left(mb) < 2) return EBADMSG; memset(ext, 0, sizeof(*ext)); ext->id = mbuf_read_u8(mb); ext->len = mbuf_read_u8(mb); if (ext->id == 0) { DEBUG_WARNING("decode_long: invalid ID %u\n", ext->id); return EBADMSG; } if (ext->len > mbuf_get_left(mb)) { DEBUG_WARNING("decode_long: short read (%zu > %zu)\n", ext->len, mbuf_get_left(mb)); return ENODATA; } int err = mbuf_read_mem(mb, ext->data, ext->len); if (err) return err; /* skip padding */ while (mbuf_get_left(mb)) { uint8_t pad = mbuf_buf(mb)[0]; if (pad != 0x00) break; mbuf_advance(mb, 1); } return 0; } /** * Finds an RTP extension by its ID * * @param extv Pointer to an array of RTP extensions * @param extc Number of elements in the RTP extension array * @param id The ID of the RTP extension to find * * @return Pointer to the matching RTP extension on success, otherwise NULL */ const struct rtpext *rtpext_find(const struct rtpext *extv, size_t extc, uint8_t id) { for (size_t i = 0; i < extc; i++) { const struct rtpext *rtpext = &extv[i]; if (rtpext->id == id) return rtpext; } return NULL; } ================================================ FILE: src/sa/printaddr.c ================================================ /** * @file sa/printaddr.c Socket Address printing * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_GETIFADDRS #include #include #include #endif #include #include #include /** * Print a Socket Address including IPv6 scope identifier * * @param pf Print function * @param sa Socket Address * * @return 0 if success, otherwise errorcode */ int sa_print_addr(struct re_printf *pf, const struct sa *sa) { int err; if (!sa) return 0; err = re_hprintf(pf, "%j", sa); if (sa_af(sa) == AF_INET6 && sa_is_linklocal(sa)) { #ifdef HAVE_GETIFADDRS char ifname[IF_NAMESIZE]; if (!if_indextoname(sa->u.in6.sin6_scope_id, ifname)) return errno; err |= re_hprintf(pf, "%%%s", ifname); #else uint32_t scope_id = sa_scopeid(sa); err |= re_hprintf(pf, "%%%d", scope_id); #endif } return err; } ================================================ FILE: src/sa/sa.c ================================================ /** * @file sa.c Socket Address * * Copyright (C) 2010 Creytiv.com */ #ifndef WIN32 #include #include #endif #include #include #include #include #include #include #define DEBUG_MODULE "sa" #define DEBUG_LEVEL 5 #include /** * Initialize a Socket Address * * @param sa Socket Address * @param af Address Family */ void sa_init(struct sa *sa, int af) { if (!sa) return; memset(sa, 0, sizeof(*sa)); sa->u.sa.sa_family = af; sa->len = sizeof(sa->u); } /** * Set a Socket Address from a PL string * * @param sa Socket Address * @param addr IP-address * @param port Port number * * @return 0 if success, otherwise errorcode */ int sa_set(struct sa *sa, const struct pl *addr, uint16_t port) { char buf[64]; (void)pl_strcpy(addr, buf, sizeof(buf)); return sa_set_str(sa, buf, port); } int sa_addrinfo(const char *addr, struct sa *sa) { struct addrinfo *res, *res0 = NULL; struct addrinfo hints; int err = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST; if (getaddrinfo(addr, NULL, &hints, &res0)) return EADDRNOTAVAIL; for (res = res0; res; res = res->ai_next) { err = sa_set_sa(sa, res->ai_addr); if (err) continue; break; } freeaddrinfo(res0); return err; } /** * Convert character string to a network address structure * * @param addr IP address string * @param sa Returned socket address * * @return 0 if success, otherwise errorcode */ int sa_pton(const char *addr, struct sa *sa) { int err = 0; if (!addr || !sa) return EINVAL; memset(sa, 0, sizeof(*sa)); if (inet_pton(AF_INET, addr, &sa->u.in.sin_addr) > 0) { sa->u.in.sin_family = AF_INET; } #if HAVE_UNIXSOCK == 1 else if (!strncmp(addr, "unix:", 5)) { sa->u.un.sun_family = AF_UNIX; str_ncpy(sa->u.un.sun_path, addr + 5, sizeof(sa->u.un.sun_path)); } #endif else if (!strncmp(addr, "fe80:", 5) && strrchr(addr, '%')) { err = sa_addrinfo(addr, sa); } else if (inet_pton(AF_INET6, addr, &sa->u.in6.sin6_addr) > 0) { if (IN6_IS_ADDR_V4MAPPED(&sa->u.in6.sin6_addr)) { const uint8_t *a = &sa->u.in6.sin6_addr.s6_addr[12]; sa->u.in.sin_family = AF_INET; memcpy(&sa->u.in.sin_addr.s_addr, a, 4); } else { sa->u.in6.sin6_family = AF_INET6; } } else { return EINVAL; } return err; } /** * Set a Socket Address from a string * * @param sa Socket Address * @param addr IP-address or UNIX path * @param port Port number * * @return 0 if success, otherwise errorcode */ int sa_set_str(struct sa *sa, const char *addr, uint16_t port) { int err; if (!sa || !addr) return EINVAL; err = sa_pton(addr, sa); if (err) return err; switch (sa->u.sa.sa_family) { #if HAVE_UNIXSOCK == 1 case AF_UNIX: sa->len = sizeof(struct sockaddr_un); break; #endif case AF_INET: sa->u.in.sin_port = htons(port); sa->len = sizeof(struct sockaddr_in); break; case AF_INET6: sa->u.in6.sin6_port = htons(port); sa->len = sizeof(struct sockaddr_in6); break; default: return EAFNOSUPPORT; } return 0; } /** * Set a Socket Address from an IPv4 address * * @param sa Socket Address * @param addr IPv4 address in host order * @param port Port number */ void sa_set_in(struct sa *sa, uint32_t addr, uint16_t port) { if (!sa) return; memset(sa, 0, sizeof(*sa)); sa->u.in.sin_family = AF_INET; sa->u.in.sin_addr.s_addr = htonl(addr); sa->u.in.sin_port = htons(port); sa->len = sizeof(struct sockaddr_in); } /** * Set a Socket Address from an IPv6 address * * @param sa Socket Address * @param addr IPv6 address * @param port Port number */ void sa_set_in6(struct sa *sa, const uint8_t *addr, uint16_t port) { if (!sa) return; memset(sa, 0, sizeof(*sa)); sa->u.in6.sin6_family = AF_INET6; memcpy(&sa->u.in6.sin6_addr, addr, 16); sa->u.in6.sin6_port = htons(port); sa->len = sizeof(struct sockaddr_in6); } /** * Set a Socket Address from a sockaddr * * @param sa Socket Address * @param s Sockaddr * * @return 0 if success, otherwise errorcode */ int sa_set_sa(struct sa *sa, const struct sockaddr *s) { if (!sa || !s) return EINVAL; memset(sa, 0, sizeof(*sa)); switch (s->sa_family) { case AF_INET: memcpy(&sa->u.in, s, sizeof(struct sockaddr_in)); sa->len = sizeof(struct sockaddr_in); break; case AF_INET6: memcpy(&sa->u.in6, s, sizeof(struct sockaddr_in6)); sa->len = sizeof(struct sockaddr_in6); break; default: return EAFNOSUPPORT; } sa->u.sa.sa_family = s->sa_family; return 0; } /** * Set the port number on a Socket Address * * @param sa Socket Address * @param port Port number */ void sa_set_port(struct sa *sa, uint16_t port) { if (!sa) return; switch (sa->u.sa.sa_family) { case AF_INET: sa->u.in.sin_port = htons(port); break; case AF_INET6: sa->u.in6.sin6_port = htons(port); break; default: DEBUG_WARNING("sa_set_port: no af %d (port %u)\n", sa->u.sa.sa_family, port); break; } } /** * Set a socket address from a string of type "address:port" * IPv6 addresses must be encapsulated in square brackets. * * @param sa Socket Address * @param str Address and port string * @param len Length of string * * @return 0 if success, otherwise errorcode * * Example strings: * *
 *   1.2.3.4:1234
 *   [::1]:1234
 *   [::]:5060
 * 
*/ int sa_decode(struct sa *sa, const char *str, size_t len) { struct pl addr, port, pl; const char *c; if (!sa || !str || !len) return EINVAL; pl.p = str; pl.l = len; if ('[' == str[0] && (c = pl_strchr(&pl, ']'))) { addr.p = str + 1; addr.l = c - str - 1; ++c; } else if (NULL != (c = pl_strchr(&pl, ':'))) { addr.p = str; addr.l = c - str; } else { return EINVAL; } if (len < (size_t)(c - str + 2)) return EINVAL; if (':' != *c) return EINVAL; port.p = ++c; port.l = len + str - c; return sa_set(sa, &addr, pl_u32(&port)); } /** * Get the Address Family of a Socket Address * * @param sa Socket Address * * @return Address Family */ int sa_af(const struct sa *sa) { return sa ? sa->u.sa.sa_family : AF_UNSPEC; } /** * Get the IPv4-address of a Socket Address * * @param sa Socket Address * * @return IPv4 address in host order */ uint32_t sa_in(const struct sa *sa) { return sa ? ntohl(sa->u.in.sin_addr.s_addr) : 0; } /** * Get the IPv6-address of a Socket Address * * @param sa Socket Address * @param addr On return, contains the IPv6-address */ void sa_in6(const struct sa *sa, uint8_t *addr) { if (!sa || !addr) return; memcpy(addr, &sa->u.in6.sin6_addr, 16); } /** * Convert a Socket Address to Presentation format * * @param sa Socket Address * @param buf Buffer to store presentation format * @param size Buffer size * * @return 0 if success, otherwise errorcode */ int sa_ntop(const struct sa *sa, char *buf, int size) { const char *ret; if (!sa || !buf || !size) return EINVAL; switch (sa->u.sa.sa_family) { #if HAVE_UNIXSOCK == 1 case AF_UNIX: str_ncpy(buf, sa->u.un.sun_path, size); ret = buf; break; #endif case AF_INET: ret = inet_ntop(AF_INET, &sa->u.in.sin_addr, buf, size); break; case AF_INET6: ret = inet_ntop(AF_INET6, &sa->u.in6.sin6_addr, buf, size); break; default: return EAFNOSUPPORT; } if (!ret) return RE_ERRNO_SOCK; return 0; } /** * Get the port number from a Socket Address * * @param sa Socket Address * * @return Port number in host order */ uint16_t sa_port(const struct sa *sa) { if (!sa) return 0; switch (sa->u.sa.sa_family) { case AF_INET: return ntohs(sa->u.in.sin_port); case AF_INET6: return ntohs(sa->u.in6.sin6_port); default: return 0; } } /** * Check if a Socket Address is set * * @param sa Socket Address * @param flag Flags specifying which fields to check * * @return true if set, false if not set */ bool sa_isset(const struct sa *sa, int flag) { if (!sa) return false; switch (sa->u.sa.sa_family) { #if HAVE_UNIXSOCK == 1 case AF_UNIX: return str_isset(sa->u.un.sun_path); break; #endif case AF_INET: if (flag & SA_ADDR) if (INADDR_ANY == sa->u.in.sin_addr.s_addr) return false; if (flag & SA_PORT) if (0 == sa->u.in.sin_port) return false; break; case AF_INET6: if (flag & SA_ADDR) if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.in6.sin6_addr)) return false; if (flag & SA_PORT) if (0 == sa->u.in6.sin6_port) return false; break; default: return false; } return true; } /** * Calculate the hash value of a Socket Address * * @param sa Socket Address * @param flag Flags specifying which fields to use * * @return Hash value */ uint32_t sa_hash(const struct sa *sa, int flag) { uint32_t v = 0; if (!sa) return 0; switch (sa->u.sa.sa_family) { case AF_INET: if (flag & SA_ADDR) v += ntohl(sa->u.in.sin_addr.s_addr); if (flag & SA_PORT) v += ntohs(sa->u.in.sin_port); break; case AF_INET6: if (flag & SA_ADDR) { uint32_t *a = (uint32_t *)&sa->u.in6.sin6_addr; v += a[0] ^ a[1] ^ a[2] ^ a[3]; } if (flag & SA_PORT) v += ntohs(sa->u.in6.sin6_port); break; default: DEBUG_WARNING("sa_hash: unknown af %d\n", sa->u.sa.sa_family); return 0; } return v; } /** * Copy a Socket Address * * @param dst Socket Address to be written * @param src Socket Address to be copied */ void sa_cpy(struct sa *dst, const struct sa *src) { if (!dst || !src) return; memcpy(dst, src, sizeof(*dst)); } /** * Compare two Socket Address objects * * @param l Socket Address number one * @param r Socket Address number two * @param flag Flags specifying which fields to use * * @return true if match, false if no match */ bool sa_cmp(const struct sa *l, const struct sa *r, int flag) { if (!l || !r) return false; if (l == r) return true; if (l->u.sa.sa_family != r->u.sa.sa_family) return false; switch (l->u.sa.sa_family) { #if HAVE_UNIXSOCK == 1 case AF_UNIX: if (0 == str_cmp(l->u.un.sun_path, r->u.un.sun_path)) return true; else return false; break; #endif case AF_INET: if (flag & SA_ADDR) if (l->u.in.sin_addr.s_addr != r->u.in.sin_addr.s_addr) return false; if (flag & SA_PORT) if (l->u.in.sin_port != r->u.in.sin_port) return false; break; case AF_INET6: if (flag & SA_ADDR) if (memcmp(&l->u.in6.sin6_addr, &r->u.in6.sin6_addr, 16)) return false; if (flag & SA_PORT) if (l->u.in6.sin6_port != r->u.in6.sin6_port) return false; break; default: return false; } return true; } /** IPv4 Link-local test */ #define IN_IS_ADDR_LINKLOCAL(a) \ (((a) & htonl(0xffff0000)) == htonl (0xa9fe0000)) /** * Check if socket address is a link-local address * * @param sa Socket address * * @return true if link-local address, otherwise false */ bool sa_is_linklocal(const struct sa *sa) { if (!sa) return false; switch (sa_af(sa)) { case AF_INET: return IN_IS_ADDR_LINKLOCAL(sa->u.in.sin_addr.s_addr); case AF_INET6: return IN6_IS_ADDR_LINKLOCAL(&sa->u.in6.sin6_addr); default: return false; } } /** * Check if socket address is a loopback address (127.0.0.0/8 or ::1/128) * * @param sa Socket address * * @return true if loopback address, otherwise false */ bool sa_is_loopback(const struct sa *sa) { if (!sa) return false; switch (sa_af(sa)) { case AF_INET: return (ntohl(sa->u.in.sin_addr.s_addr) & 0xff000000) == 0x7f000000; case AF_INET6: return IN6_IS_ADDR_LOOPBACK(&sa->u.in6.sin6_addr); default: return false; } } /** * Check if socket address is a multicast address * * @param sa Socket address * * @return true if multicast address, otherwise false */ bool sa_is_multicast(const struct sa *sa) { if (!sa) return false; switch (sa_af(sa)) { case AF_INET: return IN_MULTICAST(ntohl(sa->u.in.sin_addr.s_addr)); case AF_INET6: return IN6_IS_ADDR_MULTICAST(&sa->u.in6.sin6_addr); default: return false; } } /** * Check if socket address is any/unspecified address * * @param sa Socket address * * @return true if any address, otherwise false */ bool sa_is_any(const struct sa *sa) { if (!sa) return false; switch (sa_af(sa)) { case AF_INET: return INADDR_ANY == ntohl(sa->u.in.sin_addr.s_addr); case AF_INET6: return IN6_IS_ADDR_UNSPECIFIED(&sa->u.in6.sin6_addr); default: return false; } } void sa_set_scopeid(struct sa *sa, uint32_t scopeid) { if (!sa) return; if (sa_af(sa) != AF_INET6) return; sa->u.in6.sin6_scope_id = scopeid; } uint32_t sa_scopeid(const struct sa *sa) { if (!sa) return 0; if (sa_af(sa) != AF_INET6) return 0; return sa->u.in6.sin6_scope_id; } /** * Get the size of 'struct sa' as compiled by the library * * @return Size of 'struct sa' in bytes */ size_t sa_struct_get_size(void) { return sizeof(struct sa); } ================================================ FILE: src/sdp/attr.c ================================================ /** * @file sdp/attr.c SDP Attributes * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "sdp.h" struct sdp_attr { struct le le; char *name; char *val; }; static void destructor(void *arg) { struct sdp_attr *attr = arg; list_unlink(&attr->le); mem_deref(attr->name); mem_deref(attr->val); } int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val) { struct sdp_attr *attr; int err; attr = mem_zalloc(sizeof(*attr), destructor); if (!attr) return ENOMEM; list_append(lst, &attr->le, attr); err = pl_strdup(&attr->name, name); if (pl_isset(val)) err |= pl_strdup(&attr->val, val); if (err) mem_deref(attr); return err; } int sdp_attr_addv(struct list *lst, const char *name, const char *val, va_list ap) { struct sdp_attr *attr; int err; attr = mem_zalloc(sizeof(*attr), destructor); if (!attr) return ENOMEM; list_append(lst, &attr->le, attr); err = str_dup(&attr->name, name); if (str_isset(val)) err |= re_vsdprintf(&attr->val, val, ap); if (err) mem_deref(attr); return err; } void sdp_attr_del(const struct list *lst, const char *name) { struct le *le = list_head(lst); while (le) { struct sdp_attr *attr = le->data; le = le->next; if (0 == str_casecmp(name, attr->name)) mem_deref(attr); } } const char *sdp_attr_apply(const struct list *lst, const char *name, sdp_attr_h *attrh, void *arg) { struct le *le = list_head(lst); while (le) { const struct sdp_attr *attr = le->data; le = le->next; if (name && (!attr->name || strcmp(name, attr->name))) continue; if (!attrh || attrh(attr->name, attr->val?attr->val : "", arg)) return attr->val ? attr->val : ""; } return NULL; } int sdp_attr_print(struct re_printf *pf, const struct sdp_attr *attr) { if (!attr) return 0; if (attr->val) return re_hprintf(pf, "a=%s:%s\r\n", attr->name, attr->val); else return re_hprintf(pf, "a=%s\r\n", attr->name); } int sdp_attr_debug(struct re_printf *pf, const struct sdp_attr *attr) { if (!attr) return 0; if (attr->val) return re_hprintf(pf, "%s='%s'", attr->name, attr->val); else return re_hprintf(pf, "%s", attr->name); } ================================================ FILE: src/sdp/format.c ================================================ /** * @file sdp/format.c SDP format * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "sdp.h" static void destructor(void *arg) { struct sdp_format *fmt = arg; list_unlink(&fmt->le); if (fmt->ref) mem_deref(fmt->data); mem_deref(fmt->id); mem_deref(fmt->params); mem_deref(fmt->rparams); mem_deref(fmt->name); } /** * Add an SDP Format to an SDP Media line * * @param fmtp Pointer to allocated SDP Format * @param m SDP Media line * @param prepend True to prepend, False to append * @param id Format identifier * @param name Format name * @param srate Sampling rate * @param ch Number of channels * @param ench Optional format encode handler * @param cmph Optional format comparison handler * @param data Opaque data for handler * @param ref True to mem_ref() data * @param params Formatted parameters * * @return 0 if success, otherwise errorcode */ int sdp_format_add(struct sdp_format **fmtp, struct sdp_media *m, bool prepend, const char *id, const char *name, uint32_t srate, uint8_t ch, sdp_fmtp_enc_h *ench, sdp_fmtp_cmp_h *cmph, void *data, bool ref, const char *params, ...) { struct sdp_format *fmt; int err; if (!m) return EINVAL; if (!id && (m->dynpt > RTP_DYNPT_END)) return ERANGE; fmt = mem_zalloc(sizeof(*fmt), destructor); if (!fmt) return ENOMEM; if (prepend) list_prepend(&m->lfmtl, &fmt->le, fmt); else list_append(&m->lfmtl, &fmt->le, fmt); if (id) err = str_dup(&fmt->id, id); else err = re_sdprintf(&fmt->id, "%i", m->dynpt++); if (err) goto out; if (name) { err = str_dup(&fmt->name, name); if (err) goto out; } if (params) { va_list ap; va_start(ap, params); err = re_vsdprintf(&fmt->params, params, ap); va_end(ap); if (err) goto out; } fmt->pt = atoi(fmt->id); fmt->srate = srate; fmt->ch = ch; fmt->ench = ench; fmt->cmph = cmph; fmt->data = ref ? mem_ref(data) : data; fmt->ref = ref; fmt->sup = true; out: if (err) mem_deref(fmt); else if (fmtp) *fmtp = fmt; return err; } int sdp_format_radd(struct sdp_media *m, const struct pl *id) { struct sdp_format *fmt; int err; if (!m || !id) return EINVAL; fmt = mem_zalloc(sizeof(*fmt), destructor); if (!fmt) return ENOMEM; list_append(&m->rfmtl, &fmt->le, fmt); err = pl_strdup(&fmt->id, id); if (err) goto out; fmt->pt = atoi(fmt->id); out: if (err) mem_deref(fmt); return err; } struct sdp_format *sdp_format_find(const struct list *lst, const struct pl *id) { struct le *le; if (!lst || !id) return NULL; for (le=lst->head; le; le=le->next) { struct sdp_format *fmt = le->data; if (pl_strcmp(id, fmt->id)) continue; return fmt; } return NULL; } /** * Set the parameters of an SDP format * * @param fmt SDP Format * @param params Formatted parameters * * @return 0 if success, otherwise errorcode */ int sdp_format_set_params(struct sdp_format *fmt, const char *params, ...) { int err = 0; if (!fmt) return EINVAL; fmt->params = mem_deref(fmt->params); if (params) { va_list ap; va_start(ap, params); err = re_vsdprintf(&fmt->params, params, ap); va_end(ap); } return err; } /** * Compare two SDP Formats * * @param fmt1 First SDP format * @param fmt2 Second SDP format * * @return True if matching, False if not */ bool sdp_format_cmp(const struct sdp_format *fmt1, const struct sdp_format *fmt2) { if (!fmt1 || !fmt2) return false; if (fmt1->pt < RTP_DYNPT_START && fmt2->pt < RTP_DYNPT_START) { if (!fmt1->id || !fmt2->id) return false; return strcmp(fmt1->id, fmt2->id) ? false : true; } if (str_casecmp(fmt1->name, fmt2->name)) return false; if (fmt1->srate != fmt2->srate) return false; if (fmt1->ch != fmt2->ch) return false; if (fmt1->cmph && !fmt1->cmph(fmt1->params, fmt2->params, fmt1->data)) return false; if (fmt2->cmph && !fmt2->cmph(fmt2->params, fmt1->params, fmt2->data)) return false; return true; } /** * Print SDP Format debug information * * @param pf Print function for output * @param fmt SDP Format * * @return 0 if success, otherwise errorcode */ int sdp_format_debug(struct re_printf *pf, const struct sdp_format *fmt) { int err; if (!fmt) return 0; err = re_hprintf(pf, "%3s", fmt->id); if (fmt->name) err |= re_hprintf(pf, " %s/%u/%u", fmt->name, fmt->srate, fmt->ch); if (fmt->params) err |= re_hprintf(pf, " (%s)", fmt->params); if (fmt->sup) err |= re_hprintf(pf, " *"); return err; } ================================================ FILE: src/sdp/media.c ================================================ /** * @file sdp/media.c SDP Media * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "sdp.h" static void destructor(void *arg) { struct sdp_media *m = arg; unsigned i; list_flush(&m->lfmtl); list_flush(&m->rfmtl); list_flush(&m->rattrl); list_flush(&m->lattrl); if (m->le.list) { m->disabled = true; m->ench = NULL; mem_ref(m); return; } for (i=0; iprotov); i++) mem_deref(m->protov[i]); list_unlink(&m->le); mem_deref(m->name); mem_deref(m->proto); mem_deref(m->uproto); } static int media_alloc(struct sdp_media **mp, struct list *list) { struct sdp_media *m; int i; m = mem_zalloc(sizeof(*m), destructor); if (!m) return ENOMEM; list_append(list, &m->le, m); m->ldir = SDP_SENDRECV; m->rdir = SDP_SENDRECV; m->dynpt = RTP_DYNPT_START; sa_init(&m->laddr, AF_INET); sa_init(&m->raddr, AF_INET); sa_init(&m->laddr_rtcp, AF_INET); sa_init(&m->raddr_rtcp, AF_INET); for (i=0; ilbwv[i] = -1; m->rbwv[i] = -1; } *mp = m; return 0; } /** * Add a media line to an SDP Session * * @param mp Pointer to allocated SDP Media line object * @param sess SDP Session * @param name Media name * @param port Port number * @param proto Transport protocol * * @return 0 if success, otherwise errorcode */ int sdp_media_add(struct sdp_media **mp, struct sdp_session *sess, const char *name, uint16_t port, const char *proto) { struct sdp_media *m; int err; if (!sess || !name || !proto) return EINVAL; err = media_alloc(&m, &sess->lmedial); if (err) return err; err = str_dup(&m->name, name); err |= str_dup(&m->proto, proto); if (err) goto out; sa_set_port(&m->laddr, port); out: if (err) mem_deref(m); else if (mp) *mp = m; return err; } /** * Add a remote SDP media line to an SDP Session * * @param mp Pointer to allocated SDP Media line object * @param sess SDP Session * @param name Media name * @param proto Transport protocol * * @return 0 if success, otherwise errorcode */ int sdp_media_radd(struct sdp_media **mp, struct sdp_session *sess, const struct pl *name, const struct pl *proto) { struct sdp_media *m; int err; if (!mp || !sess || !name || !proto) return EINVAL; err = media_alloc(&m, &sess->medial); if (err) return err; m->disabled = true; err = pl_strdup(&m->name, name); err |= pl_strdup(&m->proto, proto); if (err) mem_deref(m); else *mp = m; return err; } /** * Reset the remote part of an SDP Media line * * @param m SDP Media line */ void sdp_media_rreset(struct sdp_media *m) { int i; if (!m) return; sa_init(&m->raddr, AF_INET); sa_init(&m->raddr_rtcp, AF_INET); list_flush(&m->rfmtl); list_flush(&m->rattrl); m->rdir = SDP_SENDRECV; for (i=0; irbwv[i] = -1; } /** * Compare media line protocols * * @param m SDP Media line * @param proto Transport protocol * @param update Update media protocol if match is found in alternate set * * @return True if matching, False if not */ bool sdp_media_proto_cmp(struct sdp_media *m, const struct pl *proto, bool update) { unsigned i; if (!m || !proto) return false; if (!pl_strcmp(proto, m->proto)) return true; for (i=0; iprotov); i++) { if (!m->protov[i] || pl_strcmp(proto, m->protov[i])) continue; if (update) { mem_deref(m->proto); m->proto = mem_ref(m->protov[i]); } return true; } return false; } /** * Find an SDP Media line from name and transport protocol * * @param sess SDP Session * @param name Media name * @param proto Transport protocol * @param update_proto Update media transport protocol * * @return Matching media line if found, NULL if not found */ struct sdp_media *sdp_media_find(const struct sdp_session *sess, const struct pl *name, const struct pl *proto, bool update_proto) { struct le *le; if (!sess || !name || !proto) return NULL; for (le=sess->lmedial.head; le; le=le->next) { struct sdp_media *m = le->data; if (pl_strcmp(name, m->name)) continue; if (!sdp_media_proto_cmp(m, proto, update_proto)) continue; return m; } return NULL; } /** * Align the locate/remote formats of an SDP Media line * * @param m SDP Media line * @param offer True if SDP Offer, False if SDP Answer */ void sdp_media_align_formats(struct sdp_media *m, bool offer) { struct sdp_format *rfmt, *lfmt = NULL; struct le *rle, *lle; int pt_offer = RTP_DYNPT_START; if (!m || m->disabled || !sa_port(&m->raddr) || m->fmt_ignore) return; for (lle=m->lfmtl.head; lle; lle=lle->next) { lfmt = lle->data; lfmt->rparams = mem_deref(lfmt->rparams); lfmt->sup = false; } rle = m->rfmtl.tail; while (rle) { rfmt = rle->data; rle = rle->prev; for (lle=m->lfmtl.head; lle; lle=lle->next) { lfmt = lle->data; if (sdp_format_cmp(lfmt, rfmt)) break; } if (!lle || !lfmt) { rfmt->sup = false; continue; } mem_deref(lfmt->rparams); lfmt->rparams = mem_ref(rfmt->params); lfmt->sup = true; rfmt->sup = true; if (rfmt->ref) rfmt->data = mem_deref(rfmt->data); else rfmt->data = NULL; if (lfmt->ref) rfmt->data = mem_ref(lfmt->data); else rfmt->data = lfmt->data; rfmt->ref = lfmt->ref; /* Use payload type from offer - RFC3264 - 6.1 */ if (offer) { mem_deref(lfmt->id); lfmt->id = mem_ref(rfmt->id); lfmt->pt = atoi(lfmt->id ? lfmt->id : ""); list_unlink(&lfmt->le); list_prepend(&m->lfmtl, &lfmt->le, lfmt); if (lfmt->pt > pt_offer) pt_offer = lfmt->pt; } } /* Recalculate pt and reorder unsupported codecs */ if (offer) { for (lle = m->lfmtl.tail; lle;) { lfmt = lle->data; lle = lle->prev; if (lfmt && !lfmt->sup) { if (lfmt->pt >= RTP_DYNPT_START) { mem_deref(lfmt->id); lfmt->pt = ++pt_offer; re_sdprintf(&lfmt->id, "%i", lfmt->pt); } list_unlink(&lfmt->le); list_append(&m->lfmtl, &lfmt->le, lfmt); } } } } /** * Set alternative protocols for an SDP Media line * * @param m SDP Media line * @param protoc Number of alternative protocols * * @return 0 if success, otherwise errorcode */ int sdp_media_set_alt_protos(struct sdp_media *m, unsigned protoc, ...) { const char *proto; int err = 0; unsigned i; va_list ap; if (!m) return EINVAL; va_start(ap, protoc); for (i=0; iprotov); i++) { m->protov[i] = mem_deref(m->protov[i]); if (i >= protoc) continue; proto = va_arg(ap, const char *); if (proto) err |= str_dup(&m->protov[i], proto); } va_end(ap); return err; } /** * Set SDP Media line encode handler * * @param m SDP Media line * @param ench Encode handler * @param arg Encode handler argument */ void sdp_media_set_encode_handler(struct sdp_media *m, sdp_media_enc_h *ench, void *arg) { if (!m) return; m->ench = ench; m->arg = arg; } /** * Set an SDP Media line to ignore formats * * @param m SDP Media line * @param fmt_ignore True for ignore formats, otherwise false */ void sdp_media_set_fmt_ignore(struct sdp_media *m, bool fmt_ignore) { if (!m) return; m->fmt_ignore = fmt_ignore; } /** * Set an SDP Media line to enabled/disabled * * @param m SDP Media line * @param disabled True for disabled, False for enabled */ void sdp_media_set_disabled(struct sdp_media *m, bool disabled) { if (!m) return; m->disabled = disabled; } /** * Check if an SDP Media line is disabled * * @param m SDP Media line * @return True if disabled, otherwise false */ bool sdp_media_disabled(struct sdp_media *m) { if (!m) return true; return m->disabled; } /** * Set the local port number of an SDP Media line * * @param m SDP Media line * @param port Port number */ void sdp_media_set_lport(struct sdp_media *m, uint16_t port) { if (!m) return; sa_set_port(&m->laddr, port); } /** * Set the local network address of an SDP media line * * @param m SDP Media line * @param laddr Local network address */ void sdp_media_set_laddr(struct sdp_media *m, const struct sa *laddr) { if (!m || !laddr) return; m->laddr = *laddr; } /** * Set a local bandwidth of an SDP Media line * * @param m SDP Media line * @param type Bandwidth type * @param bw Bandwidth value */ void sdp_media_set_lbandwidth(struct sdp_media *m, enum sdp_bandwidth type, int32_t bw) { if (!m || type < SDP_BANDWIDTH_MIN || type >= SDP_BANDWIDTH_MAX) return; m->lbwv[type] = bw; } /** * Set the local RTCP port number of an SDP Media line * * @param m SDP Media line * @param port RTCP Port number */ void sdp_media_set_lport_rtcp(struct sdp_media *m, uint16_t port) { if (!m) return; sa_set_port(&m->laddr_rtcp, port); } /** * Set the local RTCP network address of an SDP media line * * @param m SDP Media line * @param laddr Local RTCP network address */ void sdp_media_set_laddr_rtcp(struct sdp_media *m, const struct sa *laddr) { if (!m || !laddr) return; m->laddr_rtcp = *laddr; } /** * Set the local direction flag of an SDP Media line * * @param m SDP Media line * @param dir Media direction flag */ void sdp_media_set_ldir(struct sdp_media *m, enum sdp_dir dir) { if (!m) return; m->ldir = dir; } /** * Set a local attribute of an SDP Media line * * @param m SDP Media line * @param replace True to replace attribute, False to append * @param name Attribute name * @param value Formatted attribute value * * @return 0 if success, otherwise errorcode */ int sdp_media_set_lattr(struct sdp_media *m, bool replace, const char *name, const char *value, ...) { va_list ap; int err; if (!m || !name) return EINVAL; if (replace) sdp_attr_del(&m->lattrl, name); va_start(ap, value); err = sdp_attr_addv(&m->lattrl, name, value, ap); va_end(ap); return err; } /** * Delete a local attribute of an SDP Media line * * @param m SDP Media line * @param name Attribute name */ void sdp_media_del_lattr(struct sdp_media *m, const char *name) { if (!m || !name) return; sdp_attr_del(&m->lattrl, name); } const char *sdp_media_proto(const struct sdp_media *m) { return m ? m->proto : NULL; } /** * Get the remote port number of an SDP Media line * * @param m SDP Media line * * @return Remote port number */ uint16_t sdp_media_rport(const struct sdp_media *m) { return m ? sa_port(&m->raddr) : 0; } /** * Get the remote network address of an SDP Media line * * @param m SDP Media line * * @return Remote network address */ const struct sa *sdp_media_raddr(const struct sdp_media *m) { return m ? &m->raddr : NULL; } /** * Get the local network address of an SDP Media line * * @param m SDP Media line * * @return Local network address */ const struct sa *sdp_media_laddr(const struct sdp_media *m) { return m ? &m->laddr : NULL; } /** * Get the remote RTCP network address of an SDP Media line * * @param m SDP Media line * @param raddr On return, contains remote RTCP network address */ void sdp_media_raddr_rtcp(const struct sdp_media *m, struct sa *raddr) { if (!m || !raddr) return; if (sa_isset(&m->raddr_rtcp, SA_ALL)) { *raddr = m->raddr_rtcp; } else if (sa_isset(&m->raddr_rtcp, SA_PORT)) { *raddr = m->raddr; sa_set_port(raddr, sa_port(&m->raddr_rtcp)); } else { uint16_t port = sa_port(&m->raddr); *raddr = m->raddr; sa_set_port(raddr, port ? port + 1 : 0); } } /** * Get a remote bandwidth of an SDP Media line * * @param m SDP Media line * @param type Bandwidth type * * @return Remote bandwidth value */ int32_t sdp_media_rbandwidth(const struct sdp_media *m, enum sdp_bandwidth type) { if (!m || type < SDP_BANDWIDTH_MIN || type >= SDP_BANDWIDTH_MAX) return 0; return m->rbwv[type]; } /** * Get the local media direction of an SDP Media line * * @param m SDP Media line * * @return Local media direction */ enum sdp_dir sdp_media_ldir(const struct sdp_media *m) { return m ? m->ldir : SDP_INACTIVE; } /** * Get the remote media direction of an SDP Media line * * @param m SDP Media line * * @return Remote media direction */ enum sdp_dir sdp_media_rdir(const struct sdp_media *m) { return m ? m->rdir : SDP_INACTIVE; } /** * Get the combined media direction of an SDP Media line * * @param m SDP Media line * * @return Combined media direction */ enum sdp_dir sdp_media_dir(const struct sdp_media *m) { return m ? (enum sdp_dir)(m->ldir & m->rdir) : SDP_INACTIVE; } /** * Find a local SDP format from a payload type * * @param m SDP Media line * @param pt Payload type * * @return Local SDP format if found, NULL if not found */ const struct sdp_format *sdp_media_lformat(const struct sdp_media *m, int pt) { struct le *le; if (!m) return NULL; for (le=m->lfmtl.head; le; le=le->next) { const struct sdp_format *fmt = le->data; if (pt == fmt->pt) return fmt; } return NULL; } /** * Find a remote SDP format from a format name * * @param m SDP Media line * @param name Format name * * @return Remote SDP format if found, NULL if not found */ const struct sdp_format *sdp_media_rformat(const struct sdp_media *m, const char *name) { struct le *le; if (!m || !sa_port(&m->raddr)) return NULL; for (le=m->rfmtl.head; le; le=le->next) { const struct sdp_format *fmt = le->data; if (!fmt->sup) continue; if (name && str_casecmp(name, fmt->name)) continue; return fmt; } return NULL; } /** * Find an SDP Format of an SDP Media line * * @param m SDP Media line * @param local True if local media, False if remote * @param id SDP format id * @param pt Payload type * @param name Format name * @param srate Sampling rate * @param ch Number of channels * * @return SDP Format if found, NULL if not found */ struct sdp_format *sdp_media_format(const struct sdp_media *m, bool local, const char *id, int pt, const char *name, int32_t srate, int8_t ch) { return sdp_media_format_apply(m, local, id, pt, name, srate, ch, NULL, NULL); } /** * Apply a function handler to all matching SDP formats * * @param m SDP Media line * @param local True if local media, False if remote * @param id SDP format id * @param pt Payload type * @param name Format name * @param srate Sampling rate * @param ch Number of channels * @param fmth SDP Format handler * @param arg Handler argument * * @return SDP Format if found, NULL if not found */ struct sdp_format *sdp_media_format_apply(const struct sdp_media *m, bool local, const char *id, int pt, const char *name, int32_t srate, int8_t ch, sdp_format_h *fmth, void *arg) { struct le *le; if (!m) return NULL; le = local ? m->lfmtl.head : m->rfmtl.head; while (le) { struct sdp_format *fmt = le->data; le = le->next; if (id && (!fmt->id || strcmp(id, fmt->id))) continue; if (pt >= 0 && pt != fmt->pt) continue; if (name && str_casecmp(name, fmt->name)) continue; if (srate >= 0 && (uint32_t)srate != fmt->srate) continue; if (ch >= 0 && (uint8_t)ch != fmt->ch) continue; if (!fmth || fmth(fmt, arg)) return fmt; } return NULL; } /** * Get the list of SDP Formats * * @param m SDP Media line * @param local True if local, False if remote * * @return List of SDP Formats */ const struct list *sdp_media_format_lst(const struct sdp_media *m, bool local) { if (!m) return NULL; return local ? &m->lfmtl : &m->rfmtl; } /** * Get a remote attribute from an SDP Media line * * @param m SDP Media line * @param name Attribute name * * @return Attribute value, NULL if not found */ const char *sdp_media_rattr(const struct sdp_media *m, const char *name) { if (!m || !name) return NULL; return sdp_attr_apply(&m->rattrl, name, NULL, NULL); } /** * Get a remote attribute from an SDP Media line or the SDP session * * @param m SDP Media line * @param sess SDP Session * @param name Attribute name * * @return Attribute value, NULL if not found */ const char *sdp_media_session_rattr(const struct sdp_media *m, const struct sdp_session *sess, const char *name) { const char *val; val = sdp_media_rattr(m, name); if (!val) val = sdp_session_rattr(sess, name); return val; } /** * Apply a function handler to all matching local attributes * * @param m SDP Media line * @param name Attribute name * @param attrh Attribute handler * @param arg Handler argument * * @return Attribute value if match */ const char *sdp_media_lattr_apply(const struct sdp_media *m, const char *name, sdp_attr_h *attrh, void *arg) { if (!m) return NULL; return sdp_attr_apply(&m->lattrl, name, attrh, arg); } /** * Apply a function handler to all matching remote attributes * * @param m SDP Media line * @param name Attribute name * @param attrh Attribute handler * @param arg Handler argument * * @return Attribute value if match */ const char *sdp_media_rattr_apply(const struct sdp_media *m, const char *name, sdp_attr_h *attrh, void *arg) { if (!m) return NULL; return sdp_attr_apply(&m->rattrl, name, attrh, arg); } /** * Get the name of an SDP Media line * * @param m SDP Media line * * @return SDP Media line name */ const char *sdp_media_name(const struct sdp_media *m) { return m ? m->name : NULL; } /** * Print SDP Media line debug information * * @param pf Print function for output * @param m SDP Media line * * @return 0 if success, otherwise errorcode */ int sdp_media_debug(struct re_printf *pf, const struct sdp_media *m) { struct le *le; int err; if (!m) return 0; err = re_hprintf(pf, "%s %s\n", m->name, m->proto); err |= re_hprintf(pf, " local formats:\n"); for (le=m->lfmtl.head; le; le=le->next) err |= re_hprintf(pf, " %H\n", sdp_format_debug, le->data); err |= re_hprintf(pf, " remote formats:\n"); for (le=m->rfmtl.head; le; le=le->next) err |= re_hprintf(pf, " %H\n", sdp_format_debug, le->data); err |= re_hprintf(pf, " local attributes:\n"); for (le=m->lattrl.head; le; le=le->next) err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data); err |= re_hprintf(pf, " remote attributes:\n"); for (le=m->rattrl.head; le; le=le->next) err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data); err |= re_hprintf(pf, " local direction: %s\n", sdp_dir_name(m->ldir)); err |= re_hprintf(pf, " remote direction: %s\n", sdp_dir_name(m->rdir)); return err; } ================================================ FILE: src/sdp/msg.c ================================================ /** * @file sdp/msg.c SDP Message processing * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include "sdp.h" static int attr_decode_fmtp(struct sdp_media *m, const struct pl *pl) { struct sdp_format *fmt; struct pl id, params; if (!m) return 0; if (re_regex(pl->p, pl->l, "[^ ]+ [^]*", &id, ¶ms)) return EBADMSG; fmt = sdp_format_find(&m->rfmtl, &id); if (!fmt) return 0; fmt->params = mem_deref(fmt->params); return pl_strdup(&fmt->params, ¶ms); } static int attr_decode_rtcp(struct sdp_media *m, const struct pl *pl) { struct pl port, addr; int err = 0; if (!m) return 0; if (!re_regex(pl->p, pl->l, "[0-9]+ IN IP[46]1 [^ ]+", &port, NULL, &addr)) { (void)sa_set(&m->raddr_rtcp, &addr, pl_u32(&port)); } else if (!re_regex(pl->p, pl->l, "[0-9]+", &port)) { sa_set_port(&m->raddr_rtcp, pl_u32(&port)); } else err = EBADMSG; return err; } static int attr_decode_rtpmap(struct sdp_media *m, const struct pl *pl) { struct pl id, name, srate, ch; struct sdp_format *fmt; int err; if (!m) return 0; if (re_regex(pl->p, pl->l, "[^ ]+ [^/]+/[0-9]+[/]*[^]*", &id, &name, &srate, NULL, &ch)) return EBADMSG; fmt = sdp_format_find(&m->rfmtl, &id); if (!fmt) return 0; fmt->name = mem_deref(fmt->name); err = pl_strdup(&fmt->name, &name); if (err) return err; fmt->srate = pl_u32(&srate); fmt->ch = ch.l ? pl_u32(&ch) : 1; return 0; } static int attr_decode(struct sdp_session *sess, struct sdp_media *m, enum sdp_dir *dir, const struct pl *pl) { struct pl name, val; int err = 0; if (re_regex(pl->p, pl->l, "[^:]+:[^]+", &name, &val)) { name = *pl; val = pl_null; } if (!pl_strcmp(&name, "fmtp")) err = attr_decode_fmtp(m, &val); else if (!pl_strcmp(&name, "inactive")) *dir = SDP_INACTIVE; else if (!pl_strcmp(&name, "recvonly")) *dir = SDP_SENDONLY; else if (!pl_strcmp(&name, "rtcp")) err = attr_decode_rtcp(m, &val); else if (!pl_strcmp(&name, "rtpmap")) err = attr_decode_rtpmap(m, &val); else if (!pl_strcmp(&name, "sendonly")) *dir = SDP_RECVONLY; else if (!pl_strcmp(&name, "sendrecv")) *dir = SDP_SENDRECV; else err = sdp_attr_add(m ? &m->rattrl : &sess->rattrl, &name, &val); return err; } static int bandwidth_decode(int32_t *bwv, const struct pl *pl) { struct pl type, bw; if (re_regex(pl->p, pl->l, "[^:]+:[0-9]+", &type, &bw)) return EBADMSG; if (!pl_strcmp(&type, "CT")) bwv[SDP_BANDWIDTH_CT] = pl_u32(&bw); else if (!pl_strcmp(&type, "AS")) bwv[SDP_BANDWIDTH_AS] = pl_u32(&bw); else if (!pl_strcmp(&type, "RS")) bwv[SDP_BANDWIDTH_RS] = pl_u32(&bw); else if (!pl_strcmp(&type, "RR")) bwv[SDP_BANDWIDTH_RR] = pl_u32(&bw); else if (!pl_strcmp(&type, "TIAS")) bwv[SDP_BANDWIDTH_TIAS] = pl_u32(&bw); return 0; } static int conn_decode(struct sa *sa, const struct pl *pl) { struct pl v; if (re_regex(pl->p, pl->l, "IN IP[46]1 [^ ]+", NULL, &v)) return EBADMSG; (void)sa_set(sa, &v, sa_port(sa)); return 0; } static int media_decode(struct sdp_media **mp, struct sdp_session *sess, bool offer, const struct pl *pl) { struct pl name, port, proto, fmtv, fmt; struct sdp_media *m; int err; if (re_regex(pl->p, pl->l, "[a-z]+ [^ ]+ [^ ]+[^]*", &name, &port, &proto, &fmtv)) return EBADMSG; m = list_ledata(*mp ? (*mp)->le.next : sess->medial.head); if (!m) { if (!offer) return EPROTO; m = sdp_media_find(sess, &name, &proto, true); if (!m) { err = sdp_media_radd(&m, sess, &name, &proto); if (err) return err; } else { list_unlink(&m->le); list_append(&sess->medial, &m->le, m); } m->uproto = mem_deref(m->uproto); } else { if (pl_strcmp(&name, m->name)) return offer ? ENOTSUP : EPROTO; m->uproto = mem_deref(m->uproto); if (!sdp_media_proto_cmp(m, &proto, offer)) { err = pl_strdup(&m->uproto, &proto); if (err) return err; } } while (!re_regex(fmtv.p, fmtv.l, " [^ ]+", &fmt)) { pl_advance(&fmtv, fmt.p + fmt.l - fmtv.p); err = sdp_format_radd(m, &fmt); if (err) return err; } m->raddr = sess->raddr; sa_set_port(&m->raddr, m->uproto ? 0 : pl_u32(&port)); m->rdir = sess->rdir; if (!pl_u32(&port)) m->rdir = SDP_INACTIVE; *mp = m; return 0; } static int version_decode(const struct pl *pl) { return pl_strcmp(pl, "0") ? ENOSYS : 0; } /** * Decode an SDP message into an SDP Session * * @param sess SDP Session * @param mb Memory buffer containing SDP message * @param offer True if SDP offer, False if SDP answer * * @return 0 if success, otherwise errorcode */ int sdp_decode(struct sdp_session *sess, struct mbuf *mb, bool offer) { struct sdp_media *m; struct pl pl, val; struct le *le; char type = 0; int err = 0; if (!sess || !mb) return EINVAL; sdp_session_rreset(sess); for (le=sess->medial.head; le; le=le->next) { m = le->data; sdp_media_rreset(m); } pl.p = (const char *)mbuf_buf(mb); pl.l = mbuf_get_left(mb); m = NULL; for (;pl.l && !err; pl.p++, pl.l--) { switch (*pl.p) { case '\r': case '\n': if (!type) break; switch (type) { case 'a': err = attr_decode(sess, m, m ? &m->rdir : &sess->rdir, &val); break; case 'b': err = bandwidth_decode(m? m->rbwv : sess->rbwv, &val); break; case 'c': err = conn_decode(m ? &m->raddr : &sess->raddr, &val); break; case 'm': err = media_decode(&m, sess, offer, &val); break; case 'v': err = version_decode(&val); break; } #if 0 if (err) re_printf("** %c='%r': %m\n", type, &val, err); #endif type = 0; break; default: if (type) { val.l++; break; } if (pl.l < 2 || *(pl.p + 1) != '=') { err = EBADMSG; break; } type = *pl.p; val.p = pl.p + 2; val.l = 0; pl.p += 1; pl.l -= 1; break; } } if (err) return err; if (type) return EBADMSG; for (le=sess->medial.head; le; le=le->next) sdp_media_align_formats(le->data, offer); return 0; } static int media_encode(const struct sdp_media *m, struct mbuf *mb, bool offer) { enum sdp_bandwidth i; const char *proto; int err, supc = 0; bool disabled = false; bool rejected = false; struct le *le; uint16_t port; for (le=m->lfmtl.head; le; le=le->next) { const struct sdp_format *fmt = le->data; if (fmt->sup) ++supc; } /*disable if: local supported and (m->disabled or raddr port 0)*/ if (supc && (m->disabled || (!offer && !sa_port(&m->raddr)))) { disabled = true; port = sa_port(&m->laddr); proto = m->proto; } /*reject if: not supported or not supported proto in the offer*/ else if (supc == 0 || (!offer && m->uproto)) { rejected = true; port = 0; if (str_isset(m->uproto)) proto = m->uproto; else proto = m->proto; } /*everything works*/ else { port = sa_port(&m->laddr); proto = m->proto; } err = mbuf_printf(mb, "m=%s %u %s", m->name, port, proto); if (rejected) { err |= mbuf_write_str(mb, " 0\r\n"); return err; } for (le=m->lfmtl.head; le; le=le->next) { const struct sdp_format *fmt = le->data; if (!fmt->sup && !offer) continue; err |= mbuf_printf(mb, " %s", fmt->id); } err |= mbuf_write_str(mb, "\r\n"); if (sa_isset(&m->laddr, SA_ADDR)) { const int ipver = sa_af(&m->laddr) == AF_INET ? 4 : 6; err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &m->laddr); } for (i=SDP_BANDWIDTH_MIN; ilbwv[i] < 0) continue; err |= mbuf_printf(mb, "b=%s:%i\r\n", sdp_bandwidth_name(i), m->lbwv[i]); } for (le=m->lfmtl.head; le; le=le->next) { const struct sdp_format *fmt = le->data; if (!str_isset(fmt->name)) continue; if (!fmt->sup && !offer) continue; if ((str_ncmp(m->proto, "RTP/", 4) == 0) || (str_str(m->proto, "/RTP/") != NULL)) { err |= mbuf_printf(mb, "a=rtpmap:%s %s/%u", fmt->id, fmt->name, fmt->srate); if (fmt->ch > 1) err |= mbuf_printf(mb, "/%u", fmt->ch); err |= mbuf_printf(mb, "\r\n"); } if (str_isset(fmt->params)) err |= mbuf_printf(mb, "a=fmtp:%s %s\r\n", fmt->id, fmt->params); if (fmt->ench) err |= fmt->ench(mb, fmt, offer, fmt->data); } if (sa_isset(&m->laddr_rtcp, SA_ALL)) err |= mbuf_printf(mb, "a=rtcp:%u IN IP%d %j\r\n", sa_port(&m->laddr_rtcp), (AF_INET == sa_af(&m->laddr_rtcp)) ? 4 : 6, &m->laddr_rtcp); else if (sa_isset(&m->laddr_rtcp, SA_PORT)) err |= mbuf_printf(mb, "a=rtcp:%u\r\n", sa_port(&m->laddr_rtcp)); err |= mbuf_printf(mb, "a=%s\r\n", disabled ? sdp_dir_name(SDP_INACTIVE) : sdp_dir_name(offer ? m->ldir : m->ldir & m->rdir)); for (le = m->lattrl.head; le; le = le->next) err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data); if (m->ench) err |= m->ench(mb, offer, m->arg); return err; } /** * Encode an SDP Session into a memory buffer * * @param mbp Pointer to allocated memory buffer * @param sess SDP Session * @param offer True if SDP Offer, False if SDP Answer * * @return 0 if success, otherwise errorcode */ int sdp_encode(struct mbuf **mbp, struct sdp_session *sess, bool offer) { int ipver; enum sdp_bandwidth i; struct mbuf *mb; struct le *le; int err; if (!mbp || !sess) return EINVAL; mb = mbuf_alloc(512); if (!mb) return ENOMEM; ipver = sa_af(&sess->laddr) == AF_INET ? 4 : 6; err = mbuf_printf(mb, "v=%u\r\n", SDP_VERSION); err |= mbuf_printf(mb, "o=- %u %u IN IP%d %j\r\n", sess->id, sess->ver++, ipver, &sess->laddr); err |= mbuf_write_str(mb, "s=-\r\n"); err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &sess->laddr); for (i=SDP_BANDWIDTH_MIN; ilbwv[i] < 0) continue; err |= mbuf_printf(mb, "b=%s:%i\r\n", sdp_bandwidth_name(i), sess->lbwv[i]); } err |= mbuf_write_str(mb, "t=0 0\r\n"); for (le = sess->lattrl.head; le; le = le->next) err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data); for (le=sess->lmedial.head; offer && le;) { struct sdp_media *m = le->data; le = le->next; if (m->disabled) continue; list_unlink(&m->le); list_append(&sess->medial, &m->le, m); } for (le=sess->medial.head; le; le=le->next) { struct sdp_media *m = le->data; err |= media_encode(m, mb, offer); } mb->pos = 0; if (err) mem_deref(mb); else *mbp = mb; return err; } ================================================ FILE: src/sdp/sdp.h ================================================ /** * @file sdp.h Internal SDP interface * * Copyright (C) 2010 Creytiv.com */ enum { RTP_DYNPT_START = 96, RTP_DYNPT_END = 127, }; struct sdp_session { struct list lmedial; struct list medial; struct list lattrl; struct list rattrl; struct sa laddr; struct sa raddr; int32_t lbwv[SDP_BANDWIDTH_MAX]; int32_t rbwv[SDP_BANDWIDTH_MAX]; uint32_t id; uint32_t ver; enum sdp_dir rdir; }; struct sdp_media { struct le le; struct list lfmtl; struct list rfmtl; struct list lattrl; struct list rattrl; struct sa laddr; struct sa raddr; struct sa laddr_rtcp; struct sa raddr_rtcp; int32_t lbwv[SDP_BANDWIDTH_MAX]; int32_t rbwv[SDP_BANDWIDTH_MAX]; char *name; char *proto; char *protov[8]; char *uproto; /* unsupported protocol */ sdp_media_enc_h *ench; void *arg; enum sdp_dir ldir; enum sdp_dir rdir; bool fmt_ignore; bool disabled; int dynpt; }; /* session */ void sdp_session_rreset(struct sdp_session *sess); /* media */ int sdp_media_radd(struct sdp_media **mp, struct sdp_session *sess, const struct pl *name, const struct pl *proto); void sdp_media_rreset(struct sdp_media *m); bool sdp_media_proto_cmp(struct sdp_media *m, const struct pl *proto, bool update); struct sdp_media *sdp_media_find(const struct sdp_session *sess, const struct pl *name, const struct pl *proto, bool update_proto); void sdp_media_align_formats(struct sdp_media *m, bool offer); /* format */ int sdp_format_radd(struct sdp_media *m, const struct pl *id); struct sdp_format *sdp_format_find(const struct list *lst, const struct pl *id); /* attribute */ struct sdp_attr; int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val); int sdp_attr_addv(struct list *lst, const char *name, const char *val, va_list ap); void sdp_attr_del(const struct list *lst, const char *name); const char *sdp_attr_apply(const struct list *lst, const char *name, sdp_attr_h *attrh, void *arg); int sdp_attr_print(struct re_printf *pf, const struct sdp_attr *attr); int sdp_attr_debug(struct re_printf *pf, const struct sdp_attr *attr); ================================================ FILE: src/sdp/session.c ================================================ /** * @file sdp/session.c SDP Session * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include "sdp.h" static void destructor(void *arg) { struct sdp_session *sess = arg; list_flush(&sess->lmedial); list_flush(&sess->medial); list_flush(&sess->rattrl); list_flush(&sess->lattrl); } /** * Allocate a new SDP Session * * @param sessp Pointer to allocated SDP Session object * @param laddr Local network address * * @return 0 if success, otherwise errorcode */ int sdp_session_alloc(struct sdp_session **sessp, const struct sa *laddr) { struct sdp_session *sess; int i; if (!sessp || !laddr) return EINVAL; sess = mem_zalloc(sizeof(*sess), destructor); if (!sess) return ENOMEM; sess->laddr = *laddr; sess->id = rand_u32(); sess->ver = rand_u32() & 0x7fffffff; sess->rdir = SDP_SENDRECV; sa_init(&sess->raddr, AF_INET); for (i=0; ilbwv[i] = -1; sess->rbwv[i] = -1; } *sessp = sess; return 0; } /** * Reset the remote side of an SDP Session * * @param sess SDP Session */ void sdp_session_rreset(struct sdp_session *sess) { int i; if (!sess) return; sa_init(&sess->raddr, AF_INET); list_flush(&sess->rattrl); sess->rdir = SDP_SENDRECV; for (i=0; irbwv[i] = -1; } /** * Set the local network address of an SDP Session * * @param sess SDP Session * @param laddr Local network address */ void sdp_session_set_laddr(struct sdp_session *sess, const struct sa *laddr) { if (!sess || !laddr) return; sess->laddr = *laddr; } /** * Get the local network address of an SDP Session * * @param sess SDP Session * @return Local network address */ const struct sa *sdp_session_laddr(struct sdp_session *sess) { return sess ? &sess->laddr : NULL; } /** * Set the local bandwidth of an SDP Session * * @param sess SDP Session * @param type Bandwidth type * @param bw Bandwidth value */ void sdp_session_set_lbandwidth(struct sdp_session *sess, enum sdp_bandwidth type, int32_t bw) { if (!sess || type < SDP_BANDWIDTH_MIN || type >= SDP_BANDWIDTH_MAX) return; sess->lbwv[type] = bw; } /** * Set a local attribute of an SDP Session * * @param sess SDP Session * @param replace True to replace any existing attributes, false to append * @param name Attribute name * @param value Formatted attribute value * * @return 0 if success, otherwise errorcode */ int sdp_session_set_lattr(struct sdp_session *sess, bool replace, const char *name, const char *value, ...) { va_list ap; int err; if (!sess || !name) return EINVAL; if (replace) sdp_attr_del(&sess->lattrl, name); va_start(ap, value); err = sdp_attr_addv(&sess->lattrl, name, value, ap); va_end(ap); return err; } /** * Delete a local attribute of an SDP Session * * @param sess SDP Session * @param name Attribute name */ void sdp_session_del_lattr(struct sdp_session *sess, const char *name) { if (!sess || !name) return; sdp_attr_del(&sess->lattrl, name); } /** * Get the local bandwidth of an SDP Session * * @param sess SDP Session * @param type Bandwidth type * * @return Bandwidth value */ int32_t sdp_session_lbandwidth(const struct sdp_session *sess, enum sdp_bandwidth type) { if (!sess || type < SDP_BANDWIDTH_MIN || type >= SDP_BANDWIDTH_MAX) return 0; return sess->lbwv[type]; } /** * Get the remote bandwidth of an SDP Session * * @param sess SDP Session * @param type Bandwidth type * * @return Bandwidth value */ int32_t sdp_session_rbandwidth(const struct sdp_session *sess, enum sdp_bandwidth type) { if (!sess || type < SDP_BANDWIDTH_MIN || type >= SDP_BANDWIDTH_MAX) return 0; return sess->rbwv[type]; } /** * Get a remote attribute of an SDP Session * * @param sess SDP Session * @param name Attribute name * * @return Attribute value if exist, NULL if not exist */ const char *sdp_session_rattr(const struct sdp_session *sess, const char *name) { if (!sess || !name) return NULL; return sdp_attr_apply(&sess->rattrl, name, NULL, NULL); } /** * Apply a function handler of all matching remote attributes * * @param sess SDP Session * @param name Attribute name * @param attrh Attribute handler * @param arg Handler argument * * @return Attribute value if match */ const char *sdp_session_rattr_apply(const struct sdp_session *sess, const char *name, sdp_attr_h *attrh, void *arg) { if (!sess) return NULL; return sdp_attr_apply(&sess->rattrl, name, attrh, arg); } /** * Get the list of media-lines from an SDP Session * * @param sess SDP Session * @param local True for local, False for remote * * @return List of media-lines */ const struct list *sdp_session_medial(const struct sdp_session *sess, bool local) { if (!sess) return NULL; return local ? &sess->lmedial : &sess->medial; } /** * Print SDP Session debug information * * @param pf Print function for output * @param sess SDP Session * * @return 0 if success, otherwise errorcode */ int sdp_session_debug(struct re_printf *pf, const struct sdp_session *sess) { struct le *le; int err; if (!sess) return 0; err = re_hprintf(pf, "SDP session\n"); err |= re_hprintf(pf, " local attributes:\n"); for (le=sess->lattrl.head; le; le=le->next) err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data); err |= re_hprintf(pf, " remote attributes:\n"); err |= re_hprintf(pf, " remote direction: %s\n", sdp_dir_name(sess->rdir)); for (le=sess->rattrl.head; le; le=le->next) err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data); err |= re_hprintf(pf, "session media:\n"); for (le=sess->medial.head; le; le=le->next) err |= sdp_media_debug(pf, le->data); err |= re_hprintf(pf, "local media:\n"); for (le=sess->lmedial.head; le; le=le->next) err |= sdp_media_debug(pf, le->data); return err; } ================================================ FILE: src/sdp/str.c ================================================ /** * @file sdp/str.c SDP strings * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include const char sdp_attr_fmtp[] = "fmtp"; /**< fmtp */ const char sdp_attr_maxptime[] = "maxptime"; /**< maxptime */ const char sdp_attr_ptime[] = "ptime"; /**< ptime */ const char sdp_attr_rtcp[] = "rtcp"; /**< rtcp */ const char sdp_attr_rtpmap[] = "rtpmap"; /**< rtpmap */ const char sdp_media_audio[] = "audio"; /**< Media type 'audio' */ const char sdp_media_video[] = "video"; /**< Media type 'video' */ const char sdp_media_text[] = "text"; /**< Media type 'text' */ const char sdp_proto_rtpavp[] = "RTP/AVP"; /**< RTP Profile */ const char sdp_proto_rtpsavp[] = "RTP/SAVP"; /**< Secure RTP Profile */ /** * Get the SDP media direction name * * @param dir Media direction * * @return Name of media direction */ const char *sdp_dir_name(enum sdp_dir dir) { switch (dir) { case SDP_INACTIVE: return "inactive"; case SDP_RECVONLY: return "recvonly"; case SDP_SENDONLY: return "sendonly"; case SDP_SENDRECV: return "sendrecv"; default: return "??"; } } /** * Get the SDP bandwidth name * * @param type Bandwidth type * * @return Bandwidth name */ const char *sdp_bandwidth_name(enum sdp_bandwidth type) { switch (type) { case SDP_BANDWIDTH_CT: return "CT"; case SDP_BANDWIDTH_AS: return "AS"; case SDP_BANDWIDTH_RS: return "RS"; case SDP_BANDWIDTH_RR: return "RR"; case SDP_BANDWIDTH_TIAS: return "TIAS"; default: return "??"; } } ================================================ FILE: src/sdp/util.c ================================================ /** * @file sdp/util.c SDP utility functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include /** * Decode an SDP direction * * @param pl SDP direction as string * * @return sdp_dir SDP direction, SDP_SENDRECV as fallback */ enum sdp_dir sdp_dir_decode(const struct pl *pl) { if (!pl_strcmp(pl, "off")) { return SDP_INACTIVE; } else if (!pl_strcmp(pl, "inactive")) { return SDP_INACTIVE; } else if (!pl_strcmp(pl, "sendonly")) { return SDP_SENDONLY; } else if (!pl_strcmp(pl, "recvonly")) { return SDP_RECVONLY; } return SDP_SENDRECV; } /** * Decode RTP Header Extension SDP attribute value * * @param ext Extension-map object * @param val SDP attribute value * * @return 0 for success, otherwise errorcode */ int sdp_extmap_decode(struct sdp_extmap *ext, const char *val) { struct pl id, dir; if (!ext || !val) return EINVAL; if (re_regex(val, strlen(val), "[0-9]+[/]*[a-z]* [^ ]+[ ]*[^ ]*", &id, NULL, &dir, &ext->name, NULL, &ext->attrs)) return EBADMSG; ext->dir_set = false; ext->dir = SDP_SENDRECV; if (pl_isset(&dir)) { ext->dir_set = true; if (!pl_strcmp(&dir, "sendonly")) ext->dir = SDP_SENDONLY; else if (!pl_strcmp(&dir, "sendrecv")) ext->dir = SDP_SENDRECV; else if (!pl_strcmp(&dir, "recvonly")) ext->dir = SDP_RECVONLY; else if (!pl_strcmp(&dir, "inactive")) ext->dir = SDP_INACTIVE; else ext->dir_set = false; } ext->id = pl_u32(&id); return 0; } ================================================ FILE: src/sha/wrap.c ================================================ /** * @file wrap.c SHA wrappers * * Copyright (C) 2022 Alfred E. Heggestad * Copyright (C) 2022 Sebastian Reimers */ #include #include #ifdef USE_OPENSSL #include #elif defined (__APPLE__) #include #elif defined (WIN32) #include #include #elif defined (USE_MBEDTLS) #include #include #include #endif #include #define DEBUG_MODULE "sha" #define DEBUG_LEVEL 5 #include #if !defined (USE_OPENSSL) && defined (WIN32) static void compute_hash(ALG_ID alg_id, const void *data, size_t data_size, uint8_t *md, DWORD hash_size) { HCRYPTPROV context; HCRYPTHASH hash; CryptAcquireContext(&context, 0, 0, PROV_RSA_AES,CRYPT_VERIFYCONTEXT); CryptCreateHash(context, alg_id, 0, 0, &hash); CryptHashData(hash, (BYTE*)data, (DWORD)data_size, 0); CryptGetHashParam(hash, HP_HASHVAL, md, &hash_size, 0); CryptDestroyHash(hash); CryptReleaseContext(context, 0); } #endif /** * Calculate the SHA1 hash from a buffer * * @param d Data buffer (input) * @param n Number of input bytes * @param md Calculated SHA1 hash (output) */ void sha1(const uint8_t *d, size_t n, uint8_t *md) { #ifdef USE_OPENSSL (void)SHA1(d, n, md); #elif defined (__APPLE__) CC_SHA1(d, (uint32_t)n, md); #elif defined (WIN32) compute_hash(CALG_SHA1, d, n, md, SHA1_DIGEST_SIZE); #elif defined (MBEDTLS_MD_C) int err; err = mbedtls_sha1(d, n, md); if (err) DEBUG_WARNING("mbedtls_sha1: %s\n", mbedtls_high_level_strerr(err)); #else (void)d; (void)n; (void)md; #error missing SHA-1 backend #endif } /** * Calculate the SHA256 hash from a buffer * * @param d Data buffer (input) * @param n Number of input bytes * @param md Calculated SHA1 hash (output) */ void sha256(const uint8_t *d, size_t n, uint8_t *md) { #ifdef USE_OPENSSL (void)SHA256(d, n, md); #elif defined (__APPLE__) CC_SHA256(d, (uint32_t)n, md); #elif defined (WIN32) compute_hash(CALG_SHA_256, d, n, md, SHA256_DIGEST_SIZE); #elif defined (MBEDTLS_MD_C) int err; err = mbedtls_sha256(d, n, md, 0); if (err) DEBUG_WARNING("mbedtls_sha256: %s\n", mbedtls_high_level_strerr(err)); #else (void)d; (void)n; (void)md; #error missing SHA-256 backend #endif } /** * Calculate the SHA-256 hash from a formatted string * * @param md Calculated SHA-256 hash * @param fmt Formatted string * * @return 0 if success, otherwise errorcode */ int sha256_printf(uint8_t md[32], const char *fmt, ...) { struct mbuf mb; va_list ap; int err; mbuf_init(&mb); va_start(ap, fmt); err = mbuf_vprintf(&mb, fmt, ap); va_end(ap); if (!err) sha256(mb.buf, mb.end, md); mbuf_reset(&mb); return err; } ================================================ FILE: src/shim/shim.c ================================================ /** * @file re_shim.h Interface to SHIM layer * * Copyright (C) 2015 - 2022 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #define DEBUG_MODULE "shim" #define DEBUG_LEVEL 5 #include struct shim { struct tcp_conn *tc; struct tcp_helper *th; struct mbuf *mb; shim_frame_h *frameh; void *arg; uint64_t n_tx; uint64_t n_rx; }; /* responsible for adding the SHIM header - assumes that the sent MBUF contains a complete packet */ static bool shim_send_handler(int *err, struct mbuf *mb, void *arg) { struct shim *shim = arg; int err_len; uint16_t len; (void)shim; if (mb->pos < SHIM_HDR_SIZE) { DEBUG_WARNING("send: not enough space for SHIM header\n"); *err = ENOMEM; return true; } err_len = try_into_u16_from_size(&len, mbuf_get_left(mb)); if (err_len) { DEBUG_WARNING("send: mbuf to big\n"); *err = err_len; return true; } mb->pos -= SHIM_HDR_SIZE; *err = mbuf_write_u16(mb, htons(len)); mb->pos -= SHIM_HDR_SIZE; ++shim->n_tx; return false; } static bool shim_recv_handler(int *errp, struct mbuf *mbx, bool *estab, void *arg) { struct shim *shim = arg; int err = 0; (void)estab; /* handle re-assembly */ if (!shim->mb) { shim->mb = mbuf_alloc(1024); if (!shim->mb) { *errp = ENOMEM; return true; } } if (shim->mb) { size_t pos; pos = shim->mb->pos; shim->mb->pos = shim->mb->end; err = mbuf_write_mem(shim->mb, mbuf_buf(mbx), mbuf_get_left(mbx)); if (err) goto out; shim->mb->pos = pos; } /* extract all SHIM-frames in the TCP-stream */ for (;;) { size_t start, len, pos, end; bool hdld; start = shim->mb->pos; if (mbuf_get_left(shim->mb) < (SHIM_HDR_SIZE)) break; len = ntohs(mbuf_read_u16(shim->mb)); if (mbuf_get_left(shim->mb) < len) goto rewind; pos = shim->mb->pos; end = shim->mb->end; shim->mb->end = pos + len; ++shim->n_rx; hdld = shim->frameh(shim->mb, shim->arg); if (!hdld) { /* XXX: handle multiple frames per segment */ shim->mb->pos = pos; shim->mb->end = pos + len; mbx->pos = mbx->end = 2; err = mbuf_write_mem(mbx, mbuf_buf(shim->mb), len); if (err) goto out; mbx->pos = 2; shim->mb->pos = pos + len; shim->mb->end = end; return false; /* continue recv-handlers */ } shim->mb->pos = pos + len; shim->mb->end = end; if (shim->mb->pos >= shim->mb->end) { shim->mb = mem_deref(shim->mb); break; } continue; rewind: shim->mb->pos = start; break; } out: if (err) *errp = err; return true; /* always handled */ } static void destructor(void *arg) { struct shim *shim = arg; mem_deref(shim->th); mem_deref(shim->tc); mem_deref(shim->mb); } int shim_insert(struct shim **shimp, struct tcp_conn *tc, int layer, shim_frame_h *frameh, void *arg) { struct shim *shim; int err; if (!shimp || !tc || !frameh) return EINVAL; shim = mem_zalloc(sizeof(*shim), destructor); if (!shim) return ENOMEM; shim->tc = mem_ref(tc); err = tcp_register_helper(&shim->th, tc, layer, NULL, shim_send_handler, shim_recv_handler, shim); if (err) goto out; shim->frameh = frameh; shim->arg = arg; out: if (err) mem_deref(shim); else *shimp = shim; return err; } int shim_debug(struct re_printf *pf, const struct shim *shim) { if (!shim) return 0; return re_hprintf(pf, "tx=%llu, rx=%llu", shim->n_tx, shim->n_rx); } ================================================ FILE: src/sip/addr.c ================================================ /** * @file sip/addr.c SIP Address decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include /** * Decode a pointer-length string into a SIP Address object * * @param addr SIP Address object * @param pl Pointer-length string * * @return 0 for success, otherwise errorcode */ int sip_addr_decode(struct sip_addr *addr, const struct pl *pl) { int err; if (!addr || !pl) return EINVAL; memset(addr, 0, sizeof(*addr)); if (0 == re_regex(pl->p, pl->l, "[~ \t\r\n<]*[ \t\r\n]*<[^>]+>[^]*", &addr->dname, NULL, &addr->auri, &addr->params)) { if (!addr->dname.l) addr->dname.p = NULL; if (!addr->params.l) addr->params.p = NULL; } else { memset(addr, 0, sizeof(*addr)); if (re_regex(pl->p, pl->l, "[^;]+[^]*", &addr->auri, &addr->params)) return EBADMSG; } err = uri_decode(&addr->uri, &addr->auri); if (err) memset(addr, 0, sizeof(*addr)); return err; } ================================================ FILE: src/sip/auth.c ================================================ /** * @file sip/auth.c SIP Authentication * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" #define DEBUG_MODULE "sip_auth" #define DEBUG_LEVEL 5 #include enum { NONCE_EXPIRES = 300, NONCE_MIN_SIZE = 33, }; struct sip_auth { struct list realml; sip_auth_h *authh; void *arg; bool ref; int err; }; struct realm { struct le le; char *realm; char *nonce; char *qop; char *opaque; char *user; char *pass; char *algorithm; uint32_t nc; enum sip_hdrid hdr; }; static int dummy_handler(char **user, char **pass, const char *rlm, void *arg) { (void)user; (void)pass; (void)rlm; (void)arg; return EAUTH; } static void realm_destructor(void *arg) { struct realm *realm = arg; list_unlink(&realm->le); mem_deref(realm->realm); mem_deref(realm->nonce); mem_deref(realm->qop); mem_deref(realm->opaque); mem_deref(realm->user); mem_deref(realm->pass); mem_deref(realm->algorithm); } static void auth_destructor(void *arg) { struct sip_auth *auth = arg; if (auth->ref) mem_deref(auth->arg); list_flush(&auth->realml); } static int mkdigest(struct mbuf **digestp, const struct realm *realm, const char *met, const char *uri, uint64_t cnonce) { struct mbuf *digest; uint8_t *ha1 = NULL, *ha2 = NULL; digest_printf_h *digest_printf; int err; bool use_sha256 = str_casecmp(realm->algorithm, "sha-256") == 0; size_t h_size = use_sha256 ? SHA256_DIGEST_SIZE : MD5_SIZE; digest = mbuf_alloc(h_size); if (!digest) return ENOMEM; mbuf_set_end(digest, h_size); ha1 = mem_zalloc(h_size, NULL); if (!ha1) { err = ENOMEM; goto out; } ha2 = mem_zalloc(h_size, NULL); if (!ha2) { err = ENOMEM; goto out; } if (use_sha256) digest_printf = &sha256_printf; else digest_printf = &md5_printf; err = digest_printf(ha1, "%s:%s:%s", realm->user, realm->realm, realm->pass); if (err) goto out; err = digest_printf(ha2, "%s:%s", met, uri); if (err) goto out; if (realm->qop) err = digest_printf( mbuf_buf(digest), "%w:%s:%08x:%016llx:auth:%w", ha1, h_size, realm->nonce, realm->nc, cnonce, ha2, h_size); else err = digest_printf(mbuf_buf(digest), "%w:%s:%w", ha1, h_size, realm->nonce, ha2, h_size); out: mem_deref(ha1); mem_deref(ha2); if (err) mem_deref(digest); else *digestp = digest; return err; } static bool cmp_handler(struct le *le, void *arg) { struct realm *realm = le->data; struct pl *chrealm = arg; /* handle multiple authenticate headers with equal realm value */ if (realm->nc == 1) return false; return 0 == pl_strcasecmp(chrealm, realm->realm); } static bool auth_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { struct httpauth_digest_chall ch; struct sip_auth *auth = arg; struct realm *realm = NULL; int err; (void)msg; if (httpauth_digest_challenge_decode(&ch, &hdr->val)) { err = EBADMSG; goto out; } if (!pl_isset(&ch.algorithm)) pl_set_str(&ch.algorithm, "MD5"); if (pl_strcasecmp(&ch.algorithm, "md5") && pl_strcasecmp(&ch.algorithm, "sha-256")) { err = ENOSYS; goto out; } realm = list_ledata(list_apply(&auth->realml, true, cmp_handler, &ch.realm)); if (!realm) { realm = mem_zalloc(sizeof(*realm), realm_destructor); if (!realm) { err = ENOMEM; goto out; } list_append(&auth->realml, &realm->le, realm); err = pl_strdup(&realm->realm, &ch.realm); if (err) goto out; err = pl_strdup(&realm->algorithm, &ch.algorithm); if (err) goto out; err = auth->authh(&realm->user, &realm->pass, realm->realm, auth->arg); if (err) goto out; } else { if (!pl_isset(&ch.stale) || pl_strcasecmp(&ch.stale, "true")) { err = EAUTH; goto out; } realm->nonce = mem_deref(realm->nonce); realm->qop = mem_deref(realm->qop); realm->opaque = mem_deref(realm->opaque); } realm->hdr = hdr->id; realm->nc = 1; err = pl_strdup(&realm->nonce, &ch.nonce); if (pl_isset(&ch.qop)) err |= pl_strdup(&realm->qop, &ch.qop); if (pl_isset(&ch.opaque)) err |= pl_strdup(&realm->opaque, &ch.opaque); out: if (err) { mem_deref(realm); auth->err = err; return true; } return false; } /** * Update a SIP authentication state from a SIP message * * @param auth SIP Authentication state * @param msg SIP Message * * @return 0 if success, otherwise errorcode */ int sip_auth_authenticate(struct sip_auth *auth, const struct sip_msg *msg) { if (!auth || !msg) return EINVAL; if (sip_msg_hdr_apply(msg, true, SIP_HDR_WWW_AUTHENTICATE, auth_handler, auth)) return auth->err; if (sip_msg_hdr_apply(msg, true, SIP_HDR_PROXY_AUTHENTICATE, auth_handler, auth)) return auth->err; return 0; } int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met, const char *uri) { struct le *le; int err = 0; if (!mb || !auth || !met || !uri) return EINVAL; for (le = auth->realml.head; le; le = le->next) { const uint64_t cnonce = rand_u64(); struct realm *realm = le->data; struct mbuf *digest = NULL; err = mkdigest(&digest, realm, met, uri, cnonce); if (err) break; switch (realm->hdr) { case SIP_HDR_WWW_AUTHENTICATE: err = mbuf_write_str(mb, "Authorization: "); break; case SIP_HDR_PROXY_AUTHENTICATE: err = mbuf_write_str(mb, "Proxy-Authorization: "); break; default: continue; } err |= mbuf_printf(mb, "Digest username=\"%s\"", realm->user); err |= mbuf_printf(mb, ", realm=\"%s\"", realm->realm); err |= mbuf_printf(mb, ", nonce=\"%s\"", realm->nonce); err |= mbuf_printf(mb, ", uri=\"%s\"", uri); err |= mbuf_printf(mb, ", response=\"%w\"", digest->buf, digest->end); digest = mem_deref(digest); if (realm->opaque) err |= mbuf_printf(mb, ", opaque=\"%s\"", realm->opaque); if (realm->qop) { err |= mbuf_printf(mb, ", cnonce=\"%016llx\"", cnonce); err |= mbuf_write_str(mb, ", qop=auth"); err |= mbuf_printf(mb, ", nc=%08x", realm->nc); } ++realm->nc; err |= mbuf_printf(mb, ", algorithm=%s", realm->algorithm); err |= mbuf_write_str(mb, "\r\n"); if (err) break; } return err; } /** * Allocate a SIP authentication state * * @param authp Pointer to allocated SIP authentication state * @param authh Authentication handler * @param arg Handler argument * @param ref True to mem_ref() argument * * @return 0 if success, otherwise errorcode */ int sip_auth_alloc(struct sip_auth **authp, sip_auth_h *authh, void *arg, bool ref) { struct sip_auth *auth; if (!authp) return EINVAL; auth = mem_zalloc(sizeof(*auth), auth_destructor); if (!auth) return ENOMEM; auth->authh = authh ? authh : dummy_handler; auth->arg = ref ? mem_ref(arg) : arg; auth->ref = ref; *authp = auth; return 0; } /** * Reset a SIP authentication state * * @param auth SIP Authentication state */ void sip_auth_reset(struct sip_auth *auth) { if (!auth) return; list_flush(&auth->realml); } static int gen_nonce(char **noncep, time_t ts, const struct sa *src, const char *realm) { uint8_t key[MD5_SIZE]; struct mbuf *mb; int err; mb = mbuf_alloc(40); if (!mb) return ENOMEM; err = mbuf_printf(mb,"%lu%j%s", (long unsigned)ts, src, realm); if (err) goto out; md5(mb->buf, mb->end, key); mbuf_rewind(mb); err = mbuf_printf(mb,"%w%016lx", key, sizeof(key), (long unsigned)ts); if (err) goto out; mbuf_set_pos(mb, 0); err = mbuf_strdup(mb, noncep, mbuf_get_left(mb)); out: mem_deref(mb); return err; } static int check_nonce(const struct pl *nonce, const struct sa *src, const char *realm) { struct pl pl; time_t ts; char *comp = NULL; bool eq; int err; if (!nonce || !nonce->p || nonce->l < NONCE_MIN_SIZE) return EINVAL; pl = *nonce; pl.p = pl.p + (pl.l - 16); pl.l = 16; ts = (time_t) pl_x64(&pl); if (time(NULL) - ts > NONCE_EXPIRES) return ETIMEDOUT; err = gen_nonce(&comp, ts, src, realm); if (err) return err; eq = !pl_strcmp(nonce, comp); mem_deref(comp); return eq ? 0 : EAUTH; } int sip_uas_auth_print(struct re_printf *pf, const struct sip_uas_auth *auth) { return re_hprintf(pf, "WWW-Authenticate: " "Digest realm=\"%s\", nonce=\"%s\", " "algorithm=MD5, " "qop=\"auth\"%s" "\r\n", auth->realm, auth->nonce, auth->stale ? ", stale=true" : ""); } static void sip_uas_destructor(void *arg) { struct sip_uas_auth *auth = arg; mem_deref(auth->nonce); } int sip_uas_auth_gen(struct sip_uas_auth **authp, const struct sip_msg *msg, const char *realm) { struct sip_uas_auth *auth; int err; if (!authp || !msg) return EINVAL; auth = mem_zalloc(sizeof(*auth), sip_uas_destructor); if (!auth) return ENOMEM; auth->realm = realm; err = gen_nonce(&auth->nonce, time(NULL), &msg->src, realm); if (err) mem_deref(auth); else *authp = auth; return err; } int sip_uas_auth_check(struct sip_uas_auth *auth, const struct sip_msg *msg, sip_uas_auth_h *authh, void *arg) { struct httpauth_digest_resp resp; const struct sip_hdr *hdr; uint8_t ha1[MD5_SIZE]; int err; if (!msg || !auth || !authh) return EINVAL; hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_AUTHORIZATION, NULL, NULL); if (!hdr) return EAUTH; if (httpauth_digest_response_decode(&resp, &hdr->val)) return EINVAL; if (pl_strcasecmp(&resp.realm, auth->realm)) return EINVAL; err = check_nonce(&resp.nonce, &msg->src, auth->realm); if (err == ETIMEDOUT || err == EAUTH) { auth->stale = true; return EAUTH; } else if (err) { return err; } if (authh(ha1, &resp.username, auth->realm, arg)) return EINVAL; if (httpauth_digest_response_auth(&resp, &msg->met, ha1)) return EACCES; return 0; } ================================================ FILE: src/sip/contact.c ================================================ /** * @file sip/contact.c SIP contact functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include /** * Set contact parameters * * @param contact SIP Contact object * @param uri Username or URI * @param addr IP-address and port * @param tp SIP Transport */ void sip_contact_set(struct sip_contact *contact, const char *uri, const struct sa *addr, enum sip_transp tp) { if (!contact) return; contact->uri = uri; contact->addr = addr; contact->tp = tp; } /** * Print contact header * * @param pf Print function * @param contact SIP Contact object * * @return 0 for success, otherwise errorcode */ int sip_contact_print(struct re_printf *pf, const struct sip_contact *contact) { if (!contact) return 0; if (contact->uri && strchr(contact->uri, ':')) return re_hprintf(pf, "Contact: <%s>\r\n", contact->uri); else return re_hprintf(pf, "Contact: \r\n", contact->uri, contact->addr, sip_transp_param(contact->tp)); } ================================================ FILE: src/sip/cseq.c ================================================ /** * @file cseq.c SIP CSeq decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include /** * Decode a pointer-length string into a SIP CSeq header * * @param cseq SIP CSeq header * @param pl Pointer-length string * * @return 0 for success, otherwise errorcode */ int sip_cseq_decode(struct sip_cseq *cseq, const struct pl *pl) { struct pl num; int err; if (!cseq || !pl) return EINVAL; err = re_regex(pl->p, pl->l, "[0-9]+[ \t\r\n]+[^ \t\r\n]+", &num, NULL, &cseq->met); if (err) return err; cseq->num = pl_u32(&num); return 0; } ================================================ FILE: src/sip/ctrans.c ================================================ /** * @file sip/ctrans.c SIP Client Transaction * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" enum state { TRYING, CALLING, PROCEEDING, COMPLETED, }; enum { COMPLETE_WAIT = 32000, }; struct sip_ctrans { struct le he; struct sa dst; struct tmr tmr; struct tmr tmre; struct sip *sip; struct mbuf *mb; struct mbuf *mb_ack; struct sip_msg *req; struct sip_connqent *qent; char *met; char *branch; char *host; sip_conn_h *connh; sip_resp_h *resph; void *arg; enum sip_transp tp; enum state state; uint32_t txc; bool invite; }; static bool route_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { (void)msg; return 0 != mbuf_printf(arg, "Route: %r\r\n", &hdr->val); } static int request_copy(struct mbuf **mbp, struct sip_ctrans *ct, const char *met, const struct sip_msg *resp) { struct mbuf *mb; int err; if (!ct->req) { err = sip_msg_decode(&ct->req, ct->mb); if (err) return err; } mb = mbuf_alloc(1024); if (!mb) return ENOMEM; err = mbuf_printf(mb, "%s %r SIP/2.0\r\n", met, &ct->req->ruri); err |= mbuf_printf(mb, "Via: %r\r\n", &ct->req->via.val); err |= mbuf_write_str(mb, "Max-Forwards: 70\r\n"); err |= sip_msg_hdr_apply(ct->req, true, SIP_HDR_ROUTE, route_handler, mb) ? ENOMEM : 0; err |= mbuf_printf(mb, "To: %r\r\n", resp ? &resp->to.val : &ct->req->to.val); err |= mbuf_printf(mb, "From: %r\r\n", &ct->req->from.val); err |= mbuf_printf(mb, "Call-ID: %r\r\n", &ct->req->callid); err |= mbuf_printf(mb, "CSeq: %u %s\r\n", ct->req->cseq.num, met); if (ct->sip->software) err |= mbuf_printf(mb, "User-Agent: %s\r\n",ct->sip->software); err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n"); mb->pos = 0; if (err) mem_deref(mb); else *mbp = mb; return err; } static void destructor(void *arg) { struct sip_ctrans *ct = arg; hash_unlink(&ct->he); tmr_cancel(&ct->tmr); tmr_cancel(&ct->tmre); mem_deref(ct->met); mem_deref(ct->branch); mem_deref(ct->host); mem_deref(ct->qent); mem_deref(ct->req); mem_deref(ct->mb); mem_deref(ct->mb_ack); } static bool cmp_handler(struct le *le, void *arg) { struct sip_ctrans *ct = le->data; const struct sip_msg *msg = arg; if (pl_strcmp(&msg->via.branch, ct->branch)) return false; if (pl_strcmp(&msg->cseq.met, ct->met)) return false; return true; } static void dummy_handler(int err, const struct sip_msg *msg, void *arg) { (void)err; (void)msg; (void)arg; } static void terminate(struct sip_ctrans *ct, int err) { switch (ct->state) { case TRYING: case CALLING: case PROCEEDING: ct->resph(err, NULL, ct->arg); break; default: break; } } static void transport_handler(int err, void *arg) { struct sip_ctrans *ct = arg; terminate(ct, err); mem_deref(ct); } static void tmr_handler(void *arg) { struct sip_ctrans *ct = arg; terminate(ct, ETIMEDOUT); mem_deref(ct); } static int connect_handler(struct sa *src, const struct sa *dst, struct mbuf *mb, void *arg) { struct sip_ctrans *ct = arg; if (ct->connh) return ct->connh(src, dst, mb, ct->arg); return 0; } static void retransmit_handler(void *arg) { struct sip_ctrans *ct = arg; uint32_t timeout; int err; ct->txc++; switch (ct->state) { case TRYING: timeout = MIN(SIP_T1<txc, SIP_T2); break; case CALLING: timeout = SIP_T1<txc; break; case PROCEEDING: timeout = SIP_T2; break; default: return; } tmr_start(&ct->tmre, timeout, retransmit_handler, ct); err = sip_transp_send(&ct->qent, ct->sip, NULL, ct->tp, &ct->dst, ct->host, ct->mb, connect_handler, transport_handler, ct); if (err) { terminate(ct, err); mem_deref(ct); } } static void invite_response(struct sip_ctrans *ct, const struct sip_msg *msg) { switch (ct->state) { case CALLING: tmr_cancel(&ct->tmr); tmr_cancel(&ct->tmre); /*@fallthrough@*/ case PROCEEDING: if (msg->scode < 200) { ct->state = PROCEEDING; ct->resph(0, msg, ct->arg); } else if (msg->scode < 300) { ct->resph(0, msg, ct->arg); mem_deref(ct); } else { ct->state = COMPLETED; (void)request_copy(&ct->mb_ack, ct, "ACK", msg); (void)sip_send(ct->sip, NULL, ct->tp, &ct->dst, ct->mb_ack); ct->resph(0, msg, ct->arg); if (sip_transp_reliable(ct->tp)) { mem_deref(ct); break; } tmr_start(&ct->tmr, COMPLETE_WAIT, tmr_handler, ct); } break; case COMPLETED: if (msg->scode < 300) break; (void)sip_send(ct->sip, NULL, ct->tp, &ct->dst, ct->mb_ack); break; default: break; } } static bool response_handler(const struct sip_msg *msg, void *arg) { struct sip_ctrans *ct; struct sip *sip = arg; ct = list_ledata(hash_lookup(sip->ht_ctrans, hash_joaat_pl(&msg->via.branch), cmp_handler, (void *)msg)); if (!ct) return false; if (ct->invite) { invite_response(ct, msg); return true; } switch (ct->state) { case TRYING: case PROCEEDING: if (msg->scode < 200) { ct->state = PROCEEDING; ct->resph(0, msg, ct->arg); } else { ct->state = COMPLETED; ct->resph(0, msg, ct->arg); if (sip_transp_reliable(ct->tp)) { mem_deref(ct); break; } tmr_start(&ct->tmr, SIP_T4, tmr_handler, ct); tmr_cancel(&ct->tmre); } break; default: break; } return true; } int sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip, enum sip_transp tp, const struct sa *dst, char *met, char *branch, char *host, struct mbuf *mb, sip_conn_h *connh, sip_resp_h *resph, void *arg) { struct sip_ctrans *ct; int err; if (!sip || !dst || !met || !branch || !mb) return EINVAL; ct = mem_zalloc(sizeof(*ct), destructor); if (!ct) return ENOMEM; hash_append(sip->ht_ctrans, hash_joaat_str(branch), &ct->he, ct); ct->invite = !strcmp(met, "INVITE"); ct->branch = mem_ref(branch); ct->host = mem_ref(host); ct->met = mem_ref(met); ct->mb = mem_ref(mb); ct->dst = *dst; ct->tp = tp; ct->sip = sip; ct->state = ct->invite ? CALLING : TRYING; ct->connh = connh; ct->resph = resph ? resph : dummy_handler; ct->arg = arg; err = sip_transp_send(&ct->qent, sip, NULL, tp, dst, host, mb, connect_handler, transport_handler, ct); if (err) goto out; tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct); if (!sip_transp_reliable(ct->tp)) tmr_start(&ct->tmre, SIP_T1, retransmit_handler, ct); out: if (err) mem_deref(ct); else if (ctp) *ctp = ct; return err; } int sip_ctrans_cancel(struct sip_ctrans *ct) { struct mbuf *mb = NULL; char *cancel = NULL; int err; if (!ct) return EINVAL; if (!ct->invite) return 0; switch (ct->state) { case PROCEEDING: tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct); break; default: return EPROTO; } err = str_dup(&cancel, "CANCEL"); if (err) goto out; err = request_copy(&mb, ct, cancel, NULL); if (err) goto out; err = sip_ctrans_request(NULL, ct->sip, ct->tp, &ct->dst, cancel, ct->branch, NULL, mb, NULL, NULL, NULL); if (err) goto out; out: mem_deref(cancel); mem_deref(mb); return err; } int sip_ctrans_init(struct sip *sip, uint32_t sz) { int err; err = sip_listen(NULL, sip, false, response_handler, sip); if (err) return err; return hash_alloc(&sip->ht_ctrans, sz); } static const char *statename(enum state state) { switch (state) { case TRYING: return "TRYING"; case CALLING: return "CALLING"; case PROCEEDING: return "PROCEEDING"; case COMPLETED: return "COMPLETED"; default: return "???"; } } static bool debug_handler(struct le *le, void *arg) { struct sip_ctrans *ct = le->data; struct re_printf *pf = arg; (void)re_hprintf(pf, " %-10s %-10s %2llus (%s)\n", ct->met, statename(ct->state), tmr_get_expire(&ct->tmr)/1000, ct->branch); return false; } int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip) { int err; err = re_hprintf(pf, "client transactions:\n"); hash_apply(sip->ht_ctrans, debug_handler, pf); return err; } ================================================ FILE: src/sip/dialog.c ================================================ /** * @file dialog.c SIP Dialog * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" enum { ROUTE_OFFSET = 7, }; struct sip_dialog { struct uri route; struct mbuf *mb; char *callid; char *ltag; char *rtag; char *uri; uint32_t hash; uint32_t lseq; uint32_t rseq; uint32_t lseqinv; size_t cpos; size_t rpos; enum sip_transp tp; uint32_t srcport; }; struct route_enc { struct mbuf *mb; size_t end; }; static void destructor(void *arg) { struct sip_dialog *dlg = arg; mem_deref(dlg->callid); mem_deref(dlg->ltag); mem_deref(dlg->rtag); mem_deref(dlg->uri); mem_deref(dlg->mb); } /** * Allocate a SIP Dialog * * @param dlgp Pointer to allocated SIP Dialog * @param uri Target URI * @param to_uri To URI * @param from_name From displayname (optional) * @param from_uri From URI * @param routev Route vector * @param routec Route count * * @return 0 if success, otherwise errorcode */ int sip_dialog_alloc(struct sip_dialog **dlgp, const char *uri, const char *to_uri, const char *from_name, const char *from_uri, const char *routev[], uint32_t routec) { const uint64_t ltag = rand_u64(); struct sip_dialog *dlg; struct sip_addr addr; size_t rend = 0; struct pl pl; uint32_t i; int err; if (!dlgp || !uri || !to_uri || !from_uri) return EINVAL; dlg = mem_zalloc(sizeof(*dlg), destructor); if (!dlg) return ENOMEM; dlg->hash = hash_fast_str(from_uri); dlg->lseq = rand_u16(); dlg->tp = SIP_TRANSP_NONE; err = str_dup(&dlg->uri, uri); if (err) goto out; err = str_x64dup(&dlg->callid, rand_u64()); if (err) goto out; err = str_x64dup(&dlg->ltag, ltag); if (err) goto out; dlg->mb = mbuf_alloc(512); if (!dlg->mb) { err = ENOMEM; goto out; } for (i=0; imb, "Route: <%s;lr>\r\n", routev[i]); if (i == 0) rend = dlg->mb->pos - 2; } dlg->rpos = dlg->mb->pos; err |= mbuf_printf(dlg->mb, "To: <%s>\r\n", to_uri); dlg->cpos = dlg->mb->pos; err |= mbuf_printf(dlg->mb, "From: %s%s%s<%s>;tag=%016llx\r\n", from_name ? "\"" : "", from_name, from_name ? "\" " : "", from_uri, ltag); if (err) goto out; dlg->mb->pos = 0; if (rend) { pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; pl.l = rend - ROUTE_OFFSET; err = sip_addr_decode(&addr, &pl); dlg->route = addr.uri; } else { pl_set_str(&pl, dlg->uri); err = uri_decode(&dlg->route, &pl); } out: if (err) mem_deref(dlg); else *dlgp = dlg; return err; } static bool record_route_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { struct route_enc *renc = arg; (void)msg; if (mbuf_printf(renc->mb, "Route: %r\r\n", &hdr->val)) return true; if (!renc->end) renc->end = renc->mb->pos - 2; return false; } /** * Accept and create a SIP Dialog from an incoming SIP Message * * @param dlgp Pointer to allocated SIP Dialog * @param msg SIP Message * * @return 0 if success, otherwise errorcode */ int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg) { const struct sip_hdr *contact; struct sip_dialog *dlg; struct route_enc renc; struct sip_addr addr; struct pl pl; int err; if (!dlgp || !msg || !msg->req) return EINVAL; contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); if (!contact || !msg->callid.p) return EBADMSG; if (sip_addr_decode(&addr, &contact->val)) return EBADMSG; dlg = mem_zalloc(sizeof(*dlg), destructor); if (!dlg) return ENOMEM; dlg->hash = rand_u32(); dlg->lseq = rand_u16(); dlg->rseq = msg->cseq.num; dlg->tp = msg->tp; err = pl_strdup(&dlg->uri, &addr.auri); if (err) goto out; err = pl_strdup(&dlg->callid, &msg->callid); if (err) goto out; err = str_x64dup(&dlg->ltag, msg->tag); if (err) goto out; err = pl_strdup(&dlg->rtag, &msg->from.tag); if (err) goto out; dlg->mb = mbuf_alloc(512); if (!dlg->mb) { err = ENOMEM; goto out; } renc.mb = dlg->mb; renc.end = 0; err |= sip_msg_hdr_apply(msg, true, SIP_HDR_RECORD_ROUTE, record_route_handler, &renc) ? ENOMEM : 0; dlg->rpos = dlg->mb->pos; err |= mbuf_printf(dlg->mb, "To: %r\r\n", &msg->from.val); dlg->cpos = dlg->mb->pos; err |= mbuf_printf(dlg->mb, "From: %r;tag=%016llx\r\n", &msg->to.val, msg->tag); if (err) goto out; dlg->mb->pos = 0; if (renc.end) { pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; pl.l = renc.end - ROUTE_OFFSET; err = sip_addr_decode(&addr, &pl); dlg->route = addr.uri; } else { pl_set_str(&pl, dlg->uri); err = uri_decode(&dlg->route, &pl); } out: if (err) mem_deref(dlg); else *dlgp = dlg; return err; } /** * Initialize a SIP Dialog from an incoming SIP Message * * @param dlg SIP Dialog to initialize * @param msg SIP Message * * @return 0 if success, otherwise errorcode */ int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg) { char *uri = NULL, *rtag = NULL; const struct sip_hdr *contact; struct route_enc renc; struct sip_addr addr; struct pl pl; int err; if (!dlg || dlg->rtag || !dlg->cpos || !msg) return EINVAL; contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); if (!contact) return EBADMSG; if (sip_addr_decode(&addr, &contact->val)) return EBADMSG; renc.mb = mbuf_alloc(512); if (!renc.mb) return ENOMEM; err = pl_strdup(&uri, &addr.auri); if (err) goto out; err = pl_strdup(&rtag, msg->req ? &msg->from.tag : &msg->to.tag); if (err) goto out; renc.end = 0; err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE, record_route_handler, &renc) ? ENOMEM : 0; dlg->rpos = renc.mb->pos; err |= mbuf_printf(renc.mb, "To: %r\r\n", msg->req ? &msg->from.val : &msg->to.val); dlg->mb->pos = dlg->cpos; dlg->cpos = renc.mb->pos; err |= mbuf_write_mem(renc.mb, mbuf_buf(dlg->mb), mbuf_get_left(dlg->mb)); dlg->mb->pos = 0; if (err) goto out; renc.mb->pos = 0; if (renc.end) { pl.p = (const char *)mbuf_buf(renc.mb) + ROUTE_OFFSET; pl.l = renc.end - ROUTE_OFFSET; err = sip_addr_decode(&addr, &pl); if (err) goto out; dlg->route = addr.uri; } else { struct uri tmp; pl_set_str(&pl, uri); err = uri_decode(&tmp, &pl); if (err) goto out; dlg->route = tmp; } mem_deref(dlg->mb); mem_deref(dlg->uri); dlg->mb = mem_ref(renc.mb); dlg->rtag = mem_ref(rtag); dlg->uri = mem_ref(uri); dlg->rseq = msg->req ? msg->cseq.num : 0; dlg->tp = msg->tp; out: mem_deref(renc.mb); mem_deref(rtag); mem_deref(uri); return err; } /** * Fork a SIP Dialog from an incoming SIP Message * * @param dlgp Pointer to allocated SIP Dialog * @param odlg Original SIP Dialog * @param msg SIP Message * * @return 0 if success, otherwise errorcode */ int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg, const struct sip_msg *msg) { const struct sip_hdr *contact; struct sip_dialog *dlg; struct route_enc renc; struct sip_addr addr; struct pl pl; int err; if (!dlgp || !odlg || !odlg->cpos || !msg) return EINVAL; contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); if (!contact || !msg->callid.p) return EBADMSG; if (sip_addr_decode(&addr, &contact->val)) return EBADMSG; dlg = mem_zalloc(sizeof(*dlg), destructor); if (!dlg) return ENOMEM; dlg->callid = mem_ref(odlg->callid); dlg->ltag = mem_ref(odlg->ltag); dlg->hash = odlg->hash; dlg->lseq = odlg->lseq; dlg->lseqinv = odlg->lseqinv; dlg->rseq = msg->req ? msg->cseq.num : 0; dlg->tp = msg->tp; err = pl_strdup(&dlg->uri, &addr.auri); if (err) goto out; err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag); if (err) goto out; dlg->mb = mbuf_alloc(512); if (!dlg->mb) { err = ENOMEM; goto out; } renc.mb = dlg->mb; renc.end = 0; err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE, record_route_handler, &renc) ? ENOMEM : 0; err |= mbuf_printf(dlg->mb, "To: %r\r\n", msg->req ? &msg->from.val : &msg->to.val); odlg->mb->pos = odlg->cpos; err |= mbuf_write_mem(dlg->mb, mbuf_buf(odlg->mb), mbuf_get_left(odlg->mb)); odlg->mb->pos = 0; if (err) goto out; dlg->mb->pos = 0; if (renc.end) { pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; pl.l = renc.end - ROUTE_OFFSET; err = sip_addr_decode(&addr, &pl); dlg->route = addr.uri; } else { pl_set_str(&pl, dlg->uri); err = uri_decode(&dlg->route, &pl); } out: if (err) mem_deref(dlg); else *dlgp = dlg; return err; } /** * Update an existing SIP Dialog from a SIP Message * * @param dlg SIP Dialog to update * @param msg SIP Message * * @return 0 if success, otherwise errorcode */ int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg) { const struct sip_hdr *contact; struct sip_addr addr; struct mbuf *mb; struct pl pl; size_t cpos; char *uri; int err; if (!dlg || !msg) return EINVAL; contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); if (!contact) return EBADMSG; if (sip_addr_decode(&addr, &contact->val)) return EBADMSG; err = pl_strdup(&uri, &addr.auri); if (err) return err; mb = mbuf_alloc(512); if (!mb) { err = ENOMEM; goto out; } err = mbuf_write_mem(mb, mbuf_buf(dlg->mb), dlg->rpos); err |= mbuf_printf(mb, "To: %r\r\n", msg->req ? &msg->from.val : &msg->to.val); cpos = mb->pos; err |= mbuf_write_mem(mb, mbuf_buf(dlg->mb) + dlg->cpos, mbuf_get_left(dlg->mb) - dlg->cpos); if (err) goto out; dlg->cpos = cpos; mb->pos = 0; dlg->rtag = mem_deref(dlg->rtag); err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag); if (err) goto out; mem_deref(dlg->mb); dlg->mb = mem_ref(mb); if (dlg->route.scheme.p == dlg->uri) { struct uri tmp; pl_set_str(&pl, uri); err = uri_decode(&tmp, &pl); if (err) goto out; dlg->route = tmp; } else { pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; pl.l = dlg->rpos - ROUTE_OFFSET; err = sip_addr_decode(&addr, &pl); dlg->route = addr.uri; } mem_deref(dlg->uri); dlg->uri = mem_ref(uri); out: mem_deref(mb); mem_deref(uri); return err; } /** * Check if a remote sequence number is valid * * @param dlg SIP Dialog * @param msg SIP Message * * @return True if valid, False if invalid */ bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg) { if (!dlg || !msg || !msg->req) return false; if (msg->cseq.num < dlg->rseq) return false; dlg->rseq = msg->cseq.num; return true; } int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq, const char *met) { int err = 0; if (!mb || !dlg || !met) return EINVAL; if (!strcmp(met, "INVITE")) dlg->lseqinv = dlg->lseq; err |= mbuf_write_mem(mb, mbuf_buf(dlg->mb), mbuf_get_left(dlg->mb)); err |= mbuf_printf(mb, "Call-ID: %s\r\n", dlg->callid); err |= mbuf_printf(mb, "CSeq: %u %s\r\n", strcmp(met, "ACK") ? dlg->lseq++ : cseq, met); return err; } const char *sip_dialog_uri(const struct sip_dialog *dlg) { return dlg ? dlg->uri : NULL; } const char *sip_dialog_ltag(const struct sip_dialog *dlg) { return dlg ? dlg->ltag : NULL; } const char *sip_dialog_rtag(const struct sip_dialog *dlg) { return dlg ? dlg->rtag : NULL; } const struct uri *sip_dialog_route(const struct sip_dialog *dlg) { return dlg ? &dlg->route : NULL; } uint32_t sip_dialog_hash(const struct sip_dialog *dlg) { return dlg ? dlg->hash : 0; } /** * Get the Call-ID from a SIP Dialog * * @param dlg SIP Dialog * * @return Call-ID string */ const char *sip_dialog_callid(const struct sip_dialog *dlg) { return dlg ? dlg->callid : NULL; } int sip_dialog_set_callid(struct sip_dialog *dlg, const char *callid) { dlg->callid = mem_deref(dlg->callid); return str_dup(&dlg->callid, callid); } /** * Set TCP source port for a SIP Dialog * * @param dlg SIP Dialog * @param srcport The TCP source port to be used */ void sip_dialog_set_srcport(struct sip_dialog *dlg, uint16_t srcport) { if (!dlg) return; dlg->srcport = srcport; } /** * Get TCP source port for a SIP Dialog * * @param dlg SIP Dialog * * @return TCP source port */ uint16_t sip_dialog_srcport(struct sip_dialog *dlg) { if (!dlg) return 0; return dlg->srcport; } /** * Get the local sequence number from a SIP Dialog * * @param dlg SIP Dialog * * @return Local sequence number */ uint32_t sip_dialog_lseq(const struct sip_dialog *dlg) { return dlg ? dlg->lseq : 0; } /** * Get the local sequence number of the last sent INVITE * * @param dlg SIP Dialog * * @return Local sequence number */ uint32_t sip_dialog_lseqinv(const struct sip_dialog *dlg) { return dlg ? dlg->lseqinv : 0; } /** * Check if a SIP Dialog is established * * @param dlg SIP Dialog * * @return True if established, False if not */ bool sip_dialog_established(const struct sip_dialog *dlg) { return dlg && dlg->rtag; } enum sip_transp sip_dialog_tp(const struct sip_dialog *dlg) { return dlg ? dlg->tp : SIP_TRANSP_NONE; } /** * Compare a SIP Dialog against a SIP Message * * @param dlg SIP Dialog * @param msg SIP Message * * @return True if match, False if no match */ bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg) { if (!dlg || !msg) return false; if (pl_strcmp(&msg->callid, dlg->callid)) return false; if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag)) return false; if (pl_strcmp(msg->req ? &msg->from.tag : &msg->to.tag, dlg->rtag)) return false; return true; } /** * Compare a half SIP Dialog against a SIP Message * * @param dlg SIP Dialog * @param msg SIP Message * * @return True if match, False if no match */ bool sip_dialog_cmp_half(const struct sip_dialog *dlg, const struct sip_msg *msg) { if (!dlg || !msg) return false; if (pl_strcmp(&msg->callid, dlg->callid)) return false; if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag)) return false; return true; } ================================================ FILE: src/sip/keepalive.c ================================================ /** * @file sip/keepalive.c SIP Keepalive * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" static void destructor(void *arg) { struct sip_keepalive *ka = arg; if (ka->kap) *ka->kap = NULL; list_unlink(&ka->le); } void sip_keepalive_signal(struct list *kal, int err) { struct le *le = list_head(kal); while (le) { struct sip_keepalive *ka = le->data; sip_keepalive_h *kah = ka->kah; void *arg = ka->arg; le = le->next; list_unlink(&ka->le); mem_deref(ka); kah(err, arg); } } uint64_t sip_keepalive_wait(uint32_t interval) { return (uint64_t) interval * (800 + rand_u16() % 201); } /** * Start a keepalive handler on a SIP transport * * @param kap Pointer to allocated keepalive object * @param sip SIP Stack instance * @param msg SIP Message * @param interval Keepalive interval in seconds * @param kah Keepalive handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sip_keepalive_start(struct sip_keepalive **kap, struct sip *sip, const struct sip_msg *msg, uint32_t interval, sip_keepalive_h *kah, void *arg) { struct sip_keepalive *ka; int err; if (!kap || !sip || !msg || !kah) return EINVAL; ka = mem_zalloc(sizeof(*ka), destructor); if (!ka) return ENOMEM; ka->kah = kah; ka->arg = arg; switch (msg->tp) { case SIP_TRANSP_UDP: err = sip_keepalive_udp(ka, sip, (struct udp_sock *)msg->sock, &msg->src, interval); break; case SIP_TRANSP_TCP: case SIP_TRANSP_TLS: err = sip_keepalive_tcp(ka, (struct sip_conn *)msg->sock, interval); break; default: err = EPROTONOSUPPORT; break; } if (err) { mem_deref(ka); } else { ka->kap = kap; *kap = ka; } return err; } ================================================ FILE: src/sip/keepalive_udp.c ================================================ /** * @file keepalive_udp.c SIP UDP Keepalive * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" enum { UDP_KEEPALIVE_INTVAL = 29, }; struct sip_udpconn { struct le he; struct list kal; struct tmr tmr_ka; struct sa maddr; struct sa paddr; struct udp_sock *us; struct stun_ctrans *ct; struct stun *stun; uint32_t ka_interval; }; static void udpconn_keepalive_handler(void *arg); static void destructor(void *arg) { struct sip_udpconn *uc = arg; list_flush(&uc->kal); hash_unlink(&uc->he); tmr_cancel(&uc->tmr_ka); mem_deref(uc->ct); mem_deref(uc->us); mem_deref(uc->stun); } static void udpconn_close(struct sip_udpconn *uc, int err) { sip_keepalive_signal(&uc->kal, err); hash_unlink(&uc->he); tmr_cancel(&uc->tmr_ka); uc->ct = mem_deref(uc->ct); uc->us = mem_deref(uc->us); uc->stun = mem_deref(uc->stun); } static void stun_response_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct sip_udpconn *uc = arg; struct stun_attr *attr; (void)reason; if (err || scode) { err = err ? err : EPROTO; goto out; } attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr) { attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR); if (!attr) { err = EPROTO; goto out; } } if (!sa_isset(&uc->maddr, SA_ALL)) { uc->maddr = attr->v.sa; } else if (!sa_cmp(&uc->maddr, &attr->v.sa, SA_ALL)) { err = ENOTCONN; goto out; } out: if (err) { udpconn_close(uc, err); mem_deref(uc); } else { tmr_start(&uc->tmr_ka, sip_keepalive_wait(uc->ka_interval), udpconn_keepalive_handler, uc); } } static void udpconn_keepalive_handler(void *arg) { struct sip_udpconn *uc = arg; int err; if (!uc->kal.head) { /* no need for us anymore */ udpconn_close(uc, 0); mem_deref(uc); return; } err = stun_request(&uc->ct, uc->stun, IPPROTO_UDP, uc->us, &uc->paddr, 0, STUN_METHOD_BINDING, NULL, 0, false, stun_response_handler, uc, 1, STUN_ATTR_SOFTWARE, stun_software); if (err) { udpconn_close(uc, err); mem_deref(uc); } } static struct sip_udpconn *udpconn_find(struct sip *sip, struct udp_sock *us, const struct sa *paddr) { struct le *le; le = list_head(hash_list(sip->ht_udpconn, sa_hash(paddr, SA_ALL))); for (; le; le = le->next) { struct sip_udpconn *uc = le->data; if (!sa_cmp(&uc->paddr, paddr, SA_ALL)) continue; if (uc->us != us) continue; return uc; } return NULL; } int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip, struct udp_sock *us, const struct sa *paddr, uint32_t interval) { struct sip_udpconn *uc; if (!ka || !sip || !us || !paddr) return EINVAL; uc = udpconn_find(sip, us, paddr); if (!uc) { uc = mem_zalloc(sizeof(*uc), destructor); if (!uc) return ENOMEM; hash_append(sip->ht_udpconn, sa_hash(paddr, SA_ALL), &uc->he, uc); uc->paddr = *paddr; uc->stun = mem_ref(sip->stun); uc->us = mem_ref(us); uc->ka_interval = interval ? interval : UDP_KEEPALIVE_INTVAL; /* learn mapped address immediately */ tmr_start(&uc->tmr_ka, 0, udpconn_keepalive_handler, uc); } list_append(&uc->kal, &ka->le, ka); return 0; } ================================================ FILE: src/sip/msg.c ================================================ /** * @file sip/msg.c SIP Message decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" enum { HDR_HASH_SIZE = 32, STARTLINE_MAX = 8192, }; static void hdr_destructor(void *arg) { struct sip_hdr *hdr = arg; list_unlink(&hdr->le); hash_unlink(&hdr->he); } static void destructor(void *arg) { struct sip_msg *msg = arg; list_flush(&msg->hdrl); hash_flush(msg->hdrht); mem_deref(msg->hdrht); mem_deref(msg->sock); mem_deref(msg->mb); } static enum sip_hdrid hdr_hash(const struct pl *name) { if (!name->l) return SIP_HDR_NONE; if (name->l > 1) { switch (name->p[0]) { case 'x': case 'X': if (name->p[1] == '-') return SIP_HDR_NONE; /*@fallthrough@*/ default: return (enum sip_hdrid) (hash_joaat_ci(name->p, name->l) & 0xfff); } } /* compact headers */ switch (tolower(name->p[0])) { case 'a': return SIP_HDR_ACCEPT_CONTACT; case 'b': return SIP_HDR_REFERRED_BY; case 'c': return SIP_HDR_CONTENT_TYPE; case 'd': return SIP_HDR_REQUEST_DISPOSITION; case 'e': return SIP_HDR_CONTENT_ENCODING; case 'f': return SIP_HDR_FROM; case 'i': return SIP_HDR_CALL_ID; case 'j': return SIP_HDR_REJECT_CONTACT; case 'k': return SIP_HDR_SUPPORTED; case 'l': return SIP_HDR_CONTENT_LENGTH; case 'm': return SIP_HDR_CONTACT; case 'n': return SIP_HDR_IDENTITY_INFO; case 'o': return SIP_HDR_EVENT; case 'r': return SIP_HDR_REFER_TO; case 's': return SIP_HDR_SUBJECT; case 't': return SIP_HDR_TO; case 'u': return SIP_HDR_ALLOW_EVENTS; case 'v': return SIP_HDR_VIA; case 'x': return SIP_HDR_SESSION_EXPIRES; case 'y': return SIP_HDR_IDENTITY; default: return SIP_HDR_NONE; } } static inline bool hdr_comma_separated(enum sip_hdrid id) { switch (id) { case SIP_HDR_ACCEPT: case SIP_HDR_ACCEPT_CONTACT: case SIP_HDR_ACCEPT_ENCODING: case SIP_HDR_ACCEPT_LANGUAGE: case SIP_HDR_ACCEPT_RESOURCE_PRIORITY: case SIP_HDR_ALERT_INFO: case SIP_HDR_ALLOW: case SIP_HDR_ALLOW_EVENTS: case SIP_HDR_AUTHENTICATION_INFO: case SIP_HDR_CALL_INFO: case SIP_HDR_CONTACT: case SIP_HDR_CONTENT_ENCODING: case SIP_HDR_CONTENT_LANGUAGE: case SIP_HDR_ERROR_INFO: case SIP_HDR_HISTORY_INFO: case SIP_HDR_IN_REPLY_TO: case SIP_HDR_P_ASSERTED_IDENTITY: case SIP_HDR_P_ASSOCIATED_URI: case SIP_HDR_P_EARLY_MEDIA: case SIP_HDR_P_MEDIA_AUTHORIZATION: case SIP_HDR_P_PREFERRED_IDENTITY: case SIP_HDR_P_REFUSED_URI_LIST: case SIP_HDR_P_VISITED_NETWORK_ID: case SIP_HDR_PATH: case SIP_HDR_PERMISSION_MISSING: case SIP_HDR_PROXY_REQUIRE: case SIP_HDR_REASON: case SIP_HDR_RECORD_ROUTE: case SIP_HDR_REJECT_CONTACT: case SIP_HDR_REQUEST_DISPOSITION: case SIP_HDR_REQUIRE: case SIP_HDR_RESOURCE_PRIORITY: case SIP_HDR_ROUTE: case SIP_HDR_SECURITY_CLIENT: case SIP_HDR_SECURITY_SERVER: case SIP_HDR_SECURITY_VERIFY: case SIP_HDR_SERVICE_ROUTE: case SIP_HDR_SUPPORTED: case SIP_HDR_TRIGGER_CONSENT: case SIP_HDR_UNSUPPORTED: case SIP_HDR_VIA: case SIP_HDR_WARNING: return true; default: return false; } } static inline int hdr_add(struct sip_msg *msg, const struct pl *name, enum sip_hdrid id, const char *p, ssize_t l, bool atomic, bool line) { struct sip_hdr *hdr; int err = 0; hdr = mem_zalloc(sizeof(*hdr), hdr_destructor); if (!hdr) return ENOMEM; hdr->name = *name; hdr->val.p = p; hdr->val.l = MAX(l, 0); hdr->id = id; switch (id) { case SIP_HDR_VIA: case SIP_HDR_ROUTE: if (!atomic) break; hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr)); list_append(&msg->hdrl, &hdr->le, mem_ref(hdr)); break; default: if (atomic) hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr)); if (line) list_append(&msg->hdrl, &hdr->le, mem_ref(hdr)); break; } /* parse common headers */ switch (id) { case SIP_HDR_VIA: if (!atomic || pl_isset(&msg->via.sentby)) break; err = sip_via_decode(&msg->via, &hdr->val); break; case SIP_HDR_TO: err = sip_addr_decode((struct sip_addr *)&msg->to, &hdr->val); if (err) break; (void)msg_param_decode(&msg->to.params, "tag", &msg->to.tag); msg->to.val = hdr->val; break; case SIP_HDR_FROM: err = sip_addr_decode((struct sip_addr *)&msg->from, &hdr->val); if (err) break; (void)msg_param_decode(&msg->from.params, "tag", &msg->from.tag); msg->from.val = hdr->val; break; case SIP_HDR_CALL_ID: msg->callid = hdr->val; break; case SIP_HDR_CSEQ: err = sip_cseq_decode(&msg->cseq, &hdr->val); break; case SIP_HDR_RSEQ: msg->rel_seq = pl_u32(&hdr->val); break; case SIP_HDR_RACK: err = sip_rack_decode(&msg->rack, &hdr->val); break; case SIP_HDR_MAX_FORWARDS: msg->maxfwd = hdr->val; break; case SIP_HDR_CONTENT_TYPE: err = msg_ctype_decode(&msg->ctyp, &hdr->val); break; case SIP_HDR_CONTENT_LENGTH: msg->clen = hdr->val; break; case SIP_HDR_EXPIRES: msg->expires = hdr->val; break; default: /* re_printf("%r = %u\n", &hdr->name, id); */ break; } mem_deref(hdr); return err; } /** * Decode a SIP message * * @param msgp Pointer to allocated SIP Message * @param mb Buffer containing SIP Message * * @return 0 if success, otherwise errorcode */ int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb) { struct pl x, y, z, e, name; const char *p, *v, *cv; struct sip_msg *msg; bool comsep, quote; enum sip_hdrid id = SIP_HDR_NONE; uint32_t ws, lf; size_t l; int err; if (!msgp || !mb) return EINVAL; p = (const char *)mbuf_buf(mb); l = mbuf_get_left(mb); if (re_regex(p, l, "[^ \t\r\n]+ [^ \t\r\n]+ [^\r\n]*[\r]*[\n]1", &x, &y, &z, NULL, &e) || x.p != (char *)mbuf_buf(mb)) return (l > STARTLINE_MAX) ? EBADMSG : ENODATA; msg = mem_zalloc(sizeof(*msg), destructor); if (!msg) return ENOMEM; err = hash_alloc(&msg->hdrht, HDR_HASH_SIZE); if (err) goto out; msg->tag = rand_u64(); msg->mb = mem_ref(mb); msg->req = (0 == pl_strcmp(&z, "SIP/2.0")); if (msg->req) { msg->met = x; msg->ruri = y; msg->ver = z; if (uri_decode(&msg->uri, &y)) { err = EBADMSG; goto out; } } else { msg->ver = x; msg->scode = pl_u32(&y); msg->reason = z; if (!msg->scode) { err = EBADMSG; goto out; } } l -= e.p + e.l - p; p = e.p + e.l; name.p = v = cv = NULL; name.l = ws = lf = 0; comsep = false; quote = false; for (; l > 0; p++, l--) { switch (*p) { case ' ': case '\t': lf = 0; /* folding */ ++ws; break; case '\r': ++ws; break; case '\n': ++ws; if (!lf++) break; ++p; --l; /* eoh */ /*@fallthrough@*/ default: if (lf || (*p == ',' && comsep && !quote)) { if (!name.l) { err = EBADMSG; goto out; } err = hdr_add(msg, &name, id, cv ? cv : p, cv ? p - cv - ws : 0, true, cv == v && lf); if (err) goto out; if (!lf) { /* comma separated */ cv = NULL; break; } if (cv != v) { err = hdr_add(msg, &name, id, v ? v : p, v ? p - v - ws : 0, false, true); if (err) goto out; } if (lf > 1) { /* eoh */ err = 0; goto out; } comsep = false; name.p = NULL; cv = v = NULL; lf = 0; } if (!name.p) { name.p = p; name.l = 0; ws = 0; } if (!name.l) { if (*p != ':') { ws = 0; break; } name.l = MAX((int)(p - name.p - ws), 0); if (!name.l) { err = EBADMSG; goto out; } id = hdr_hash(&name); comsep = hdr_comma_separated(id); break; } if (!cv) { quote = false; cv = p; } if (!v) { v = p; } if (*p == '"') quote = !quote; ws = 0; break; } } err = ENODATA; out: if (err) mem_deref(msg); else { *msgp = msg; mb->pos = mb->end - l; } return err; } /** * Get a SIP Header from a SIP Message * * @param msg SIP Message * @param id SIP Header ID * * @return SIP Header if found, NULL if not found */ const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg, enum sip_hdrid id) { return sip_msg_hdr_apply(msg, true, id, NULL, NULL); } /** * Apply a function handler to certain SIP Headers * * @param msg SIP Message * @param fwd True to traverse forwards, false to traverse backwards * @param id SIP Header ID * @param h Function handler * @param arg Handler argument * * @return SIP Header if handler returns true, otherwise NULL */ const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg, bool fwd, enum sip_hdrid id, sip_hdr_h *h, void *arg) { struct list *lst; struct le *le; if (!msg) return NULL; lst = hash_list(msg->hdrht, id); le = fwd ? list_head(lst) : list_tail(lst); while (le) { const struct sip_hdr *hdr = le->data; le = fwd ? le->next : le->prev; if (hdr->id != id) continue; if (!h || h(hdr, msg, arg)) return hdr; } return NULL; } /** * Get an unknown SIP Header from a SIP Message * * @param msg SIP Message * @param name Header name * * @return SIP Header if found, NULL if not found */ const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg, const char *name) { return sip_msg_xhdr_apply(msg, true, name, NULL, NULL); } /** * Apply a function handler to certain unknown SIP Headers * * @param msg SIP Message * @param fwd True to traverse forwards, false to traverse backwards * @param name SIP Header name * @param h Function handler * @param arg Handler argument * * @return SIP Header if handler returns true, otherwise NULL */ const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg, bool fwd, const char *name, sip_hdr_h *h, void *arg) { struct list *lst; struct le *le; struct pl pl; if (!msg || !name) return NULL; pl_set_str(&pl, name); lst = hash_list(msg->hdrht, hdr_hash(&pl)); le = fwd ? list_head(lst) : list_tail(lst); while (le) { const struct sip_hdr *hdr = le->data; le = fwd ? le->next : le->prev; if (pl_casecmp(&hdr->name, &pl)) continue; if (!h || h(hdr, msg, arg)) return hdr; } return NULL; } static bool count_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { uint32_t *n = arg; (void)hdr; (void)msg; ++(*n); return false; } /** * Count the number of SIP Headers * * @param msg SIP Message * @param id SIP Header ID * * @return Number of SIP Headers */ uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id) { uint32_t n = 0; sip_msg_hdr_apply(msg, true, id, count_handler, &n); return n; } /** * Count the number of unknown SIP Headers * * @param msg SIP Message * @param name SIP Header name * * @return Number of SIP Headers */ uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name) { uint32_t n = 0; sip_msg_xhdr_apply(msg, true, name, count_handler, &n); return n; } static bool value_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { (void)msg; return 0 == pl_strcasecmp(&hdr->val, (const char *)arg); } /** * Check if a SIP Header matches a certain value * * @param msg SIP Message * @param id SIP Header ID * @param value Header value to check * * @return True if value matches, false if not */ bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id, const char *value) { return NULL != sip_msg_hdr_apply(msg, true, id, value_handler, (void *)value); } /** * Check if an unknown SIP Header matches a certain value * * @param msg SIP Message * @param name SIP Header name * @param value Header value to check * * @return True if value matches, false if not */ bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name, const char *value) { return NULL != sip_msg_xhdr_apply(msg, true, name, value_handler, (void *)value); } /** * Print a SIP Message to stdout * * @param msg SIP Message */ void sip_msg_dump(const struct sip_msg *msg) { struct le *le; uint32_t i; if (!msg) return; for (i=0; ihdrht, i)); while (le) { const struct sip_hdr *hdr = le->data; le = le->next; (void)re_printf("%02u '%r'='%r'\n", i, &hdr->name, &hdr->val); } } le = list_head(&msg->hdrl); while (le) { const struct sip_hdr *hdr = le->data; le = le->next; (void)re_printf("%02u '%r'='%r'\n", hdr->id, &hdr->name, &hdr->val); } } ================================================ FILE: src/sip/rack.c ================================================ /** * @file rack.c SIP RAck decode (RFC 3262) * * Copyright (C) 2022 commend.com - m.fridrich@commend.com */ #include #include #include #include #include #include #include #include /** * Decode a pointer-length string into a SIP RAck header * * @param rack SIP RAck header * @param pl Pointer-length string * * @return 0 for success, otherwise errorcode */ int sip_rack_decode(struct sip_rack *rack, const struct pl *pl) { struct pl rel_seq; struct pl cseq; int err; if (!rack || !pl) return EINVAL; err = re_regex(pl->p, pl->l, "[0-9]+[ \t\r\n]+[0-9]+[ \t\r\n]+[^ \t\r\n]+", &rel_seq, NULL, &cseq, NULL, &rack->met); if (err) return err; rack->rel_seq = pl_u32(&rel_seq); rack->cseq = pl_u32(&cseq); return 0; } ================================================ FILE: src/sip/reply.c ================================================ /** * @file sip/reply.c SIP Reply * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "sip.h" static int vreplyf(struct sip_strans **stp, struct mbuf **mbp, bool trans, struct sip *sip, const struct sip_msg *msg, bool rec_route, uint16_t scode, const char *reason, const char *fmt, va_list ap) { bool rport = false; uint32_t viac = 0; struct mbuf *mb; struct sa dst; struct le *le; int err; if (!sip || !msg || !reason) return EINVAL; if (!pl_strcmp(&msg->met, "ACK")) return 0; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } err = mbuf_printf(mb, "SIP/2.0 %u %s\r\n", scode, reason); for (le = msg->hdrl.head; le; le = le->next) { struct sip_hdr *hdr = le->data; struct pl rp; switch (hdr->id) { case SIP_HDR_VIA: err |= mbuf_printf(mb, "%r: ", &hdr->name); if (viac++) { err |= mbuf_printf(mb, "%r\r\n", &hdr->val); break; } if (!msg_param_exists(&msg->via.params, "rport", &rp)){ err |= mbuf_write_pl_skip(mb, &hdr->val, &rp); err |= mbuf_printf(mb, ";rport=%u", sa_port(&msg->src)); rport = true; } else err |= mbuf_write_pl(mb, &hdr->val); if (rport || !sa_cmp(&msg->src, &msg->via.addr, SA_ADDR)) err |= mbuf_printf(mb, ";received=%j", &msg->src); err |= mbuf_write_str(mb, "\r\n"); break; case SIP_HDR_TO: err |= mbuf_printf(mb, "%r: %r", &hdr->name, &hdr->val); if (!pl_isset(&msg->to.tag) && scode > 100) err |= mbuf_printf(mb, ";tag=%016llx", msg->tag); err |= mbuf_write_str(mb, "\r\n"); break; case SIP_HDR_RECORD_ROUTE: if (!rec_route) break; /*@fallthrough@*/ case SIP_HDR_FROM: case SIP_HDR_CALL_ID: case SIP_HDR_CSEQ: err |= mbuf_printf(mb, "%r: %r\r\n", &hdr->name, &hdr->val); break; default: break; } } if (sip->software) err |= mbuf_printf(mb, "Server: %s\r\n", sip->software); if (fmt) err |= mbuf_vprintf(mb, fmt, ap); else err |= mbuf_printf(mb, "Content-Length: 0\r\n\r\n"); if (err) goto out; mb->pos = 0; sip_reply_addr(&dst, msg, rport); if (trans) { err = sip_strans_reply(stp, sip, msg, &dst, scode, mb); } else { err = sip_send(sip, msg->sock, msg->tp, &dst, mb); } out: if (err && stp) *stp = mem_deref(*stp); if (!err && mbp) *mbp = mb; else mem_deref(mb); return err; } /** * Formatted reply using Server Transaction * * @param stp Pointer to allocated SIP Server Transaction (optional) * @param mbp Pointer to allocated SIP message buffer (optional) * @param sip SIP Stack instance * @param msg Incoming SIP message * @param rec_route True to copy Record-Route headers * @param scode Response status code * @param reason Response reason phrase * @param fmt Additional formatted SIP headers and body, otherwise NULL * * @return 0 if success, otherwise errorcode */ int sip_treplyf(struct sip_strans **stp, struct mbuf **mbp, struct sip *sip, const struct sip_msg *msg, bool rec_route, uint16_t scode, const char *reason, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = vreplyf(stp, mbp, true, sip, msg, rec_route, scode, reason, fmt, ap); va_end(ap); return err; } /** * Reply using Server Transaction * * @param stp Pointer to allocated SIP Server Transaction (optional) * @param sip SIP Stack instance * @param msg Incoming SIP message * @param scode Response status code * @param reason Response reason phrase * * @return 0 if success, otherwise errorcode */ int sip_treply(struct sip_strans **stp, struct sip *sip, const struct sip_msg *msg, uint16_t scode, const char *reason) { return sip_treplyf(stp, NULL, sip, msg, false, scode, reason, NULL); } /** * Stateless formatted reply * * @param sip SIP Stack instance * @param msg Incoming SIP message * @param scode Response status code * @param reason Response reason phrase * @param fmt Additional formatted SIP headers and body, otherwise NULL * * @return 0 if success, otherwise errorcode */ int sip_replyf(struct sip *sip, const struct sip_msg *msg, uint16_t scode, const char *reason, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = vreplyf(NULL, NULL, false, sip, msg, false, scode, reason, fmt, ap); va_end(ap); return err; } /** * Stateless reply * * @param sip SIP Stack instance * @param msg Incoming SIP message * @param scode Response status code * @param reason Response reason phrase * * @return 0 if success, otherwise errorcode */ int sip_reply(struct sip *sip, const struct sip_msg *msg, uint16_t scode, const char *reason) { return sip_replyf(sip, msg, scode, reason, NULL); } /** * Get the reply address from a SIP message * * @param addr Network address, set on return * @param msg SIP message * @param rport Rport value */ void sip_reply_addr(struct sa *addr, const struct sip_msg *msg, bool rport) { uint16_t port; struct pl pl; if (!addr || !msg) return; port = sa_port(&msg->via.addr); *addr = msg->src; switch (msg->tp) { case SIP_TRANSP_UDP: if (!msg_param_decode(&msg->via.params, "maddr", &pl)) { (void)sa_set(addr, &pl,sip_transp_port(msg->tp, port)); break; } if (rport) break; /*@fallthrough@*/ case SIP_TRANSP_TCP: case SIP_TRANSP_TLS: sa_set_port(addr, sip_transp_port(msg->tp, port)); break; default: break; } } ================================================ FILE: src/sip/request.c ================================================ /** * @file sip/request.c SIP Request * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" struct sip_request { struct le le; struct list cachel; struct list addrl; struct list srvl; struct sip_request **reqp; struct sip_ctrans *ct; struct dns_query *dnsq; struct dns_query *dnsq2; struct sip *sip; char *met; char *uri; char *host; char *branch; struct mbuf *mb; sip_send_h *sendh; sip_resp_h *resph; void *arg; size_t sortkey; enum sip_transp tp; bool tp_selected; bool stateful; bool canceled; bool provrecv; uint16_t port; uint16_t srcport; }; static int request_next(struct sip_request *req); static bool rr_append_handler(struct dnsrr *rr, void *arg); static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg); static int srv_lookup(struct sip_request *req, const char *domain); static int addr_lookup(struct sip_request *req, const char *name); static int str_ldup(char **dst, const char *src, int len) { struct pl pl; pl.p = src; pl.l = len < 0 ? str_len(src) : (size_t)len; return pl_strdup(dst, &pl); } static void destructor(void *arg) { struct sip_request *req = arg; if (req->reqp && req->stateful) { /* user does deref before request has completed */ *req->reqp = NULL; req->reqp = NULL; req->sendh = NULL; req->resph = NULL; sip_request_cancel(mem_ref(req)); return; } list_flush(&req->cachel); list_flush(&req->addrl); list_flush(&req->srvl); list_unlink(&req->le); mem_deref(req->dnsq); mem_deref(req->dnsq2); mem_deref(req->ct); mem_deref(req->met); mem_deref(req->uri); mem_deref(req->host); mem_deref(req->branch); mem_deref(req->mb); } static void terminate(struct sip_request *req, int err, const struct sip_msg *msg) { if (req->reqp) { *req->reqp = NULL; req->reqp = NULL; } list_unlink(&req->le); req->sendh = NULL; if (req->resph) { req->resph(err, msg, req->arg); req->resph = NULL; } } static bool close_handler(struct le *le, void *arg) { struct sip_request *req = le->data; (void)arg; req->dnsq = mem_deref(req->dnsq); req->dnsq2 = mem_deref(req->dnsq2); req->ct = mem_deref(req->ct); terminate(req, ECONNABORTED, NULL); mem_deref(req); return false; } static void response_handler(int err, const struct sip_msg *msg, void *arg) { struct sip_request *req = arg; if (msg && msg->scode < 200) { if (!req->provrecv) { req->provrecv = true; if (req->canceled) (void)sip_ctrans_cancel(req->ct); } if (req->resph) req->resph(err, msg, req->arg); return; } req->ct = NULL; if (!req->canceled && (err || (msg && msg->scode == 503)) && (req->addrl.head || req->srvl.head)) { err = request_next(req); if (!err) return; } terminate(req, err, msg); mem_deref(req); } static int connect_handler(struct sa *src, const struct sa *dst, struct mbuf *mb, void *arg) { struct sip_request *req = arg; struct mbuf *mbs = NULL; struct mbuf *cont = NULL; int err; if (!sa_isset(src, SA_ALL)) return EINVAL; mbuf_set_posend(mb, 0, 0); mbs = mbuf_alloc(256); if (!mbs) return ENOMEM; err = req->sendh ? req->sendh(req->tp, src, dst, mbs, &cont, req->arg) : 0; if (err) goto out; mbuf_set_pos(mbs, 0); err = mbuf_printf(mb, "%s %s SIP/2.0\r\n", req->met, req->uri); err |= mbuf_printf(mb, "Via: SIP/2.0/%s %J;branch=%s;rport\r\n", sip_transp_name(req->tp), src, req->branch); err |= mbuf_write_mem(mb, mbuf_buf(mbs), mbuf_get_left(mbs)); err |= mbuf_write_mem(mb, mbuf_buf(req->mb), mbuf_get_left(req->mb)); if (cont) { err |= mbuf_write_mem(mb, mbuf_buf(cont), mbuf_get_left(cont)); mem_deref(cont); } if (err) goto out; mb->pos = 0; out: if (err) mbuf_reset(mb); mem_deref(mbs); return err; } static int request(struct sip_request *req, enum sip_transp tp, const struct sa *dst) { struct mbuf *mb = NULL; int err = ENOMEM; req->provrecv = false; mem_deref(req->branch); (void)re_sdprintf(&req->branch, "z9hG4bK%016llx", rand_u64()); mb = mbuf_alloc(1024); if (!req->branch || !mb) goto out; if (!req->branch || !mb) goto out; if (req->srcport) { struct sip_conncfg cfg = {.srcport = req->srcport}; err = sip_conncfg_set(req->sip, dst, &cfg); if (err) goto out; } if (!req->stateful) { err = sip_send_conn(req->sip, NULL, tp, dst, req->host, mb, connect_handler, req); } else { err = sip_ctrans_request(&req->ct, req->sip, tp, dst, req->met, req->branch, req->host, mb, connect_handler, response_handler, req); } if (err) goto out; out: mem_deref(mb); return err; } static int request_next(struct sip_request *req) { struct dnsrr *rr; struct sa dst; int err; again: rr = list_ledata(req->addrl.head); if (!rr) { rr = list_ledata(req->srvl.head); if (!rr) return ENOENT; req->port = rr->rdata.srv.port; dns_rrlist_apply2(&req->cachel, rr->rdata.srv.target, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN, true, rr_append_handler, &req->addrl); list_unlink(&rr->le); if (req->addrl.head) { dns_rrlist_sort_addr(&req->addrl, req->sortkey); mem_deref(rr); goto again; } err = addr_lookup(req, rr->rdata.srv.target); mem_deref(rr); return err; } switch (rr->type) { case DNS_TYPE_A: sa_set_in(&dst, rr->rdata.a.addr, req->port); break; case DNS_TYPE_AAAA: sa_set_in6(&dst, rr->rdata.aaaa.addr, req->port); break; default: return EINVAL; } list_unlink(&rr->le); mem_deref(rr); err = request(req, req->tp, &dst); if (err) { if (req->addrl.head || req->srvl.head) goto again; } else if (!req->stateful) { req->resph = NULL; terminate(req, 0, NULL); mem_deref(req); } return err; } static bool transp_next(struct sip *sip, enum sip_transp *tp) { enum sip_transp i; for (i=(enum sip_transp)(*tp+1); iSIP_TRANSP_NONE; i--) { const char *srvid; srvid = sip_transp_srvid(i); if (0 == str_cmp(srvid, "???")) continue; if (!sip_transp_supported(sip, i, AF_UNSPEC)) continue; *tp = i; return true; } return false; } static bool transp_first(struct sip *sip, enum sip_transp *tp) { if (!sip || !tp) return false; if (sip->tp_def != SIP_TRANSP_NONE && sip_transp_supported(sip, sip->tp_def, AF_UNSPEC)) { *tp = sip->tp_def; return true; } *tp = SIP_TRANSP_NONE; return transp_next(sip, tp); } static bool rr_append_handler(struct dnsrr *rr, void *arg) { struct dnsrr *rrdup; struct list *lst = arg; switch (rr->type) { case DNS_TYPE_A: case DNS_TYPE_AAAA: case DNS_TYPE_SRV: if (0 == dns_rr_dup(&rrdup, rr)) list_append(lst, &rrdup->le, rrdup); } return false; } static bool rr_cache_handler(struct dnsrr *rr, void *arg) { struct sip_request *req = arg; switch (rr->type) { case DNS_TYPE_A: if (!sip_transp_supported(req->sip, req->tp, AF_INET)) break; list_unlink(&rr->le_priv); list_append(&req->cachel, &rr->le_priv, rr); break; case DNS_TYPE_AAAA: if (!sip_transp_supported(req->sip, req->tp, AF_INET6)) break; list_unlink(&rr->le_priv); list_append(&req->cachel, &rr->le_priv, rr); break; case DNS_TYPE_CNAME: list_unlink(&rr->le_priv); list_append(&req->cachel, &rr->le_priv, rr); break; } return false; } static bool rr_naptr_handler(struct dnsrr *rr, void *arg) { struct sip_request *req = arg; enum sip_transp tp; if (rr->type != DNS_TYPE_NAPTR) return false; if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2U")) tp = SIP_TRANSP_UDP; else if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2T")) tp = SIP_TRANSP_TCP; else if (!str_casecmp(rr->rdata.naptr.services, "SIPS+D2T")) tp = SIP_TRANSP_TLS; else if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2W")) tp = SIP_TRANSP_WS; else if (!str_casecmp(rr->rdata.naptr.services, "SIPS+D2W")) tp = SIP_TRANSP_WSS; else return false; if (!sip_transp_supported(req->sip, tp, AF_UNSPEC)) return false; req->tp = tp; req->tp_selected = true; return true; } static void naptr_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct sip_request *req = arg; struct dnsrr *rr; (void)hdr; (void)authl; dns_rrlist_sort(ansl, DNS_TYPE_NAPTR, req->sortkey); rr = dns_rrlist_apply(ansl, NULL, DNS_TYPE_NAPTR, DNS_CLASS_IN, false, rr_naptr_handler, req); if (!rr) { req->tp = SIP_TRANSPC; if (!transp_next_srv(req->sip, &req->tp)) { err = EPROTONOSUPPORT; goto fail; } err = srv_lookup(req, req->host); if (err) goto fail; return; } dns_rrlist_apply(addl, rr->rdata.naptr.replace, DNS_TYPE_SRV, DNS_CLASS_IN, true, rr_append_handler, &req->srvl); if (!req->srvl.head) { err = dnsc_query(&req->dnsq, req->sip->dnsc, rr->rdata.naptr.replace, DNS_TYPE_SRV, DNS_CLASS_IN, true, srv_handler, req); if (err) goto fail; return; } dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey); dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false, rr_cache_handler, req); err = request_next(req); if (err) goto fail; return; fail: terminate(req, err, NULL); mem_deref(req); } static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct sip_request *req = arg; (void)hdr; (void)authl; dns_rrlist_apply(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false, rr_append_handler, &req->srvl); if (!req->srvl.head) { if (!req->tp_selected) { if (transp_next_srv(req->sip, &req->tp)) { err = srv_lookup(req, req->host); if (err) goto fail; return; } if (!transp_first(req->sip, &req->tp)) { err = EPROTONOSUPPORT; goto fail; } } req->port = sip_transp_port(req->tp, 0); err = addr_lookup(req, req->host); if (err) goto fail; return; } dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey); dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false, rr_cache_handler, req); err = request_next(req); if (err) goto fail; return; fail: terminate(req, err, NULL); mem_deref(req); } static void addr_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct sip_request *req = arg; (void)hdr; (void)authl; (void)addl; dns_rrlist_apply2(ansl, NULL, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN, false, rr_append_handler, &req->addrl); /* wait for other (A/AAAA) query to complete */ if (req->dnsq || req->dnsq2) return; if (!req->addrl.head && !req->srvl.head) { err = err ? err : EDESTADDRREQ; goto fail; } dns_rrlist_sort_addr(&req->addrl, req->sortkey); err = request_next(req); if (err) goto fail; return; fail: terminate(req, err, NULL); mem_deref(req); } static int srv_lookup(struct sip_request *req, const char *domain) { char name[256]; if (re_snprintf(name, sizeof(name), "%s.%s", sip_transp_srvid(req->tp), domain) < 0) return ENOMEM; return dnsc_query(&req->dnsq, req->sip->dnsc, name, DNS_TYPE_SRV, DNS_CLASS_IN, true, srv_handler, req); } static int addr_lookup(struct sip_request *req, const char *name) { int err; if (sip_transp_supported(req->sip, req->tp, AF_INET)) { err = dnsc_query(&req->dnsq, req->sip->dnsc, name, DNS_TYPE_A, DNS_CLASS_IN, true, addr_handler, req); if (err) return err; } if (sip_transp_supported(req->sip, req->tp, AF_INET6)) { err = dnsc_query(&req->dnsq2, req->sip->dnsc, name, DNS_TYPE_AAAA, DNS_CLASS_IN, true, addr_handler, req); if (err) return err; } if (!req->dnsq && !req->dnsq2) return EPROTONOSUPPORT; return 0; } static int sip_request_alloc(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, int metl, const char *uri, int uril, const struct uri *route, enum sip_transp tp, struct mbuf *mb, size_t sortkey, sip_send_h *sendh, sip_resp_h *resph, void *arg) { struct sip_request *req; struct pl pl; int err; if (!sip || !met || !uri || !route || !mb) return EINVAL; if (pl_strcasecmp(&route->scheme, "sip")) return ENOSYS; req = mem_zalloc(sizeof(*req), destructor); if (!req) return ENOMEM; list_append(&sip->reql, &req->le, req); err = str_ldup(&req->met, met, metl); if (err) goto out; err = str_ldup(&req->uri, uri, uril); if (err) goto out; if (msg_param_decode(&route->params, "maddr", &pl)) pl = route->host; err = pl_strdup(&req->host, &pl); if (err) goto out; req->stateful = stateful; req->sortkey = sortkey; req->mb = mem_ref(mb); req->sip = sip; req->sendh = sendh; req->resph = resph; req->arg = arg; if (tp != SIP_TRANSP_NONE) { req->tp = tp; } else if (!msg_param_decode(&route->params, "transport", &pl)) { req->tp = sip_transp_decode(&pl); if (req->tp == SIP_TRANSP_NONE) { err = EPROTONOSUPPORT; goto out; } if (!sip_transp_supported(sip, req->tp, AF_UNSPEC)) { err = EPROTONOSUPPORT; goto out; } req->tp_selected = true; } else { if (!transp_first(sip, &req->tp)) { err = EPROTONOSUPPORT; goto out; } req->tp_selected = false; } out: if (err) mem_deref(req); else if (reqp) *reqp = req; return err; } static int sip_request_send(struct sip_request *req, struct sip *sip, const struct uri *route) { struct sa dst; int err; bool addr_only = req->tp_selected && dnsc_getaddrinfo_only(req->sip->dnsc); if (!sa_set_str(&dst, req->host, sip_transp_port(req->tp, route->port))) { err = request(req, req->tp, &dst); if (!req->stateful) { mem_deref(req); return err; } } else if (route->port || addr_only){ req->port = sip_transp_port(req->tp, route->port); err = addr_lookup(req, req->host); } else if (req->tp_selected) { err = srv_lookup(req, req->host); } else { err = dnsc_query(&req->dnsq, sip->dnsc, req->host, DNS_TYPE_NAPTR, DNS_CLASS_IN, true, naptr_handler, req); } if (err) mem_deref(req); else if (req->reqp) *req->reqp = req; return err; } /** * Send a SIP request * * @param reqp Pointer to allocated SIP request object * @param sip SIP Stack * @param stateful Stateful client transaction * @param met SIP Method string * @param metl Length of SIP Method string * @param uri Request URI * @param uril Length of Request URI string * @param route Next hop route URI * @param mb Buffer containing SIP request * @param sortkey Key for DNS record sorting * @param sendh Send handler * @param resph Response handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, int metl, const char *uri, int uril, const struct uri *route, struct mbuf *mb, size_t sortkey, sip_send_h *sendh, sip_resp_h *resph, void *arg) { struct sip_request *req = NULL; int err; if (!sip || !met || !uri || !route || !mb) return EINVAL; err = sip_request_alloc(&req, sip, stateful, met, metl, uri, uril, route, SIP_TRANSP_NONE, mb, sortkey, sendh, resph, arg); if (err) goto out; req->reqp = reqp; err = sip_request_send(req, sip, route); out: return err; } /** * Send a SIP request with formatted arguments * * @param reqp Pointer to allocated SIP request object * @param sip SIP Stack * @param stateful Stateful client transaction * @param met Null-terminated SIP Method string * @param uri Null-terminated Request URI string * @param route Next hop route URI (optional) * @param auth SIP authentication state * @param sendh Send handler * @param resph Response handler * @param arg Handler argument * @param fmt Formatted SIP headers and body * * @return 0 if success, otherwise errorcode */ int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, const char *uri, const struct uri *route, struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph, void *arg, const char *fmt, ...) { struct uri lroute; struct mbuf *mb; va_list ap; int err; if (!sip || !met || !uri || !fmt) return EINVAL; if (!route) { struct pl uripl; pl_set_str(&uripl, uri); err = uri_decode(&lroute, &uripl); if (err) return err; route = &lroute; } mb = mbuf_alloc(2048); if (!mb) return ENOMEM; err = mbuf_write_str(mb, "Max-Forwards: 70\r\n"); if (auth) err |= sip_auth_encode(mb, auth, met, uri); if (err) goto out; va_start(ap, fmt); err = mbuf_vprintf(mb, fmt, ap); va_end(ap); if (err) goto out; mb->pos = 0; err = sip_request(reqp, sip, stateful, met, -1, uri, -1, route, mb, (size_t)arg, sendh, resph, arg); if (err) goto out; out: mem_deref(mb); return err; } /** * Send a SIP dialog request with formatted arguments * * @param reqp Pointer to allocated SIP request object * @param sip SIP Stack * @param stateful Stateful client transaction * @param met Null-terminated SIP Method string * @param dlg SIP Dialog state * @param cseq CSeq number * @param auth SIP authentication state * @param sendh Send handler * @param resph Response handler * @param arg Handler argument * @param fmt Formatted SIP headers and body * * @return 0 if success, otherwise errorcode */ int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful, const char *met, struct sip_dialog *dlg, uint32_t cseq, struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph, void *arg, const char *fmt, ...) { struct sip_request *req; struct mbuf *mb; va_list ap; int err; if (!sip || !met || !dlg || !fmt) return EINVAL; mb = mbuf_alloc(2048); if (!mb) return ENOMEM; err = mbuf_write_str(mb, "Max-Forwards: 70\r\n"); if (auth) err |= sip_auth_encode(mb, auth, met, sip_dialog_uri(dlg)); err |= sip_dialog_encode(mb, dlg, cseq, met); if (sip->software) err |= mbuf_printf(mb, "User-Agent: %s\r\n", sip->software); if (err) goto out; va_start(ap, fmt); err = mbuf_vprintf(mb, fmt, ap); va_end(ap); if (err) goto out; mb->pos = 0; err = sip_request_alloc(&req, sip, stateful, met, -1, sip_dialog_uri(dlg), -1, sip_dialog_route(dlg), sip_dialog_tp(dlg), mb, sip_dialog_hash(dlg), sendh, resph, arg); if (err) goto out; req->reqp = reqp; req->srcport = sip_dialog_srcport(dlg); err = sip_request_send(req, sip, sip_dialog_route(dlg)); out: mem_deref(mb); return err; } /** * Cancel a pending SIP Request * * @param req SIP Request */ void sip_request_cancel(struct sip_request *req) { if (!req || req->canceled) return; req->canceled = true; if (!req->provrecv) { req->ct = mem_deref(req->ct); return; } (void)sip_ctrans_cancel(req->ct); } /** * Check if a provisional response was received for a SIP Request * * @param req SIP Request * * @return True if a provisional response was received, false otherwise */ bool sip_request_provrecv(const struct sip_request *req) { return req ? req->provrecv : false; } void sip_request_close(struct sip *sip) { if (!sip) return; list_apply(&sip->reql, true, close_handler, NULL); } /** * Check if a SIP request loops * * @param ls Loop state * @param scode Status code from SIP response * * @return True if loops, otherwise false */ bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode) { bool loop = false; if (!ls) return false; if (scode < 200) { return false; } else if (scode < 300) { ls->failc = 0; } else if (scode < 400) { loop = (++ls->failc >= 16); } else { switch (scode) { default: if (ls->last_scode == scode) loop = true; /*@fallthrough@*/ case 401: case 407: case 491: if (++ls->failc >= 16) loop = true; break; } } ls->last_scode = scode; return loop; } /** * Reset the loop state * * @param ls Loop state */ void sip_loopstate_reset(struct sip_loopstate *ls) { if (!ls) return; ls->last_scode = 0; ls->failc = 0; } ================================================ FILE: src/sip/sip.c ================================================ /** * @file sip.c SIP Core * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" static void websock_shutdown_handler(void *arg) { struct sip *sip = arg; if (sip->exith) sip->exith(sip->arg); } static void destructor(void *arg) { struct sip *sip = arg; if (sip->closing) { sip->closing = false; mem_ref(sip); if (mem_nrefs(sip->websock) > 1) { /* NOTE: we must flush all connections here, since they have a reference to websock */ hash_flush(sip->ht_conn); sip->ht_conn = mem_deref(sip->ht_conn); websock_shutdown(sip->websock); } else { if (sip->exith) sip->exith(sip->arg); } return; } sip_request_close(sip); sip_request_close(sip); hash_flush(sip->ht_ctrans); mem_deref(sip->ht_ctrans); hash_flush(sip->ht_strans); hash_clear(sip->ht_strans_mrg); mem_deref(sip->ht_strans); mem_deref(sip->ht_strans_mrg); hash_flush(sip->ht_conn); mem_deref(sip->ht_conn); hash_flush(sip->ht_conncfg); mem_deref(sip->ht_conncfg); hash_flush(sip->ht_udpconn); mem_deref(sip->ht_udpconn); list_flush(&sip->transpl); list_flush(&sip->lsnrl); mem_deref(sip->software); mem_deref(sip->dnsc); mem_deref(sip->stun); mem_deref(sip->websock); } static void lsnr_destructor(void *arg) { struct sip_lsnr *lsnr = arg; if (lsnr->lsnrp) *lsnr->lsnrp = NULL; list_unlink(&lsnr->le); } /** * Allocate a SIP stack instance * * @param sipp Pointer to allocated SIP stack * @param dnsc DNS Client (optional) * @param ctsz Size of client transactions hashtable (power of 2) * @param stsz Size of server transactions hashtable (power of 2) * @param tcsz Size of SIP transport hashtable (power of 2) * @param software Software identifier * @param exith SIP-stack exit handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sip_alloc(struct sip **sipp, struct dnsc *dnsc, uint32_t ctsz, uint32_t stsz, uint32_t tcsz, const char *software, sip_exit_h *exith, void *arg) { struct sip *sip; int err; if (!sipp) return EINVAL; sip = mem_zalloc(sizeof(*sip), destructor); if (!sip) return ENOMEM; sip->tp_def = SIP_TRANSP_NONE; err = sip_transp_init(sip, tcsz); if (err) goto out; err = sip_ctrans_init(sip, ctsz); if (err) goto out; err = sip_strans_init(sip, stsz); if (err) goto out; err = hash_alloc(&sip->ht_udpconn, tcsz); if (err) goto out; err = stun_alloc(&sip->stun, NULL, NULL, NULL); if (err) goto out; if (software) { err = str_dup(&sip->software, software); if (err) goto out; } sip->dnsc = mem_ref(dnsc); sip->exith = exith; sip->arg = arg; err = websock_alloc(&sip->websock, websock_shutdown_handler, sip); if (err) goto out; out: if (err) mem_deref(sip); else *sipp = sip; return err; } /** * Close the SIP stack instance * * @param sip SIP stack instance * @param force Don't wait for transactions to complete */ void sip_close(struct sip *sip, bool force) { if (!sip) return; if (force) { sip_request_close(sip); sip_request_close(sip); } else if (!sip->closing) { sip->closing = true; mem_deref(sip); } } /** * Send a SIP message and use given SIP connected handler * * @param sip SIP stack instance * @param sock Optional socket to send from * @param tp SIP transport * @param dst Destination network address * @param host Target hostname * @param mb Buffer containing SIP message * @param connh SIP connected handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sip_send_conn(struct sip *sip, void *sock, enum sip_transp tp, const struct sa *dst, char *host, struct mbuf *mb, sip_conn_h *connh, void *arg) { return sip_transp_send(NULL, sip, sock, tp, dst, host, mb, connh, NULL, arg); } /** * Send a SIP message * * @param sip SIP stack instance * @param sock Optional socket to send from * @param tp SIP transport * @param dst Destination network address * @param mb Buffer containing SIP message * * @return 0 if success, otherwise errorcode */ int sip_send(struct sip *sip, void *sock, enum sip_transp tp, const struct sa *dst, struct mbuf *mb) { return sip_transp_send(NULL, sip, sock, tp, dst, NULL, mb, NULL, NULL, NULL); } /** * Listen for incoming SIP Requests and SIP Responses * * @param lsnrp Pointer to allocated listener * @param sip SIP stack instance * @param req True for Request, false for Response * @param msgh SIP message handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sip_listen(struct sip_lsnr **lsnrp, struct sip *sip, bool req, sip_msg_h *msgh, void *arg) { struct sip_lsnr *lsnr; if (!sip || !msgh) return EINVAL; lsnr = mem_zalloc(sizeof(*lsnr), lsnr_destructor); if (!lsnr) return ENOMEM; list_append(&sip->lsnrl, &lsnr->le, lsnr); lsnr->msgh = msgh; lsnr->arg = arg; lsnr->req = req; if (lsnrp) { lsnr->lsnrp = lsnrp; *lsnrp = lsnr; } return 0; } /** * Print debug information about the SIP stack * * @param pf Print function for debug output * @param sip SIP stack instance * * @return 0 if success, otherwise errorcode */ int sip_debug(struct re_printf *pf, const struct sip *sip) { int err; if (!sip) return 0; err = sip_transp_debug(pf, sip); err |= sip_ctrans_debug(pf, sip); err |= sip_strans_debug(pf, sip); return err; } void sip_set_trace_handler(struct sip *sip, sip_trace_h *traceh) { if (!sip) return; sip->traceh = traceh; } struct sip_conncfg *sip_conncfg_find(struct sip *sip, const struct sa *paddr) { struct le *le; le = list_head(hash_list(sip->ht_conncfg, sa_hash(paddr, SA_ALL))); for (; le; le = le->next) { struct sip_conncfg *cfg = le->data; if (!sa_cmp(&cfg->paddr, paddr, SA_ALL)) continue; return cfg; } return NULL; } ================================================ FILE: src/sip/sip.h ================================================ /** * @file sip.h SIP Private Interface * * Copyright (C) 2010 Creytiv.com */ struct sip { struct list transpl; struct list lsnrl; struct list reql; struct hash *ht_ctrans; struct hash *ht_strans; struct hash *ht_strans_mrg; struct hash *ht_conn; struct hash *ht_udpconn; struct hash *ht_conncfg; struct dnsc *dnsc; struct stun *stun; struct websock *websock; char *software; sip_exit_h *exith; sip_trace_h *traceh; void *arg; bool closing; uint8_t tos; enum sip_transp tp_def; }; struct sip_lsnr { struct le le; struct sip_lsnr **lsnrp; sip_msg_h *msgh; void *arg; bool req; }; struct sip_keepalive { struct le le; struct sip_keepalive **kap; sip_keepalive_h *kah; void *arg; }; /* request */ void sip_request_close(struct sip *sip); /* ctrans */ struct sip_ctrans; int sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip, enum sip_transp tp, const struct sa *dst, char *met, char *branch, char *host, struct mbuf *mb, sip_conn_h *connh, sip_resp_h *resph, void *arg); int sip_ctrans_cancel(struct sip_ctrans *ct); int sip_ctrans_init(struct sip *sip, uint32_t sz); int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip); /* strans */ int sip_strans_init(struct sip *sip, uint32_t sz); int sip_strans_debug(struct re_printf *pf, const struct sip *sip); /* transp */ struct sip_connqent; typedef void(sip_transp_h)(int err, void *arg); int sip_transp_init(struct sip *sip, uint32_t sz); int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock, enum sip_transp tp, const struct sa *dst, char *host, struct mbuf *mb, sip_conn_h *connh, sip_transp_h *transph, void *arg); bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af); const char *sip_transp_srvid(enum sip_transp tp); bool sip_transp_reliable(enum sip_transp tp); int sip_transp_debug(struct re_printf *pf, const struct sip *sip); /* dialog */ int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq, const char *met); const struct uri *sip_dialog_route(const struct sip_dialog *dlg); uint32_t sip_dialog_hash(const struct sip_dialog *dlg); /* keepalive */ struct sip_conn; void sip_keepalive_signal(struct list *kal, int err); uint64_t sip_keepalive_wait(uint32_t interval); int sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn, uint32_t interval); int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip, struct udp_sock *us, const struct sa *paddr, uint32_t interval); /* sip_conncfg */ struct sip_conncfg *sip_conncfg_find(struct sip *sip, const struct sa *paddr); ================================================ FILE: src/sip/strans.c ================================================ /** * @file strans.c SIP Server Transaction * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" enum state { TRYING, PROCEEDING, ACCEPTED, COMPLETED, CONFIRMED, }; struct sip_strans { struct le he; struct le he_mrg; struct tmr tmr; struct tmr tmrg; struct sa dst; struct sip *sip; struct sip_msg *msg; struct sip_msg *cancel_msg; struct mbuf *mb; sip_cancel_h *cancelh; void *arg; enum state state; uint32_t txc; bool invite; }; static void destructor(void *arg) { struct sip_strans *st = arg; hash_unlink(&st->he); hash_unlink(&st->he_mrg); tmr_cancel(&st->tmr); tmr_cancel(&st->tmrg); mem_deref(st->msg); mem_deref(st->cancel_msg); mem_deref(st->mb); } const struct sip_msg *sip_strans_cancel_msg(struct sip_strans *st) { if (!st) return NULL; return st->cancel_msg; } static bool strans_cmp(const struct sip_msg *msg1, const struct sip_msg *msg2) { if (pl_cmp(&msg1->via.branch, &msg2->via.branch)) return false; if (pl_cmp(&msg1->via.sentby, &msg2->via.sentby)) return false; return true; } static bool cmp_handler(struct le *le, void *arg) { struct sip_strans *st = le->data; const struct sip_msg *msg = arg; if (!strans_cmp(st->msg, msg)) return false; if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met)) return false; return true; } static bool cmp_ack_handler(struct le *le, void *arg) { struct sip_strans *st = le->data; const struct sip_msg *msg = arg; if (!strans_cmp(st->msg, msg)) return false; if (pl_strcmp(&st->msg->cseq.met, "INVITE")) return false; return true; } static bool cmp_cancel_handler(struct le *le, void *arg) { struct sip_strans *st = le->data; const struct sip_msg *msg = arg; if (!strans_cmp(st->msg, msg)) return false; if (!pl_strcmp(&st->msg->cseq.met, "CANCEL")) return false; return true; } static bool cmp_merge_handler(struct le *le, void *arg) { struct sip_strans *st = le->data; const struct sip_msg *msg = arg; if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met)) return false; if (st->msg->cseq.num != msg->cseq.num) return false; if (pl_cmp(&st->msg->callid, &msg->callid)) return false; if (pl_cmp(&st->msg->from.tag, &msg->from.tag)) return false; if (pl_cmp(&st->msg->ruri, &msg->ruri)) return false; return true; } static void dummy_handler(void *arg) { (void)arg; } static void tmr_handler(void *arg) { struct sip_strans *st = arg; mem_deref(st); } static void retransmit_handler(void *arg) { struct sip_strans *st = arg; (void)sip_send(st->sip, st->msg->sock, st->msg->tp, &st->dst, st->mb); st->txc++; tmr_start(&st->tmrg, MIN(SIP_T1<txc, SIP_T2), retransmit_handler, st); } static bool ack_handler(struct sip *sip, const struct sip_msg *msg) { struct sip_strans *st; st = list_ledata(hash_lookup(sip->ht_strans, hash_joaat_pl(&msg->via.branch), cmp_ack_handler, (void *)msg)); if (!st) return false; switch (st->state) { case ACCEPTED: /* make sure ACKs for 2xx are passed to TU */ return false; case COMPLETED: if (sip_transp_reliable(st->msg->tp)) { mem_deref(st); break; } tmr_start(&st->tmr, SIP_T4, tmr_handler, st); tmr_cancel(&st->tmrg); st->state = CONFIRMED; break; default: break; } return true; } static bool cancel_handler(struct sip *sip, const struct sip_msg *msg) { struct sip_strans *st; st = list_ledata(hash_lookup(sip->ht_strans, hash_joaat_pl(&msg->via.branch), cmp_cancel_handler, (void *)msg)); if (!st) return false; ((struct sip_msg *)msg)->tag = st->msg->tag; (void)sip_reply(sip, msg, 200, "OK"); switch (st->state) { case TRYING: case PROCEEDING: mem_deref(st->cancel_msg); st->cancel_msg = mem_ref((void *)msg); st->cancelh(st->arg); break; default: break; } return true; } static bool request_handler(const struct sip_msg *msg, void *arg) { struct sip_strans *st; struct sip *sip = arg; if (!pl_strcmp(&msg->met, "ACK")) return ack_handler(sip, msg); st = list_ledata(hash_lookup(sip->ht_strans, hash_joaat_pl(&msg->via.branch), cmp_handler, (void *)msg)); if (st) { switch (st->state) { case PROCEEDING: case COMPLETED: (void)sip_send(st->sip, st->msg->sock, st->msg->tp, &st->dst, st->mb); break; default: break; } return true; } else if (!pl_isset(&msg->to.tag)) { st = list_ledata(hash_lookup(sip->ht_strans_mrg, hash_joaat_pl(&msg->callid), cmp_merge_handler, (void *)msg)); if (st) { (void)sip_reply(sip, msg, 482, "Loop Detected"); return true; } } if (!pl_strcmp(&msg->met, "CANCEL")) return cancel_handler(sip, msg); return false; } /** * Allocate a SIP Server Transaction * * @param stp Pointer to allocated SIP Server Transaction * @param sip SIP Stack instance * @param msg Incoming SIP message * @param cancelh Cancel handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sip_strans_alloc(struct sip_strans **stp, struct sip *sip, const struct sip_msg *msg, sip_cancel_h *cancelh, void *arg) { struct sip_strans *st; if (!stp || !sip || !msg) return EINVAL; st = mem_zalloc(sizeof(*st), destructor); if (!st) return ENOMEM; hash_append(sip->ht_strans, hash_joaat_pl(&msg->via.branch), &st->he, st); hash_append(sip->ht_strans_mrg, hash_joaat_pl(&msg->callid), &st->he_mrg, st); st->invite = !pl_strcmp(&msg->met, "INVITE"); st->msg = mem_ref((void *)msg); st->state = TRYING; st->cancelh = cancelh ? cancelh : dummy_handler; st->arg = arg; st->sip = sip; *stp = st; return 0; } /** * Reply using a SIP Server Transaction * * @param stp Pointer to allocated SIP Server Transaction * @param sip SIP Stack instance * @param msg Incoming SIP message * @param dst Destination network address * @param scode Response status code * @param mb Buffer containing SIP response * * @return 0 if success, otherwise errorcode */ int sip_strans_reply(struct sip_strans **stp, struct sip *sip, const struct sip_msg *msg, const struct sa *dst, uint16_t scode, struct mbuf *mb) { struct sip_strans *st = NULL; int err; if (!sip || !mb || !dst || (scode < 200 && !stp)) return EINVAL; if (stp) st = *stp; if (!st) { err = sip_strans_alloc(&st, sip, msg, NULL, NULL); if (err) return err; } mem_deref(st->mb); st->mb = mem_ref(mb); st->dst = *dst; err = sip_send(sip, st->msg->sock, st->msg->tp, dst, mb); if (stp) *stp = (err || scode >= 200) ? NULL : st; if (err) { mem_deref(st); return err; } if (st->invite) { if (scode < 200) { st->state = PROCEEDING; } else if (scode < 300) { tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st); st->state = ACCEPTED; } else { tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st); st->state = COMPLETED; if (!sip_transp_reliable(st->msg->tp)) tmr_start(&st->tmrg, SIP_T1, retransmit_handler, st); } } else { if (scode < 200) { st->state = PROCEEDING; } else { if (!sip_transp_reliable(st->msg->tp)) { tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st); st->state = COMPLETED; } else { mem_deref(st); } } } return 0; } int sip_strans_init(struct sip *sip, uint32_t sz) { int err; err = sip_listen(NULL, sip, true, request_handler, sip); if (err) return err; err = hash_alloc(&sip->ht_strans_mrg, sz); if (err) return err; return hash_alloc(&sip->ht_strans, sz); } static const char *statename(enum state state) { switch (state) { case TRYING: return "TRYING"; case PROCEEDING: return "PROCEEDING"; case ACCEPTED: return "ACCEPTED"; case COMPLETED: return "COMPLETED"; case CONFIRMED: return "CONFIRMED"; default: return "???"; } } static bool debug_handler(struct le *le, void *arg) { struct sip_strans *st = le->data; struct re_printf *pf = arg; (void)re_hprintf(pf, " %-10r %-10s %2llus (%r)\n", &st->msg->met, statename(st->state), tmr_get_expire(&st->tmr)/1000, &st->msg->via.branch); return false; } int sip_strans_debug(struct re_printf *pf, const struct sip *sip) { int err; err = re_hprintf(pf, "server transactions:\n"); hash_apply(sip->ht_strans, debug_handler, pf); return err; } ================================================ FILE: src/sip/transp.c ================================================ /** * @file sip/transp.c SIP Transport * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sip.h" #define DEBUG_MODULE "transp" #define DEBUG_LEVEL 5 #include enum { TCP_ACCEPT_TIMEOUT = 32, TCP_IDLE_TIMEOUT = 900, TCP_KEEPALIVE_TIMEOUT = 10, TCP_KEEPALIVE_INTVAL = 120, TCP_BUFSIZE_MAX = 65536, }; struct sip_ccert { struct le he; struct pl file; }; struct sip_ccert_data { uint32_t hsup; struct sip_ccert *ccert; }; struct sip_transport { struct le le; struct sa laddr; struct sip *sip; struct hash *ht_ccert; struct tls *tls; void *sock; enum sip_transp tp; uint8_t tos; struct http_cli *http_cli; struct http_sock *http_sock; }; struct sip_conn { struct le he; struct list ql; struct list kal; struct tmr tmr; struct tmr tmr_ka; struct sa laddr; struct sa paddr; struct tls_conn *sc; struct tcp_conn *tc; struct mbuf *mb; struct sip *sip; uint32_t ka_interval; bool established; enum sip_transp tp; struct websock_conn *websock_conn; }; struct sip_connqent { struct le le; struct mbuf *mb; struct sip_connqent **qentp; sip_transp_h *transph; void *arg; }; static uint8_t crlfcrlf[4] = {0x0d, 0x0a, 0x0d, 0x0a}; static void internal_transport_handler(int err, void *arg) { (void)err; (void)arg; } static void transp_destructor(void *arg) { struct sip_transport *transp = arg; if (transp->tp == SIP_TRANSP_UDP) udp_handler_set(transp->sock, NULL, NULL); list_unlink(&transp->le); hash_flush(transp->ht_ccert); mem_deref(transp->ht_ccert); mem_deref(transp->sock); mem_deref(transp->tls); mem_deref(transp->http_cli); mem_deref(transp->http_sock); } static void conn_destructor(void *arg) { struct sip_conn *conn = arg; tmr_cancel(&conn->tmr_ka); tmr_cancel(&conn->tmr); list_flush(&conn->kal); list_flush(&conn->ql); hash_unlink(&conn->he); mem_deref(conn->sc); mem_deref(conn->tc); mem_deref(conn->mb); mem_deref(conn->websock_conn); } static void qent_destructor(void *arg) { struct sip_connqent *qent = arg; if (qent->qentp) *qent->qentp = NULL; list_unlink(&qent->le); mem_deref(qent->mb); } static const struct sip_transport *transp_find(struct sip *sip, enum sip_transp tp, int af, const struct sa *dst) { struct le *le; struct sa dsttmp; const struct sip_transport *fb = NULL; for (le = sip->transpl.head; le; le = le->next) { const struct sip_transport *transp = le->data; const struct sa *laddr = &transp->laddr; struct sa src; if (transp->tp != tp) continue; if (af != AF_UNSPEC && sa_af(laddr) != af) continue; if (!sa_isset(dst, SA_ADDR)) return transp; if (sa_is_linklocal(laddr) != sa_is_linklocal(dst)) continue; if (!fb) fb = transp; sa_cpy(&dsttmp, dst); sa_set_scopeid(&dsttmp, sa_scopeid(laddr)); if (net_dst_source_addr_get(&dsttmp, &src)) continue; if (!sa_cmp(&src, laddr, SA_ADDR)) continue; return transp; } return fb; } static struct le *transp_apply_all(struct sip *sip, enum sip_transp tp, int af, list_apply_h ah, void *arg) { if (!ah) return NULL; for (struct le *le = sip->transpl.head; le; le = le->next) { const struct sip_transport *transp = le->data; const struct sa *laddr = &transp->laddr; if (transp->tp != tp) continue; if (af != AF_UNSPEC && sa_af(laddr) != af) continue; if (ah(le, arg)) return le; } return NULL; } static struct sip_conn *conn_find(struct sip *sip, const struct sa *paddr, bool secure) { struct le *le; le = list_head(hash_list(sip->ht_conn, sa_hash(paddr, SA_ALL))); for (; le; le = le->next) { struct sip_conn *conn = le->data; if (secure && !conn->sc) continue; if (!secure && conn->sc) continue; if (!sa_cmp(&conn->paddr, paddr, SA_ALL)) continue; return conn; } return NULL; } static struct sip_conn *ws_conn_find(struct sip *sip, const struct sa *paddr, enum sip_transp tp) { struct le *le; (void) tp; le = list_head(hash_list(sip->ht_conn, sa_hash(paddr, SA_ALL))); for (; le; le = le->next) { struct sip_conn *conn = le->data; /* if (tp != conn->tp) continue; */ if (!sa_cmp(&conn->paddr, paddr, SA_ALL)) continue; return conn; } return NULL; } static void conn_close(struct sip_conn *conn, int err) { struct le *le; conn->websock_conn = mem_deref(conn->websock_conn); conn->sc = mem_deref(conn->sc); conn->tc = mem_deref(conn->tc); tmr_cancel(&conn->tmr_ka); tmr_cancel(&conn->tmr); hash_unlink(&conn->he); le = list_head(&conn->ql); while (le) { struct sip_connqent *qent = le->data; le = le->next; bool qentp_set = qent->qentp ? true : false; qent->transph(err, qent->arg); if (!qentp_set) { list_unlink(&qent->le); mem_deref(qent); } } sip_keepalive_signal(&conn->kal, err); } static void conn_tmr_handler(void *arg) { struct sip_conn *conn = arg; conn_close(conn, ETIMEDOUT); mem_deref(conn); } static void conn_keepalive_handler(void *arg) { struct sip_conn *conn = arg; struct mbuf mb; int err; mb.buf = crlfcrlf; mb.size = sizeof(crlfcrlf); mb.pos = 0; mb.end = 4; err = tcp_send(conn->tc, &mb); if (err) { conn_close(conn, err); mem_deref(conn); return; } tmr_start(&conn->tmr, TCP_KEEPALIVE_TIMEOUT * 1000, conn_tmr_handler, conn); tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval), conn_keepalive_handler, conn); } static bool have_essential_fields(const struct sip_msg *msg) { if (pl_isset(&(msg->to.auri)) && pl_isset(&(msg->from.auri)) && pl_isset(&(msg->cseq.met)) && pl_isset(&(msg->callid)) && pl_isset(&(msg->via.branch))) return true; return false; } static void sip_recv(struct sip *sip, const struct sip_msg *msg, size_t start) { struct le *le = sip->lsnrl.head; if (sip->traceh) { sip->traceh(false, msg->tp, &msg->src, &msg->dst, msg->mb->buf + start, msg->mb->end - start, sip->arg); } if (msg->req) { if (!have_essential_fields(msg)){ (void)sip_reply(sip, msg, 400, "Bad Request"); return; } } /* check consistency between CSeq method and that of request line */ if (msg->req && pl_casecmp(&(msg->cseq.met), &(msg->met))){ (void)sip_reply(sip, msg, 400, "Bad Request"); return; } while (le) { struct sip_lsnr *lsnr = le->data; le = le->next; if (msg->req != lsnr->req) continue; if (lsnr->msgh(msg, lsnr->arg)) return; } if (msg->req) { (void)re_fprintf(stderr, "unhandled request from %J: %r %r\n", &msg->src, &msg->met, &msg->ruri); if (!pl_strcmp(&msg->met, "CANCEL")) (void)sip_reply(sip, msg, 481, "Transaction Does Not Exist"); else (void)sip_reply(sip, msg, 501, "Not Implemented"); } else { (void)re_fprintf(stderr, "unhandled response from %J:" " %u %r (%r)\n", &msg->src, msg->scode, &msg->reason, &msg->cseq.met); } } static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { struct sip_transport *transp = arg; struct stun_unknown_attr ua; struct stun_msg *stun_msg; struct sip_msg *msg; int err; if (mb->end <= 4) return; if (!stun_msg_decode(&stun_msg, mb, &ua)) { if (stun_msg_method(stun_msg) == STUN_METHOD_BINDING) { switch (stun_msg_class(stun_msg)) { case STUN_CLASS_REQUEST: (void)stun_reply(IPPROTO_UDP, transp->sock, src, 0, stun_msg, NULL, 0, false, 2, STUN_ATTR_XOR_MAPPED_ADDR, src, STUN_ATTR_SOFTWARE, transp->sip->software); break; default: (void)stun_ctrans_recv(transp->sip->stun, stun_msg, &ua); break; } } mem_deref(stun_msg); return; } err = sip_msg_decode(&msg, mb); if (err) { (void)re_fprintf(stderr, "sip: msg decode err: %m\n", err); return; } msg->sock = mem_ref(transp->sock); msg->src = *src; msg->dst = transp->laddr; msg->tp = SIP_TRANSP_UDP; sa_set_scopeid(&msg->src, sa_scopeid(&transp->laddr)); sip_recv(transp->sip, msg, 0); mem_deref(msg); } static void tcp_recv_handler(struct mbuf *mb, void *arg) { struct sip_conn *conn = arg; size_t pos; int err = 0; if (conn->mb) { pos = conn->mb->pos; conn->mb->pos = conn->mb->end; err = mbuf_write_mem(conn->mb, mbuf_buf(mb),mbuf_get_left(mb)); if (err) goto out; conn->mb->pos = pos; if (mbuf_get_left(conn->mb) > TCP_BUFSIZE_MAX) { err = EOVERFLOW; goto out; } } else { conn->mb = mem_ref(mb); } for (;;) { struct sip_msg *msg; uint32_t clen; size_t end; if (mbuf_get_left(conn->mb) < 2) break; if (!memcmp(mbuf_buf(conn->mb), "\r\n", 2)) { tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn); conn->mb->pos += 2; if (mbuf_get_left(conn->mb) >= 2 && !memcmp(mbuf_buf(conn->mb), "\r\n", 2)) { struct mbuf mbr; conn->mb->pos += 2; mbr.buf = crlfcrlf; mbr.size = sizeof(crlfcrlf); mbr.pos = 0; mbr.end = 2; err = tcp_send(conn->tc, &mbr); if (err) break; } if (mbuf_get_left(conn->mb)) continue; conn->mb = mem_deref(conn->mb); break; } pos = conn->mb->pos; err = sip_msg_decode(&msg, conn->mb); if (err) { if (err == ENODATA) err = 0; break; } if (!msg->clen.p) { mem_deref(msg); err = EBADMSG; break; } clen = pl_u32(&msg->clen); if (mbuf_get_left(conn->mb) < clen) { conn->mb->pos = pos; mem_deref(msg); break; } tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn); end = conn->mb->end; msg->mb->end = msg->mb->pos + clen; msg->sock = mem_ref(conn); msg->src = conn->paddr; msg->dst = conn->laddr; msg->tp = conn->sc ? SIP_TRANSP_TLS : SIP_TRANSP_TCP; sip_recv(conn->sip, msg, 0); mem_deref(msg); if (end <= conn->mb->end) { conn->mb = mem_deref(conn->mb); break; } mb = mbuf_alloc(end - conn->mb->end); if (!mb) { err = ENOMEM; goto out; } (void)mbuf_write_mem(mb, &conn->mb->buf[conn->mb->end], end - conn->mb->end); mb->pos = 0; mem_deref(conn->mb); conn->mb = mb; } out: if (err) { conn_close(conn, err); mem_deref(conn); } } static void trace_send(struct sip *sip, enum sip_transp tp, void *sock, const struct sa *dst, struct mbuf *mb) { struct sa src; struct sip_conn *conn; if (sip->traceh) { switch (tp) { case SIP_TRANSP_UDP: if (udp_local_get(sock, &src)) sa_init(&src, sa_af(dst)); break; case SIP_TRANSP_TCP: case SIP_TRANSP_TLS: case SIP_TRANSP_WS: case SIP_TRANSP_WSS: conn = sock; src = conn->laddr; break; default: return; } sip->traceh(true, tp, &src, dst, mbuf_buf(mb), mbuf_get_left(mb), sip->arg); } } static void tcp_estab_handler(void *arg) { struct sip_conn *conn = arg; struct le *le; int err; #ifdef WIN32 tcp_conn_local_get(conn->tc, &conn->laddr); #endif conn->established = true; le = list_head(&conn->ql); while (le) { struct sip_connqent *qent = le->data; bool qentp_set = qent->qentp ? true : false; le = le->next; trace_send(conn->sip, conn->sc ? SIP_TRANSP_TLS : SIP_TRANSP_TCP, conn, &conn->paddr, qent->mb); err = tcp_send(conn->tc, qent->mb); if (err) qent->transph(err, qent->arg); if (!qentp_set) { list_unlink(&qent->le); mem_deref(qent); } } } static void tcp_close_handler(int err, void *arg) { struct sip_conn *conn = arg; conn_close(conn, err ? err : ECONNRESET); mem_deref(conn); } static void tcp_connect_handler(const struct sa *paddr, void *arg) { struct sip_transport *transp = arg; struct sip_conn *conn; int err; conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) { err = ENOMEM; goto out; } hash_append(transp->sip->ht_conn, sa_hash(paddr, SA_ALL), &conn->he, conn); conn->paddr = *paddr; conn->sip = transp->sip; err = tcp_accept(&conn->tc, transp->sock, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, conn); if (err) goto out; err = tcp_conn_local_get(conn->tc, &conn->laddr); if (err) goto out; (void)tcp_conn_settos(conn->tc, transp->tos); #ifdef USE_TLS if (transp->tls) { err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0); if (err) goto out; err = tls_verify_client(conn->sc); if (err) goto out; } #endif conn->tp = transp->tls ? SIP_TRANSP_TLS : SIP_TRANSP_TCP; tmr_start(&conn->tmr, TCP_ACCEPT_TIMEOUT * 1000, conn_tmr_handler, conn); out: if (err) { tcp_reject(transp->sock); mem_deref(conn); } } #ifdef USE_TLS static uint32_t get_hash_of_fromhdr(struct mbuf *mb) { struct sip_msg *msg; struct mbuf *sup = NULL; uint32_t hsup = 0; int err = 0; err = sip_msg_decode(&msg, mb); if (err) return 0; sup = mbuf_alloc(30); if (!sup) return ENOMEM; err = mbuf_printf(sup, "\"%r\" <%r:%r@%r:%d>", &msg->from.uri.user, &msg->from.uri.scheme, &msg->from.uri.user, &msg->from.uri.host, msg->from.uri.port); if (err) goto out; mbuf_set_pos(sup, 0); hsup = hash_joaat(mbuf_buf(sup), mbuf_get_left(sup)); mbuf_set_pos(mb, 0); out: mem_deref(msg); mem_deref(sup); return hsup; } #endif static int conn_send(struct sip_connqent **qentp, struct sip *sip, bool secure, const struct sa *dst, char *host, struct mbuf *mb, sip_conn_h *connh, sip_transp_h *transph, void *arg) { struct sip_conn *conn, *new_conn = NULL; struct sip_conncfg *conncfg; struct sip_connqent *qent; int err = 0; #ifndef USE_TLS (void) host; #endif conn = conn_find(sip, dst, secure); if (conn) { if (connh) err = connh(&conn->laddr, dst, mb, arg); if (!conn->established) goto enqueue; trace_send(sip, secure ? SIP_TRANSP_TLS : SIP_TRANSP_TCP, conn, dst, mb); return tcp_send(conn->tc, mb); } new_conn = conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return ENOMEM; hash_append(sip->ht_conn, sa_hash(dst, SA_ALL), &conn->he, conn); conn->paddr = *dst; conn->sip = sip; conn->tp = secure ? SIP_TRANSP_TLS : SIP_TRANSP_TCP; conncfg = sip_conncfg_find(sip, dst); if (conncfg && conncfg->srcport) { struct sa src; sa_init(&src, sa_af(dst)); sa_set_port(&src, conncfg->srcport); err = tcp_connect_bind(&conn->tc, dst, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, &src, conn); } else { err = tcp_connect(&conn->tc, dst, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, conn); } if (err) goto out; err = tcp_conn_local_get(conn->tc, &conn->laddr); if (err) goto out; /* Fallback check for any address win32 */ if (!sa_isset(&conn->laddr, SA_ALL)) { uint16_t port = sa_port(&conn->laddr); err = sip_transp_laddr(sip, &conn->laddr, conn->tp, dst); if (err) goto out; if (port) sa_set_port(&conn->laddr, port); } if (connh) { err = connh(&conn->laddr, dst, mb, arg); if (err) goto out; } (void)tcp_conn_settos(conn->tc, sip->tos); #ifdef USE_TLS if (secure) { const struct sip_transport *transp; struct sip_ccert *ccert; uint32_t hash = 0; transp = transp_find(sip, SIP_TRANSP_TLS, sa_af(dst), dst); if (!transp || !transp->tls) { err = EPROTONOSUPPORT; goto out; } err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0); if (err) goto out; hash = get_hash_of_fromhdr(mb); ccert = list_ledata( list_head(hash_list(transp->ht_ccert, hash))); if (ccert) { char *f; err = pl_strdup(&f, &ccert->file); if (err) goto out; err = tls_conn_change_cert(conn->sc, f); mem_deref(f); if (err) goto out; } err |= tls_set_verify_server(conn->sc, host); if (err) goto out; } #endif tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn); enqueue: qent = mem_zalloc(sizeof(*qent), qent_destructor); if (!qent) { err = ENOMEM; goto out; } list_append(&conn->ql, &qent->le, qent); qent->mb = mem_ref(mb); qent->transph = transph ? transph : internal_transport_handler; qent->arg = arg; if (qentp) { qent->qentp = qentp; *qentp = qent; } out: if (err) mem_deref(new_conn); return err; } static void websock_estab_handler(void *arg) { struct sip_conn *conn = arg; struct le *le; int err; re_printf("<%p> %s websock established to %J\n", conn, sip_transp_name(conn->tp), &conn->paddr); conn->established = true; err = tcp_conn_local_get(websock_tcp(conn->websock_conn), &conn->laddr); if (err) return; le = list_head(&conn->ql); while (le) { struct sip_connqent *qent = le->data; bool qentp_set = qent->qentp ? true : false; le = le->next; trace_send(conn->sip, conn->tp, conn, &conn->paddr, qent->mb); re_printf("--> send\n"); err = websock_send(conn->websock_conn, WEBSOCK_BIN, "%b", mbuf_buf(qent->mb), mbuf_get_left(qent->mb)); if (err) qent->transph(err, qent->arg); if (!qentp_set) { list_unlink(&qent->le); mem_deref(qent); } } } static void websock_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb, void *arg) { struct sip_conn *conn = arg; struct sip_msg *msg; size_t start; int err; (void) hdr; #if 0 re_printf( "~ ~ ~ ~ ~ websock receive: ~ ~ ~ ~ ~\n" "\x1b[32m" "%b" "\x1b[;m\t\n" "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~\n" , mbuf_buf(mb), mbuf_get_left(mb)); #endif if (mb->end <= 4) return; tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn); start = mb->pos; err = sip_msg_decode(&msg, mb); if (err) { (void)re_fprintf(stderr, "sip: msg decode err: %m\n", err); return; } msg->sock = mem_ref(conn); msg->src = conn->paddr; msg->dst = conn->laddr; msg->tp = conn->tp; sip_recv(conn->sip, msg, start); mem_deref(msg); } static void websock_close_handler(int err, void *arg) { struct sip_conn *conn = arg; re_printf("sip: websock connection closed (%m)\n", err); conn_close(conn, err ? err : ECONNRESET); mem_deref(conn); } static int ws_conn_send(struct sip_connqent **qentp, struct sip *sip, bool secure, const struct sa *dst, struct mbuf *mb, sip_transp_h *transph, void *arg) { struct sip_conn *conn, *new_conn = NULL; struct sip_connqent *qent; struct sip_transport *transp; enum sip_transp tp; const char *prefix; char ws_uri[256]; int err = 0; if (secure) { prefix = "wss"; tp = SIP_TRANSP_WSS; } else { prefix = "ws"; tp = SIP_TRANSP_WS; } conn = ws_conn_find(sip, dst, tp); if (conn) { if (!conn->established) goto enqueue; trace_send(sip, secure ? SIP_TRANSP_WSS : SIP_TRANSP_WS, conn, dst, mb); return websock_send(conn->websock_conn, WEBSOCK_BIN, "%b", mbuf_buf(mb), mbuf_get_left(mb)); } transp = (struct sip_transport *)transp_find(sip, tp, sa_af(dst), dst); if (!transp) { err = EPROTONOSUPPORT; goto out; } new_conn = conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return ENOMEM; hash_append(sip->ht_conn, sa_hash(dst, SA_ALL), &conn->he, conn); conn->paddr = *dst; conn->sip = sip; conn->tp = tp; /* TODO: how to select ports of outbound SIP/WS proxy ? * TODO: http url path "test" is temp, add config */ /* Use port if specified, otherwise use default HTTP/HTTPS ports */ if (sa_port(dst)) { if (re_snprintf(ws_uri, sizeof(ws_uri), "%s://%J/", prefix, dst) < 0) { err = ENOMEM; goto out; } } else { if (re_snprintf(ws_uri, sizeof(ws_uri), "%s://%j/", prefix, dst) < 0) { err = ENOMEM; goto out; } } if (!transp->http_cli) { err = http_client_alloc(&transp->http_cli, sip->dnsc); if (err) { re_fprintf(stderr, "transp: could not create" " http client (%m)\n", err); goto out; } #ifdef USE_TLS if (transp->tls) http_client_set_tls(transp->http_cli, transp->tls); #endif } re_printf("websock: connecting to '%s'\n", ws_uri); err = websock_connect(&conn->websock_conn, sip->websock, transp->http_cli, ws_uri, 15000, websock_estab_handler, websock_recv_handler, websock_close_handler, conn, "Sec-WebSocket-Protocol: sip\r\n"); if (err) { re_printf("websock_connect: %m\n", err); goto out; } tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn); enqueue: qent = mem_zalloc(sizeof(*qent), qent_destructor); if (!qent) { err = ENOMEM; goto out; } list_append(&conn->ql, &qent->le, qent); qent->mb = mem_ref(mb); qent->transph = transph ? transph : internal_transport_handler; qent->arg = arg; if (qentp) { qent->qentp = qentp; *qentp = qent; } out: if (err) mem_deref(new_conn); return err; } static int dst_set_scopeid(struct sip *sip, struct sa *dst, enum sip_transp tp) { struct sa laddr; int err; if (sa_af(dst) != AF_INET6 || !sa_is_linklocal(dst)) return 0; err = sip_transp_laddr(sip, &laddr, tp, dst); if (err) return err; sa_set_scopeid(dst, sa_scopeid(&laddr)); return 0; } int sip_transp_init(struct sip *sip, uint32_t sz) { int err; err = hash_alloc(&sip->ht_conn, sz); err |= hash_alloc(&sip->ht_conncfg, sz); return err; } static void http_req_handler(struct http_conn *hc, const struct http_msg *msg, void *arg) { struct sip_transport *transp = arg; struct sip_conn *conn = NULL; const struct sa *paddr; const struct http_hdr *hdr; int err; paddr = http_conn_peer(hc); re_printf("http request from %J\n", paddr); hdr = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_PROTOCOL); if (!hdr) { re_printf("sip: missing Sec-WebSocket-Protocol header\n"); err = EPROTO; goto out; } if (0 != pl_strcasecmp(&hdr->val, "sip")) { re_printf("sip: unknown Sec-WebSocket-Protocol '%r'\n", &hdr->val); err = EPROTO; goto out; } conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) { err = ENOMEM; goto out; } hash_append(transp->sip->ht_conn, sa_hash(paddr, SA_ALL), &conn->he, conn); conn->paddr = *paddr; conn->sip = transp->sip; conn->tp = transp->tp; err = websock_accept_proto(&conn->websock_conn, "sip", transp->sip->websock, hc, msg, 15000, websock_recv_handler, websock_close_handler, conn); if (err) goto out; err = tcp_conn_local_get(websock_tcp(conn->websock_conn), &conn->laddr); if (err) goto out; out: if (err) { (void)http_reply(hc, 500, "Server Error", NULL); mem_deref(conn); } } /** * Add a SIP transport * * @param sip SIP stack instance * @param tp SIP Transport * @param listen True to open listening socket (UDP socket always opened) * @param laddr Local network address * @param ap Optional transport parameters such as TLS context * * @return 0 if success, otherwise errorcode */ static int add_transp(struct sip *sip, enum sip_transp tp, bool listen, const struct sa *laddr, va_list ap) { struct sip_transport *transp; struct tls *tls; int err = 0; if (!sip || !laddr || !sa_isset(laddr, SA_ADDR)) return EINVAL; transp = mem_zalloc(sizeof(*transp), transp_destructor); if (!transp) return ENOMEM; if (tp == SIP_TRANSP_TLS) { err = hash_alloc(&transp->ht_ccert, 32); if (err) { mem_deref(transp); return err; } } list_append(&sip->transpl, &transp->le, transp); transp->sip = sip; transp->tp = tp; switch (tp) { case SIP_TRANSP_UDP: err = udp_listen((struct udp_sock **)&transp->sock, laddr, udp_recv_handler, transp); if (err) break; err = udp_local_get(transp->sock, &transp->laddr); break; case SIP_TRANSP_TLS: tls = va_arg(ap, struct tls *); if (!tls) { err = EINVAL; break; } transp->tls = mem_ref(tls); /*@fallthrough@*/ case SIP_TRANSP_TCP: if (!listen) { transp->laddr = *laddr; sa_set_port(&transp->laddr, 0); return err; } err = tcp_listen((struct tcp_sock **)&transp->sock, laddr, tcp_connect_handler, transp); if (err) break; err = tcp_sock_local_get(transp->sock, &transp->laddr); break; default: err = EPROTONOSUPPORT; break; } if (err) mem_deref(transp); return err; } /** * Add a SIP transport * * @param sip SIP stack instance * @param tp SIP Transport * @param laddr Local network address * @param ... Optional transport parameters such as TLS context * * @return 0 if success, otherwise errorcode */ int sip_transp_add(struct sip *sip, enum sip_transp tp, const struct sa *laddr, ...) { int err; va_list ap; va_start(ap, laddr); err = add_transp(sip, tp, true, laddr, ap); va_end(ap); return err; } /** * Add a SIP transport and open listening socket if requested * * UDP socket will always be opened even if listen is false. * * @param sip SIP stack instance * @param tp SIP Transport * @param listen True to open listening socket * @param laddr Local network address * @param ... Optional transport parameters such as TLS context * * @return 0 if success, otherwise errorcode */ int sip_transp_add_sock(struct sip *sip, enum sip_transp tp, bool listen, const struct sa *laddr, ...) { int err; va_list ap; va_start(ap, laddr); err = add_transp(sip, tp, listen, laddr, ap); va_end(ap); return err; } /** * Add a SIP websocket transport * * @param sip SIP stack instance * @param tp SIP Transport * @param laddr Local network address * @param server True if server, otherwise false * @param cert Server Certificate * @param tls Optional TLS context * * @return 0 if success, otherwise errorcode */ int sip_transp_add_websock(struct sip *sip, enum sip_transp tp, const struct sa *laddr, bool server, const char *cert, struct tls *tls) { struct sip_transport *transp; bool secure = tp == SIP_TRANSP_WSS; int err = 0; if (!sip || !laddr || !sa_isset(laddr, SA_ADDR)) return EINVAL; transp = mem_zalloc(sizeof(*transp), transp_destructor); if (!transp) return ENOMEM; list_append(&sip->transpl, &transp->le, transp); transp->sip = sip; transp->tp = tp; if (tls) transp->tls = mem_ref(tls); if (server) { if (secure) { err = https_listen(&transp->http_sock, laddr, cert, http_req_handler, transp); if (err) { re_fprintf(stderr, "websock: https_listen" " error (%m)\n", err); goto out; } } else { err = http_listen(&transp->http_sock, laddr, http_req_handler, transp); if (err) { re_fprintf(stderr, "websock: http_listen" " error (%m)\n", err); goto out; } } err = tcp_sock_local_get(http_sock_tcp(transp->http_sock), &transp->laddr); if (err) goto out; } else { transp->laddr = *laddr; sa_set_port(&transp->laddr, 9); } out: if (err) mem_deref(transp); return err; } static bool add_ccert_handler(struct le *le, void *arg) { const struct sip_transport *transp = le->data; struct sip_ccert_data *cc = arg; if (!cc->ccert->he.list) hash_append(transp->ht_ccert, cc->hsup, &cc->ccert->he, cc->ccert); else { struct sip_ccert *ccert = mem_zalloc(sizeof(*ccert), NULL); if (!ccert) return false; ccert->file = cc->ccert->file; hash_append(transp->ht_ccert, cc->hsup, &ccert->he, ccert); } return false; } /** * Add a client certificate to the TLS transport object * Client certificates are saved as hash-table. * Hashtable-Key: "username" * * @param sip Global SIP stack * @param uri Account uri information * @param cert Certificate + Key file * * @return int 0 if success, otherwise errorcode */ int sip_transp_add_ccert(struct sip *sip, const struct uri *uri, const char *cert) { int err = 0; struct sip_ccert *ccert = NULL; struct sip_ccert_data cc_data; struct mbuf *sup = NULL; if (!sip || !uri || !cert) return EINVAL; sup = mbuf_alloc(30); if (!sup) return ENOMEM; err = mbuf_printf(sup, "\"%r\" <%r:%r@%r:%d>", &uri->user, &uri->scheme, &uri->user, &uri->host, uri->port); if (err) goto out; mbuf_set_pos(sup, 0); ccert = mem_zalloc(sizeof(*ccert), NULL); if (!ccert) { err = ENOMEM; goto out; } pl_set_str(&ccert->file, cert); cc_data.hsup = hash_joaat(mbuf_buf(sup), mbuf_get_left(sup)); cc_data.ccert = ccert; (void)transp_apply_all(sip, SIP_TRANSP_TLS, AF_INET, add_ccert_handler, &cc_data); (void)transp_apply_all(sip, SIP_TRANSP_TLS, AF_INET6, add_ccert_handler, &cc_data); out: mem_deref(sup); return err; } /** * Flush all transports of a SIP stack instance * * @param sip SIP stack instance */ void sip_transp_flush(struct sip *sip) { if (!sip) return; hash_flush(sip->ht_conn); hash_flush(sip->ht_conncfg); list_flush(&sip->transpl); } int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock, enum sip_transp tp, const struct sa *dst, char *host, struct mbuf *mb, sip_conn_h *connh, sip_transp_h *transph, void *arg) { const struct sip_transport *transp; struct sip_conn *conn; bool secure = false; struct sa dsttmp; struct sa laddr; int err; if (!sip || !dst || !mb) return EINVAL; sa_cpy(&dsttmp, dst); err = dst_set_scopeid(sip, &dsttmp, tp); if (err) return err; switch (tp) { case SIP_TRANSP_UDP: err = sip_transp_laddr(sip, &laddr, tp, dst); if (err) return err; if (connh) connh(&laddr, dst, mb, arg); if (!sock) { transp = transp_find(sip, tp, sa_af(&dsttmp), &dsttmp); if (!transp) return EPROTONOSUPPORT; sock = transp->sock; } trace_send(sip, tp, sock, &dsttmp, mb); err = udp_send(sock, &dsttmp, mb); break; case SIP_TRANSP_TLS: secure = true; /*@fallthrough@*/ case SIP_TRANSP_TCP: conn = sock; if (conn && conn->tc) { if (connh) { err = connh(&conn->laddr, dst, mb, arg); if (err) return err; } trace_send(sip, tp, conn, &dsttmp, mb); err = tcp_send(conn->tc, mb); } else err = conn_send(qentp, sip, secure, &dsttmp, host, mb, connh, transph, arg); break; case SIP_TRANSP_WSS: secure = true; /*@fallthrough@*/ case SIP_TRANSP_WS: /*TODO: Ideally connh should be called if the websocket was * opened and the source port is known. As a workaround the * listen port is used for Contact and Via headers */ err = sip_transp_laddr(sip, &laddr, tp, dst); if (err) return err; if (connh) connh(&laddr, dst, mb, arg); conn = sock; if (conn && conn->websock_conn) { trace_send(sip, tp, conn, &dsttmp, mb); err = websock_send(conn->websock_conn, WEBSOCK_BIN, "%b", mbuf_buf(mb), mbuf_get_left(mb)); if (err) { re_fprintf(stderr, "websock_send failed" " (%m)\n", err); } } else { err = ws_conn_send(qentp, sip, secure, &dsttmp, mb, transph, arg); if (err) { re_fprintf(stderr, "ws_conn_send failed" " (%m)\n", err); } } break; default: err = EPROTONOSUPPORT; break; } return err; } int sip_transp_laddr(struct sip *sip, struct sa *laddr, enum sip_transp tp, const struct sa *dst) { const struct sip_transport *transp; struct sip_conncfg *conncfg; if (!sip || !laddr) return EINVAL; transp = transp_find(sip, tp, sa_af(dst), dst); if (!transp) return EPROTONOSUPPORT; *laddr = transp->laddr; if (tp != SIP_TRANSP_UDP) { conncfg = sip_conncfg_find(sip, dst); if (conncfg && conncfg->srcport) sa_set_port(laddr, conncfg->srcport); } return 0; } bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af) { if (!sip) return false; return transp_find(sip, tp, af, NULL) != NULL; } int sip_transp_set_default(struct sip *sip, enum sip_transp tp) { if (!sip) return EINVAL; sip->tp_def = tp; return 0; } /** * Check if network address is part of SIP transports * * @param sip SIP stack instance * @param tp SIP transport * @param laddr Local network address to check * * @return True if part of SIP transports, otherwise false */ bool sip_transp_isladdr(const struct sip *sip, enum sip_transp tp, const struct sa *laddr) { struct le *le; if (!sip || !laddr) return false; for (le=sip->transpl.head; le; le=le->next) { const struct sip_transport *transp = le->data; if (tp != SIP_TRANSP_NONE && transp->tp != tp) continue; if (!sa_cmp(&transp->laddr, laddr, SA_ALL)) continue; return true; } return false; } /** * Get the name of a given SIP Transport * * @param tp SIP Transport * * @return Name of the corresponding SIP Transport */ const char *sip_transp_name(enum sip_transp tp) { switch (tp) { case SIP_TRANSP_UDP: return "UDP"; case SIP_TRANSP_TCP: return "TCP"; case SIP_TRANSP_TLS: return "TLS"; case SIP_TRANSP_WS: return "WS"; case SIP_TRANSP_WSS: return "WSS"; default: return "???"; } } const char *sip_transp_srvid(enum sip_transp tp) { switch (tp) { case SIP_TRANSP_UDP: return "_sip._udp"; case SIP_TRANSP_TCP: return "_sip._tcp"; case SIP_TRANSP_TLS: return "_sips._tcp"; default: return "???"; } } /** * Get the transport parameters for a given SIP Transport * * @param tp SIP Transport * * @return Transport parameters of the corresponding SIP Transport */ const char *sip_transp_param(enum sip_transp tp) { switch (tp) { case SIP_TRANSP_UDP: return ""; case SIP_TRANSP_TCP: return ";transport=tcp"; case SIP_TRANSP_TLS: return ";transport=tls"; case SIP_TRANSP_WS: return ";transport=ws"; case SIP_TRANSP_WSS: return ";transport=wss"; default: return ""; } } enum sip_transp sip_transp_decode(const struct pl *pl) { enum sip_transp tp = SIP_TRANSP_NONE; if (!pl_strcasecmp(pl, "udp")) tp = SIP_TRANSP_UDP; else if (!pl_strcasecmp(pl, "tcp")) tp = SIP_TRANSP_TCP; else if (!pl_strcasecmp(pl, "tls")) tp = SIP_TRANSP_TLS; else if (!pl_strcasecmp(pl, "ws")) tp = SIP_TRANSP_WS; else if (!pl_strcasecmp(pl, "wss")) tp = SIP_TRANSP_WSS; return tp; } bool sip_transp_reliable(enum sip_transp tp) { switch (tp) { case SIP_TRANSP_UDP: return false; case SIP_TRANSP_TCP: return true; case SIP_TRANSP_TLS: return true; case SIP_TRANSP_WS: return true; case SIP_TRANSP_WSS: return true; default: return false; } } /** * Get the default port number for a given SIP Transport * * @param tp SIP Transport * @param port Port number * * @return Corresponding port number */ uint16_t sip_transp_port(enum sip_transp tp, uint16_t port) { if (port) return port; switch (tp) { case SIP_TRANSP_UDP: return SIP_PORT; case SIP_TRANSP_TCP: return SIP_PORT; case SIP_TRANSP_TLS: return SIP_PORT_TLS; case SIP_TRANSP_WS: return 80; case SIP_TRANSP_WSS: return 443; default: return 0; } } int sip_settos(struct sip *sip, uint8_t tos) { struct le *le; int err = 0; if (!sip) return EINVAL; sip->tos = tos; for (le = sip->transpl.head; le; le = le->next) { struct sip_transport *transp = le->data; transp->tos = tos; switch (transp->tp) { case SIP_TRANSP_UDP: err = udp_settos(transp->sock, tos); break; case SIP_TRANSP_TCP: case SIP_TRANSP_TLS: err = tcp_settos(transp->sock, tos); break; default: break; } if (err) break; } return err; } static void sip_transports_print(struct re_printf *pf, const struct sip* sip) { uint32_t mask = 0; for (struct le *le = sip->transpl.head; le; le = le->next) { const struct sip_transport *transp = le->data; mask |= (1 << transp->tp); } for (uint8_t i = 0; i < SIP_TRANSPC; ++i) { if (mask==0 || (0 != (mask & (1u << i)))) (void)re_hprintf(pf, " %s\n", sip_transp_name(i)); } } static bool debug_handler(struct le *le, void *arg) { const struct sip_transport *transp = le->data; struct re_printf *pf = arg; if (sa_port(&transp->laddr) == 0) return false; (void)re_hprintf(pf, " %J (%s)\n", &transp->laddr, sip_transp_name(transp->tp)); return false; } static bool conn_debug_handler(struct le *le, void *arg) { struct sip_conn *conn = le->data; struct re_printf *pf = arg; (void)re_hprintf(pf, " [%u] %5s %J --> %J (%s)\n", mem_nrefs(conn), sip_transp_name(conn->tp), &conn->laddr, &conn->paddr, conn->established ? "Established" : "..." ); return false; } static bool conncfg_debug_handler(struct le *le, void *arg) { struct sip_conncfg *conncfg = le->data; struct re_printf *pf = arg; (void)re_hprintf(pf, " TCP source port %u\n", conncfg->srcport); return false; } int sip_transp_debug(struct re_printf *pf, const struct sip *sip) { int err; err = re_hprintf(pf, "transports:\n"); sip_transports_print(pf, sip); err |= re_hprintf(pf, "transport sockets:\n"); list_apply(&sip->transpl, true, debug_handler, pf); err |= re_hprintf(pf, "connections:\n"); hash_apply(sip->ht_conn, conn_debug_handler, pf); err |= re_hprintf(pf, "connection configurations:\n"); hash_apply(sip->ht_conncfg, conncfg_debug_handler, pf); return err; } /** * Get the TCP Connection from a SIP Message * * @param msg SIP Message * * @return TCP Connection if reliable transport, otherwise NULL */ struct tcp_conn *sip_msg_tcpconn(const struct sip_msg *msg) { if (!msg || !msg->sock) return NULL; switch (msg->tp) { case SIP_TRANSP_TCP: case SIP_TRANSP_TLS: return ((struct sip_conn *)msg->sock)->tc; case SIP_TRANSP_WS: case SIP_TRANSP_WSS: { struct sip_conn *conn = msg->sock; return websock_tcp(conn->websock_conn); } default: return NULL; } } int sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn, uint32_t interval) { if (!ka || !conn) return EINVAL; if (!conn->tc || !conn->established) return ENOTCONN; list_append(&conn->kal, &ka->le, ka); if (!tmr_isrunning(&conn->tmr_ka)) { interval = MAX(interval ? interval : TCP_KEEPALIVE_INTVAL, TCP_KEEPALIVE_TIMEOUT * 2); conn->ka_interval = interval; tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval), conn_keepalive_handler, conn); } return 0; } /** * Remove all SIP transport instances that are bound to the given local network * address * * @param sip SIP stack instance * @param laddr Local network address */ void sip_transp_rmladdr(struct sip *sip, const struct sa *laddr) { struct le *le; struct le *len = NULL; if (!sip || !laddr) return; for (le = sip->transpl.head; le; le = len) { struct sip_transport *transp = le->data; len = le->next; if (sa_cmp(&transp->laddr, laddr, SA_ADDR)) mem_deref(transp); } } /** * Set a SIP connection configuration for a given peer address * * @param sip SIP stack instance * @param paddr Peer address * @param conncfg A SIP connection configuration * * @return 0 if success, otherwise errorcode */ int sip_conncfg_set(struct sip *sip, const struct sa *paddr, const struct sip_conncfg *conncfg) { struct sip_conncfg *cfg; if (!sip || !sa_isset(paddr, SA_ALL)) return EINVAL; cfg = sip_conncfg_find(sip, paddr); if (cfg) { cfg->srcport = conncfg->srcport; return 0; } else { cfg = mem_zalloc(sizeof(*cfg), NULL); } if (!cfg) return ENOMEM; memcpy(cfg, conncfg, sizeof(*cfg)); memset(&cfg->he, 0, sizeof(cfg->he)); sa_cpy(&cfg->paddr, paddr); hash_append(sip->ht_conncfg, sa_hash(paddr, SA_ALL), &cfg->he, cfg); return 0; } ================================================ FILE: src/sip/via.c ================================================ /** * @file via.c SIP Via decode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include static int decode_hostport(const struct pl *hostport, struct pl *host, struct pl *port) { /* Try IPv6 first */ if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*", host, NULL, port)) return 0; /* Then non-IPv6 host */ return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*", host, NULL, port); } /** * Decode a pointer-length string into a SIP Via header * * @param via SIP Via header * @param pl Pointer-length string * * @return 0 for success, otherwise errorcode */ int sip_via_decode(struct sip_via *via, const struct pl *pl) { struct pl transp, host, port; int err; if (!via || !pl) return EINVAL; err = re_regex(pl->p, pl->l, "SIP[ \t\r\n]*/[ \t\r\n]*2.0[ \t\r\n]*/[ \t\r\n]*" "[A-Z]+[ \t\r\n]*[^; \t\r\n]+[ \t\r\n]*[^]*", NULL, NULL, NULL, NULL, &transp, NULL, &via->sentby, NULL, &via->params); if (err) return err; if (!pl_strcmp(&transp, "TCP")) via->tp = SIP_TRANSP_TCP; else if (!pl_strcmp(&transp, "TLS")) via->tp = SIP_TRANSP_TLS; else if (!pl_strcmp(&transp, "UDP")) via->tp = SIP_TRANSP_UDP; else if (!pl_strcmp(&transp, "WS")) via->tp = SIP_TRANSP_WS; else if (!pl_strcmp(&transp, "WSS")) via->tp = SIP_TRANSP_WSS; else via->tp = SIP_TRANSP_NONE; err = decode_hostport(&via->sentby, &host, &port); if (err) return err; sa_init(&via->addr, AF_INET); (void)sa_set(&via->addr, &host, 0); if (pl_isset(&port)) sa_set_port(&via->addr, pl_u32(&port)); via->val = *pl; return msg_param_decode(&via->params, "branch", &via->branch); } ================================================ FILE: src/sipevent/listen.c ================================================ /** * @file sipevent/listen.c SIP Event Listen * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipevent.h" struct subcmp { const struct sipevent_event *evt; const struct sip_msg *msg; }; static void destructor(void *arg) { struct sipevent_sock *sock = arg; mem_deref(sock->lsnr); hash_flush(sock->ht_not); hash_flush(sock->ht_sub); mem_deref(sock->ht_not); mem_deref(sock->ht_sub); } static bool event_cmp(const struct sipevent_event *evt, const char *event, const char *id, int32_t refer_cseq) { if (pl_strcmp(&evt->event, event)) return false; if (!pl_isset(&evt->id) && !id) return true; if (!pl_isset(&evt->id)) return false; if (!id) { if (refer_cseq >= 0 && (int32_t)pl_u32(&evt->id) == refer_cseq) return true; return false; } if (pl_strcmp(&evt->id, id)) return false; return true; } static bool not_cmp_handler(struct le *le, void *arg) { const struct subcmp *cmp = arg; struct sipnot *not = le->data; return sip_dialog_cmp(not->dlg, cmp->msg) && event_cmp(cmp->evt, not->event, not->id, -1); } static bool sub_cmp_handler(struct le *le, void *arg) { const struct subcmp *cmp = arg; struct sipsub *sub = le->data; return sip_dialog_cmp(sub->dlg, cmp->msg) && (!cmp->evt || event_cmp(cmp->evt, sub->event, sub->id, sub->refer_cseq)); } static bool sub_cmp_half_handler(struct le *le, void *arg) { const struct subcmp *cmp = arg; struct sipsub *sub = le->data; return sip_dialog_cmp_half(sub->dlg, cmp->msg) && !sip_dialog_established(sub->dlg) && (!cmp->evt || event_cmp(cmp->evt, sub->event, sub->id, sub->refer_cseq)); } static struct sipnot *sipnot_find(struct sipevent_sock *sock, const struct sip_msg *msg, const struct sipevent_event *evt) { struct subcmp cmp; cmp.msg = msg; cmp.evt = evt; return list_ledata(hash_lookup(sock->ht_not, hash_joaat_pl(&msg->callid), not_cmp_handler, &cmp)); } struct sipsub *sipsub_find(struct sipevent_sock *sock, const struct sip_msg *msg, const struct sipevent_event *evt, bool full) { struct subcmp cmp; cmp.msg = msg; cmp.evt = evt; return list_ledata(hash_lookup(sock->ht_sub, hash_joaat_pl(&msg->callid), full ? sub_cmp_handler : sub_cmp_half_handler, &cmp)); } static void notify_handler(struct sipevent_sock *sock, const struct sip_msg *msg) { struct sipevent_substate state; struct sipevent_event event; struct sip *sip = sock->sip; const struct sip_hdr *hdr; struct sipsub *sub; uint32_t nrefs; char m[256]; int err; hdr = sip_msg_hdr(msg, SIP_HDR_EVENT); if (!hdr || sipevent_event_decode(&event, &hdr->val)) { (void)sip_reply(sip, msg, 489, "Bad Event"); return; } hdr = sip_msg_hdr(msg, SIP_HDR_SUBSCRIPTION_STATE); if (!hdr || sipevent_substate_decode(&state, &hdr->val)) { (void)sip_reply(sip, msg, 400,"Bad Subscription-State Header"); return; } sub = sipsub_find(sock, msg, &event, true); if (!sub) { sub = sipsub_find(sock, msg, &event, false); if (!sub) { (void)sip_reply(sip, msg, 481, "Subscription Does Not Exist"); return; } if (sub->forkh) { struct sipsub *fsub; err = sub->forkh(&fsub, sub, msg, sub->arg); if (err) { (void)sip_reply(sip, msg, 500, str_error(err, m, sizeof(m))); return; } sub = fsub; } else { err = sip_dialog_create(sub->dlg, msg); if (err) { (void)sip_reply(sip, msg, 500, str_error(err, m, sizeof(m))); return; } } } else { if (!sip_dialog_rseq_valid(sub->dlg, msg)) { (void)sip_reply(sip, msg, 500, "Bad Sequence"); return; } (void)sip_dialog_update(sub->dlg, msg); } if (sub->refer_cseq >= 0 && !sub->id && pl_isset(&event.id)) { err = pl_strdup(&sub->id, &event.id); if (err) { (void)sip_treply(NULL, sip, msg, 500, str_error(err, m, sizeof(m))); return; } } switch (state.state) { case SIPEVENT_ACTIVE: case SIPEVENT_PENDING: if (!sub->termconf) sub->subscribed = true; if (!sub->terminated && !sub->termwait && pl_isset(&state.expires)) sipsub_reschedule(sub, pl_u32(&state.expires) * 900); break; case SIPEVENT_TERMINATED: sub->subscribed = false; sub->termconf = true; break; } mem_ref(sub); sub->notifyh(sip, msg, sub->arg); nrefs = mem_nrefs(sub); mem_deref(sub); /* check if subscription was deref'd from notify handler */ if (nrefs == 1) return; if (state.state == SIPEVENT_TERMINATED) { if (!sub->terminated) { sub->termwait = false; sipsub_terminate(sub, 0, msg, &state); } else if (sub->termwait) { sub->termwait = false; tmr_cancel(&sub->tmr); mem_deref(sub); } } } static void subscribe_handler(struct sipevent_sock *sock, const struct sip_msg *msg) { struct sipevent_event event; struct sip *sip = sock->sip; const struct sip_hdr *hdr; struct sipnot *not; uint32_t expires; hdr = sip_msg_hdr(msg, SIP_HDR_EVENT); if (!hdr || sipevent_event_decode(&event, &hdr->val)) { (void)sip_reply(sip, msg, 400, "Bad Event Header"); return; } not = sipnot_find(sock, msg, &event); if (!not || not->terminated) { (void)sip_reply(sip, msg, 481, "Subscription Does Not Exist"); return; } if (pl_isset(&msg->expires)) expires = pl_u32(&msg->expires); else expires = not->expires_dfl; if (expires > 0 && expires < not->expires_min) { (void)sip_replyf(sip, msg, 423, "Interval Too Brief", "Min-Expires: %u\r\n" "Content-Length: 0\r\n" "\r\n", not->expires_min); return; } if (!sip_dialog_rseq_valid(not->dlg, msg)) { (void)sip_reply(sip, msg, 500, "Bad Sequence"); return; } (void)sip_dialog_update(not->dlg, msg); sipnot_refresh(not, expires); (void)sipnot_reply(not, msg, 200, "OK"); (void)sipnot_notify(not); } static bool request_handler(const struct sip_msg *msg, void *arg) { struct sipevent_sock *sock = arg; if (!pl_strcmp(&msg->met, "SUBSCRIBE")) { if (pl_isset(&msg->to.tag)) { subscribe_handler(sock, msg); return true; } return sock->subh ? sock->subh(msg, sock->arg) : false; } else if (!pl_strcmp(&msg->met, "NOTIFY")) { notify_handler(sock, msg); return true; } else { return false; } } int sipevent_listen(struct sipevent_sock **sockp, struct sip *sip, uint32_t htsize_not, uint32_t htsize_sub, sip_msg_h *subh, void *arg) { struct sipevent_sock *sock; int err; if (!sockp || !sip || !htsize_not || !htsize_sub) return EINVAL; sock = mem_zalloc(sizeof(*sock), destructor); if (!sock) return ENOMEM; err = sip_listen(&sock->lsnr, sip, true, request_handler, sock); if (err) goto out; err = hash_alloc(&sock->ht_not, htsize_not); if (err) goto out; err = hash_alloc(&sock->ht_sub, htsize_sub); if (err) goto out; sock->sip = sip; sock->subh = subh; sock->arg = arg; out: if (err) mem_deref(sock); else *sockp = sock; return err; } ================================================ FILE: src/sipevent/msg.c ================================================ /** * @file sipevent/msg.c SIP event messages * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include int sipevent_event_decode(struct sipevent_event *se, const struct pl *pl) { struct pl param; int err; if (!se || !pl) return EINVAL; err = re_regex(pl->p, pl->l, "[^; \t\r\n]+[ \t\r\n]*[^]*", &se->event, NULL, &se->params); if (err) return EBADMSG; if (!msg_param_decode(&se->params, "id", ¶m)) se->id = param; else se->id = pl_null; return 0; } int sipevent_substate_decode(struct sipevent_substate *ss, const struct pl *pl) { struct pl state, param; int err; if (!ss || !pl) return EINVAL; err = re_regex(pl->p, pl->l, "[a-z]+[ \t\r\n]*[^]*", &state, NULL, &ss->params); if (err) return EBADMSG; if (!pl_strcasecmp(&state, "active")) ss->state = SIPEVENT_ACTIVE; else if (!pl_strcasecmp(&state, "pending")) ss->state = SIPEVENT_PENDING; else if (!pl_strcasecmp(&state, "terminated")) ss->state = SIPEVENT_TERMINATED; else ss->state = -1; if (!msg_param_decode(&ss->params, "reason", ¶m)) { if (!pl_strcasecmp(¶m, "deactivated")) ss->reason = SIPEVENT_DEACTIVATED; else if (!pl_strcasecmp(¶m, "probation")) ss->reason = SIPEVENT_PROBATION; else if (!pl_strcasecmp(¶m, "rejected")) ss->reason = SIPEVENT_REJECTED; else if (!pl_strcasecmp(¶m, "timeout")) ss->reason = SIPEVENT_TIMEOUT; else if (!pl_strcasecmp(¶m, "giveup")) ss->reason = SIPEVENT_GIVEUP; else if (!pl_strcasecmp(¶m, "noresource")) ss->reason = SIPEVENT_NORESOURCE; else ss->reason = -1; } else { ss->reason = -1; } if (!msg_param_decode(&ss->params, "expires", ¶m)) ss->expires = param; else ss->expires = pl_null; if (!msg_param_decode(&ss->params, "retry-after", ¶m)) ss->retry_after = param; else ss->retry_after = pl_null; return 0; } const char *sipevent_substate_name(enum sipevent_subst state) { switch (state) { case SIPEVENT_ACTIVE: return "active"; case SIPEVENT_PENDING: return "pending"; case SIPEVENT_TERMINATED: return "terminated"; default: return "unknown"; } } const char *sipevent_reason_name(enum sipevent_reason reason) { switch (reason) { case SIPEVENT_DEACTIVATED: return "deactivated"; case SIPEVENT_PROBATION: return "probation"; case SIPEVENT_REJECTED: return "rejected"; case SIPEVENT_TIMEOUT: return "timeout"; case SIPEVENT_GIVEUP: return "giveup"; case SIPEVENT_NORESOURCE: return "noresource"; default: return "unknown"; } } ================================================ FILE: src/sipevent/notify.c ================================================ /** * @file notify.c SIP Event Notify * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sipevent.h" static int notify_request(struct sipnot *not, bool reset_ls); static void internal_close_handler(int err, const struct sip_msg *msg, void *arg) { (void)err; (void)msg; (void)arg; } static bool terminate(struct sipnot *not, enum sipevent_reason reason) { not->terminated = true; not->reason = reason; not->closeh = internal_close_handler; if (not->req) { mem_ref(not); return true; } if (not->subscribed && !notify_request(not, true)) { mem_ref(not); return true; } return false; } static void destructor(void *arg) { struct sipnot *not = arg; tmr_cancel(¬->tmr); if (!not->terminated) { if (terminate(not, SIPEVENT_DEACTIVATED)) return; } hash_unlink(¬->he); mem_deref(not->req); mem_deref(not->dlg); mem_deref(not->auth); mem_deref(not->mb); mem_deref(not->event); mem_deref(not->id); mem_deref(not->cuser); mem_deref(not->hdrs); mem_deref(not->ctype); mem_deref(not->sock); mem_deref(not->sip); } static void sipnot_terminate(struct sipnot *not, int err, const struct sip_msg *msg, enum sipevent_reason reason) { sipnot_close_h *closeh; void *arg; closeh = not->closeh; arg = not->arg; tmr_cancel(¬->tmr); (void)terminate(not, reason); closeh(err, msg, arg); } static void tmr_handler(void *arg) { struct sipnot *not = arg; if (not->terminated) return; sipnot_terminate(not, ETIMEDOUT, NULL, SIPEVENT_TIMEOUT); } void sipnot_refresh(struct sipnot *not, uint32_t expires) { not->expires = min(expires, not->expires_max); tmr_start(¬->tmr, not->expires * 1000, tmr_handler, not); } static void response_handler(int err, const struct sip_msg *msg, void *arg) { struct sipnot *not = arg; if (err) { if (err == ETIMEDOUT) not->subscribed = false; goto out; } if (sip_request_loops(¬->ls, msg->scode)) { not->subscribed = false; goto out; } if (msg->scode < 200) { return; } else if (msg->scode < 300) { (void)sip_dialog_update(not->dlg, msg); } else { switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(not->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = notify_request(not, false); if (err) break; return; } not->subscribed = false; } out: if (not->termsent) { mem_deref(not); } else if (not->terminated) { if (!not->subscribed || notify_request(not, true)) mem_deref(not); } else if (!not->subscribed) { sipnot_terminate(not, err, msg, -1); } else if (not->notify_pending) { (void)notify_request(not, true); } } static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sip_contact contact; struct sipnot *not = arg; (void)dst; (void)contp; sip_contact_set(&contact, not->cuser, src, tp); return mbuf_printf(mb, "%H", sip_contact_print, &contact); } static int print_event(struct re_printf *pf, const struct sipnot *not) { if (not->id) return re_hprintf(pf, "%s;id=%s", not->event, not->id); else return re_hprintf(pf, "%s", not->event); } static int print_substate(struct re_printf *pf, const struct sipnot *not) { int err; if (not->terminated) { err = re_hprintf(pf, "terminated;reason=%s", sipevent_reason_name(not->reason)); if (not->retry_after) err |= re_hprintf(pf, ";retry-after=%u", not->retry_after); } else { uint32_t expires; expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000); err = re_hprintf(pf, "%s;expires=%u", sipevent_substate_name(not->substate), expires); } return err; } static int print_content(struct re_printf *pf, const struct sipnot *not) { if (!not->mb) return re_hprintf(pf, "Content-Length: 0\r\n" "\r\n"); else return re_hprintf(pf, "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "\r\n" "%b", not->ctype, mbuf_get_left(not->mb), mbuf_buf(not->mb), mbuf_get_left(not->mb)); } static int notify_request(struct sipnot *not, bool reset_ls) { if (reset_ls) sip_loopstate_reset(¬->ls); if (not->terminated) not->termsent = true; not->notify_pending = false; return sip_drequestf(¬->req, not->sip, true, "NOTIFY", not->dlg, 0, not->auth, send_handler, response_handler, not, "Event: %H\r\n" "Subscription-State: %H\r\n" "%s" "%H", print_event, not, print_substate, not, not->hdrs, print_content, not); } int sipnot_notify(struct sipnot *not) { if (not->expires == 0) { return 0; } if (not->req) { not->notify_pending = true; return 0; } return notify_request(not, true); } int sipnot_reply(struct sipnot *not, const struct sip_msg *msg, uint16_t scode, const char *reason) { struct sip_contact contact; uint32_t expires; expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000); sip_contact_set(&contact, not->cuser, &msg->dst, msg->tp); return sip_treplyf(NULL, NULL, not->sip, msg, true, scode, reason, "%H" "Expires: %u\r\n" "Content-Length: 0\r\n" "\r\n", sip_contact_print, &contact, expires); } int sipevent_accept(struct sipnot **notp, struct sipevent_sock *sock, const struct sip_msg *msg, struct sip_dialog *dlg, const struct sipevent_event *event, uint16_t scode, const char *reason, uint32_t expires_min, uint32_t expires_dfl, uint32_t expires_max, const char *cuser, const char *ctype, sip_auth_h *authh, void *aarg, bool aref, sipnot_close_h *closeh, void *arg, const char *fmt, ...) { struct sipnot *not; uint32_t expires; int err; if (!notp || !sock || !msg || !scode || !reason || !expires_dfl || !expires_max || !cuser || !ctype || expires_dfl < expires_min) return EINVAL; not = mem_zalloc(sizeof(*not), destructor); if (!not) return ENOMEM; if (!pl_strcmp(&msg->met, "REFER")) { err = str_dup(¬->event, "refer"); if (err) goto out; err = re_sdprintf(¬->id, "%u", msg->cseq.num); if (err) goto out; } else { if (!event) { err = EINVAL; goto out; } err = pl_strdup(¬->event, &event->event); if (err) goto out; if (pl_isset(&event->id)) { err = pl_strdup(¬->id, &event->id); if (err) goto out; } } if (dlg) { not->dlg = mem_ref(dlg); } else { err = sip_dialog_accept(¬->dlg, msg); if (err) goto out; } hash_append(sock->ht_not, hash_joaat_str(sip_dialog_callid(not->dlg)), ¬->he, not); err = sip_auth_alloc(¬->auth, authh, aarg, aref); if (err) goto out; err = str_dup(¬->cuser, cuser); if (err) goto out; err = str_dup(¬->ctype, ctype); if (err) goto out; if (fmt) { va_list ap; va_start(ap, fmt); err = re_vsdprintf(¬->hdrs, fmt, ap); va_end(ap); if (err) goto out; } not->expires_min = expires_min; not->expires_dfl = expires_dfl; not->expires_max = expires_max; not->substate = SIPEVENT_PENDING; not->sock = mem_ref(sock); not->sip = mem_ref(sock->sip); not->closeh = closeh ? closeh : internal_close_handler; not->arg = arg; if (pl_isset(&msg->expires)) expires = pl_u32(&msg->expires); else expires = not->expires_dfl; sipnot_refresh(not, expires); err = sipnot_reply(not, msg, scode, reason); if (err) goto out; not->subscribed = true; out: if (err) mem_deref(not); else *notp = not; return err; } int sipevent_notify(struct sipnot *not, struct mbuf *mb, enum sipevent_subst state, enum sipevent_reason reason, uint32_t retry_after) { if (!not || not->terminated) return EINVAL; if (mb || state != SIPEVENT_TERMINATED) { mem_deref(not->mb); not->mb = mem_ref(mb); } switch (state) { case SIPEVENT_ACTIVE: case SIPEVENT_PENDING: not->substate = state; return sipnot_notify(not); case SIPEVENT_TERMINATED: tmr_cancel(¬->tmr); not->retry_after = retry_after; (void)terminate(not, reason); return 0; default: return EINVAL; } } int sipevent_notifyf(struct sipnot *not, struct mbuf **mbp, enum sipevent_subst state, enum sipevent_reason reason, uint32_t retry_after, const char *fmt, ...) { struct mbuf *mb; va_list ap; int err; if (!not || not->terminated || !fmt) return EINVAL; if (mbp && *mbp) return sipevent_notify(not, *mbp, state, reason, retry_after); mb = mbuf_alloc(1024); if (!mb) return ENOMEM; va_start(ap, fmt); err = mbuf_vprintf(mb, fmt, ap); va_end(ap); if (err) goto out; mb->pos = 0; err = sipevent_notify(not, mb, state, reason, retry_after); if (err) goto out; out: if (err || !mbp) mem_deref(mb); else *mbp = mb; return err; } ================================================ FILE: src/sipevent/sipevent.h ================================================ /** * @file sipevent.h SIP Event Private Interface * * Copyright (C) 2010 Creytiv.com */ /* Listener Socket */ struct sipevent_sock { struct sip_lsnr *lsnr; struct hash *ht_not; struct hash *ht_sub; struct sip *sip; sip_msg_h *subh; void *arg; }; /* Notifier */ struct sipnot { struct le he; struct sip_loopstate ls; struct tmr tmr; struct sipevent_sock *sock; struct sip_request *req; struct sip_dialog *dlg; struct sip_auth *auth; struct sip *sip; struct mbuf *mb; char *event; char *id; char *cuser; char *hdrs; char *ctype; sipnot_close_h *closeh; void *arg; uint32_t expires; uint32_t expires_min; uint32_t expires_dfl; uint32_t expires_max; uint32_t retry_after; enum sipevent_subst substate; enum sipevent_reason reason; bool notify_pending; bool subscribed; bool terminated; bool termsent; }; void sipnot_refresh(struct sipnot *not, uint32_t expires); int sipnot_notify(struct sipnot *not); int sipnot_reply(struct sipnot *not, const struct sip_msg *msg, uint16_t scode, const char *reason); /* Subscriber */ struct sipsub { struct le he; struct sip_loopstate ls; struct tmr tmr; struct sipevent_sock *sock; struct sip_request *req; struct sip_dialog *dlg; struct sip_auth *auth; struct sip *sip; char *event; char *id; char *cuser; char *hdrs; char *refer_hdrs; sipsub_fork_h *forkh; sipsub_notify_h *notifyh; sipsub_close_h *closeh; void *arg; int32_t refer_cseq; uint32_t expires; uint32_t failc; bool subscribed; bool terminated; bool termconf; bool termwait; bool refer; }; struct sipsub *sipsub_find(struct sipevent_sock *sock, const struct sip_msg *msg, const struct sipevent_event *evt, bool full); void sipsub_reschedule(struct sipsub *sub, uint64_t wait); void sipsub_terminate(struct sipsub *sub, int err, const struct sip_msg *msg, const struct sipevent_substate *substate); ================================================ FILE: src/sipevent/subscribe.c ================================================ /** * @file subscribe.c SIP Event Subscribe * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sipevent.h" enum { DEFAULT_EXPIRES = 3600, RESUB_FAIL_WAIT = 60000, RESUB_FAILC_MAX = 7, NOTIFY_TIMEOUT = 10000, }; static int request(struct sipsub *sub, bool reset_ls); static void internal_notify_handler(struct sip *sip, const struct sip_msg *msg, void *arg) { (void)arg; (void)sip_treply(NULL, sip, msg, 200, "OK"); } static void internal_close_handler(int err, const struct sip_msg *msg, const struct sipevent_substate *substate, void *arg) { (void)err; (void)msg; (void)substate; (void)arg; } static bool terminate(struct sipsub *sub) { sub->terminated = true; sub->forkh = NULL; sub->notifyh = internal_notify_handler; sub->closeh = internal_close_handler; if (sub->termwait) { mem_ref(sub); return true; } tmr_cancel(&sub->tmr); if (sub->req) { mem_ref(sub); return true; } if (sub->expires && sub->subscribed && !request(sub, true)) { mem_ref(sub); return true; } return false; } static void destructor(void *arg) { struct sipsub *sub = arg; if (!sub->terminated) { if (terminate(sub)) return; } tmr_cancel(&sub->tmr); hash_unlink(&sub->he); mem_deref(sub->req); mem_deref(sub->dlg); mem_deref(sub->auth); mem_deref(sub->event); mem_deref(sub->id); mem_deref(sub->cuser); mem_deref(sub->hdrs); mem_deref(sub->refer_hdrs); mem_deref(sub->sock); mem_deref(sub->sip); } static void notify_timeout_handler(void *arg) { struct sipsub *sub = arg; sub->termwait = false; if (sub->terminated) mem_deref(sub); else sipsub_terminate(sub, ETIMEDOUT, NULL, NULL); } static void tmr_handler(void *arg) { struct sipsub *sub = arg; int err; if (sub->req || sub->terminated) return; err = request(sub, true); if (err) { if (++sub->failc < RESUB_FAILC_MAX) { sipsub_reschedule(sub, RESUB_FAIL_WAIT); } else { sipsub_terminate(sub, err, NULL, NULL); } } } void sipsub_reschedule(struct sipsub *sub, uint64_t wait) { tmr_start(&sub->tmr, wait, tmr_handler, sub); } void sipsub_terminate(struct sipsub *sub, int err, const struct sip_msg *msg, const struct sipevent_substate *substate) { sipsub_close_h *closeh; void *arg; closeh = sub->closeh; arg = sub->arg; (void)terminate(sub); closeh(err, msg, substate, arg); } static void response_handler(int err, const struct sip_msg *msg, void *arg) { const struct sip_hdr *minexp; struct sipsub *sub = arg; if (err || sip_request_loops(&sub->ls, msg->scode)) goto out; if (msg->scode < 200) { return; } else if (msg->scode < 300) { uint32_t wait; if (sub->forkh) { struct sipsub *fsub; fsub = sipsub_find(sub->sock, msg, NULL, true); if (!fsub) { err = sub->forkh(&fsub, sub, msg, sub->arg); if (err) return; } else { (void)sip_dialog_update(fsub->dlg, msg); } sub = fsub; } else if (!sip_dialog_established(sub->dlg)) { err = sip_dialog_create(sub->dlg, msg); if (err) { sub->subscribed = false; goto out; } } else { /* Ignore 2xx responses for other dialogs * if forking is disabled */ if (!sip_dialog_cmp(sub->dlg, msg)) return; (void)sip_dialog_update(sub->dlg, msg); } if (!sub->termconf) sub->subscribed = true; sub->failc = 0; if (!sub->expires && !sub->termconf) { tmr_start(&sub->tmr, NOTIFY_TIMEOUT, notify_timeout_handler, sub); sub->termwait = true; return; } if (sub->terminated) goto out; if (sub->refer) { sub->refer = false; return; } if (pl_isset(&msg->expires)) wait = pl_u32(&msg->expires); else wait = sub->expires; sipsub_reschedule(sub, wait * 900); return; } else { if (sub->terminated && !sub->subscribed) goto out; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(sub->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = request(sub, false); if (err) break; return; case 403: sip_auth_reset(sub->auth); break; case 423: minexp = sip_msg_hdr(msg, SIP_HDR_MIN_EXPIRES); if (!minexp || !pl_u32(&minexp->val) || !sub->expires) break; sub->expires = pl_u32(&minexp->val); err = request(sub, false); if (err) break; return; case 481: sub->subscribed = false; break; } } out: sub->refer = false; if (sub->terminated) { if (!sub->expires || !sub->subscribed || request(sub, true)) mem_deref(sub); } else { if (sub->subscribed && ++sub->failc < RESUB_FAILC_MAX) sipsub_reschedule(sub, RESUB_FAIL_WAIT); else sipsub_terminate(sub, err, msg, NULL); } } static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sip_contact contact; struct sipsub *sub = arg; (void)dst; (void)contp; sip_contact_set(&contact, sub->cuser, src, tp); return mbuf_printf(mb, "%H", sip_contact_print, &contact); } static int print_event(struct re_printf *pf, const struct sipsub *sub) { if (sub->id) return re_hprintf(pf, "%s;id=%s", sub->event, sub->id); else return re_hprintf(pf, "%s", sub->event); } static int request(struct sipsub *sub, bool reset_ls) { if (reset_ls) sip_loopstate_reset(&sub->ls); if (sub->refer) { sub->refer_cseq = sip_dialog_lseq(sub->dlg); return sip_drequestf(&sub->req, sub->sip, true, "REFER", sub->dlg, 0, sub->auth, send_handler, response_handler, sub, "%s" "Content-Length: 0\r\n" "\r\n", sub->refer_hdrs); } else { if (sub->terminated) sub->expires = 0; return sip_drequestf(&sub->req, sub->sip, true, "SUBSCRIBE", sub->dlg, 0, sub->auth, send_handler, response_handler, sub, "Event: %H\r\n" "Expires: %u\r\n" "%s" "Content-Length: 0\r\n" "\r\n", print_event, sub, sub->expires, sub->hdrs); } } static int sipsub_alloc(struct sipsub **subp, struct sipevent_sock *sock, bool refer, struct sip_dialog *dlg, const char *uri, const char *from_name, const char *from_uri, const char *event, const char *id, uint32_t expires, const char *cuser, const char *routev[], uint32_t routec, sip_auth_h *authh, void *aarg, bool aref, sipsub_fork_h *forkh, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, va_list ap) { struct sipsub *sub; int err; if (!subp || !sock || !event || !cuser) return EINVAL; if (!dlg && (!uri || !from_uri)) return EINVAL; sub = mem_zalloc(sizeof(*sub), destructor); if (!sub) return ENOMEM; if (dlg) { sub->dlg = mem_ref(dlg); } else { err = sip_dialog_alloc(&sub->dlg, uri, uri, from_name, from_uri, routev, routec); if (err) goto out; } hash_append(sock->ht_sub, hash_joaat_str(sip_dialog_callid(sub->dlg)), &sub->he, sub); err = sip_auth_alloc(&sub->auth, authh, aarg, aref); if (err) goto out; err = str_dup(&sub->event, event); if (err) goto out; if (id) { err = str_dup(&sub->id, id); if (err) goto out; } err = str_dup(&sub->cuser, cuser); if (err) goto out; if (fmt) { err = re_vsdprintf(refer ? &sub->refer_hdrs : &sub->hdrs, fmt, ap); if (err) goto out; } sub->refer_cseq = -1; sub->refer = refer; sub->sock = mem_ref(sock); sub->sip = mem_ref(sock->sip); sub->expires = expires; sub->forkh = forkh; sub->notifyh = notifyh ? notifyh : internal_notify_handler; sub->closeh = closeh ? closeh : internal_close_handler; sub->arg = arg; err = request(sub, true); if (err) goto out; out: if (err) mem_deref(sub); else *subp = sub; return err; } /** * Allocate a SIP subscriber client * * @param subp Pointer to allocated SIP subscriber client * @param sock SIP Event socket * @param uri SIP Request URI * @param from_name SIP From-header Name (optional) * @param from_uri SIP From-header URI * @param event SIP Event to subscribe to * @param id SIP Event ID (optional) * @param expires Subscription expires value * @param cuser Contact username or URI * @param routev Optional route vector * @param routec Number of routes * @param authh Authentication handler * @param aarg Authentication handler argument * @param aref True to ref argument * @param forkh Fork handler * @param notifyh Notify handler * @param closeh Close handler * @param arg Response handler argument * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipevent_subscribe(struct sipsub **subp, struct sipevent_sock *sock, const char *uri, const char *from_name, const char *from_uri, const char *event, const char *id, uint32_t expires, const char *cuser, const char *routev[], uint32_t routec, sip_auth_h *authh, void *aarg, bool aref, sipsub_fork_h *forkh, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = sipsub_alloc(subp, sock, false, NULL, uri, from_name, from_uri, event, id, expires, cuser, routev, routec, authh, aarg, aref, forkh, notifyh, closeh, arg, fmt, ap); va_end(ap); return err; } /** * Allocate a SIP subscriber client using an existing dialog * * @param subp Pointer to allocated SIP subscriber client * @param sock SIP Event socket * @param dlg Established SIP Dialog * @param event SIP Event to subscribe to * @param id SIP Event ID (optional) * @param expires Subscription expires value * @param cuser Contact username or URI * @param authh Authentication handler * @param aarg Authentication handler argument * @param aref True to ref argument * @param notifyh Notify handler * @param closeh Close handler * @param arg Response handler argument * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipevent_dsubscribe(struct sipsub **subp, struct sipevent_sock *sock, struct sip_dialog *dlg, const char *event, const char *id, uint32_t expires, const char *cuser, sip_auth_h *authh, void *aarg, bool aref, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = sipsub_alloc(subp, sock, false, dlg, NULL, NULL, NULL, event, id, expires, cuser, NULL, 0, authh, aarg, aref, NULL, notifyh, closeh, arg, fmt, ap); va_end(ap); return err; } /** * Allocate a SIP refer client * * @param subp Pointer to allocated SIP subscriber client * @param sock SIP Event socket * @param uri SIP Request URI * @param from_name SIP From-header Name (optional) * @param from_uri SIP From-header URI * @param cuser Contact username or URI * @param routev Optional route vector * @param routec Number of routes * @param authh Authentication handler * @param aarg Authentication handler argument * @param aref True to ref argument * @param forkh Fork handler * @param notifyh Notify handler * @param closeh Close handler * @param arg Response handler argument * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipevent_refer(struct sipsub **subp, struct sipevent_sock *sock, const char *uri, const char *from_name, const char *from_uri, const char *cuser, const char *routev[], uint32_t routec, sip_auth_h *authh, void *aarg, bool aref, sipsub_fork_h *forkh, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = sipsub_alloc(subp, sock, true, NULL, uri, from_name, from_uri, "refer", NULL, DEFAULT_EXPIRES, cuser, routev, routec, authh, aarg, aref, forkh, notifyh, closeh, arg, fmt, ap); va_end(ap); return err; } /** * Allocate a SIP refer client using an existing dialog * * @param subp Pointer to allocated SIP subscriber client * @param sock SIP Event socket * @param dlg Established SIP Dialog * @param cuser Contact username or URI * @param authh Authentication handler * @param aarg Authentication handler argument * @param aref True to ref argument * @param notifyh Notify handler * @param closeh Close handler * @param arg Response handler argument * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipevent_drefer(struct sipsub **subp, struct sipevent_sock *sock, struct sip_dialog *dlg, const char *cuser, sip_auth_h *authh, void *aarg, bool aref, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = sipsub_alloc(subp, sock, true, dlg, NULL, NULL, NULL, "refer", NULL, DEFAULT_EXPIRES, cuser, NULL, 0, authh, aarg, aref, NULL, notifyh, closeh, arg, fmt, ap); va_end(ap); return err; } int sipevent_fork(struct sipsub **subp, struct sipsub *osub, const struct sip_msg *msg, sip_auth_h *authh, void *aarg, bool aref, sipsub_notify_h *notifyh, sipsub_close_h *closeh, void *arg) { struct sipsub *sub; int err; if (!subp || !osub || !msg) return EINVAL; sub = mem_zalloc(sizeof(*sub), destructor); if (!sub) return ENOMEM; err = sip_dialog_fork(&sub->dlg, osub->dlg, msg); if (err) goto out; hash_append(osub->sock->ht_sub, hash_joaat_str(sip_dialog_callid(sub->dlg)), &sub->he, sub); err = sip_auth_alloc(&sub->auth, authh, aarg, aref); if (err) goto out; sub->event = mem_ref(osub->event); sub->id = mem_ref(osub->id); sub->cuser = mem_ref(osub->cuser); sub->hdrs = mem_ref(osub->hdrs); sub->refer = osub->refer; sub->sock = mem_ref(osub->sock); sub->sip = mem_ref(osub->sip); sub->expires = osub->expires; sub->forkh = NULL; sub->notifyh = notifyh ? notifyh : internal_notify_handler; sub->closeh = closeh ? closeh : internal_close_handler; sub->arg = arg; if (!sub->expires) { tmr_start(&sub->tmr, NOTIFY_TIMEOUT, notify_timeout_handler, sub); sub->termwait = true; } out: if (err) mem_deref(sub); else *subp = sub; return err; } ================================================ FILE: src/sipreg/reg.c ================================================ /** * @file reg.c SIP Registration * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { DEFAULT_EXPIRES = 3600, }; /** Defines a SIP Registration client */ struct sipreg { struct sip_loopstate ls; struct sa laddr; struct tmr tmr; struct sip *sip; struct sip_keepalive *ka; struct sip_request *req; struct sip_dialog *dlg; struct sip_auth *auth; struct mbuf *hdrs; char *cuser; char *cparams; sip_resp_h *resph; void *arg; uint32_t expires; uint32_t pexpires; uint32_t failc; uint32_t rwait; uint32_t wait; uint32_t fbregint; enum sip_transp tp; bool registered; bool terminated; char *params; int regid; uint16_t srcport; }; static int request(struct sipreg *reg, bool reset_ls); static void dummy_handler(int err, const struct sip_msg *msg, void *arg) { (void)err; (void)msg; (void)arg; } static void destructor(void *arg) { struct sipreg *reg = arg; tmr_cancel(®->tmr); if (!reg->terminated) { reg->resph = dummy_handler; reg->terminated = true; if (reg->req) { mem_ref(reg); return; } if (reg->registered && !request(reg, true)) { mem_ref(reg); return; } } mem_deref(reg->ka); mem_deref(reg->dlg); mem_deref(reg->auth); mem_deref(reg->cuser); mem_deref(reg->sip); mem_deref(reg->hdrs); mem_deref(reg->params); mem_deref(reg->cparams); } static uint32_t failwait(uint32_t failc) { return min(1800, (30 * (1<tmr, failwait(++reg->failc), tmr_handler, reg); reg->resph(err, NULL, reg->arg); } } static void keepalive_handler(int err, void *arg) { struct sipreg *reg = arg; /* failure will be handled in response handler */ if (reg->req || reg->terminated) return; tmr_start(®->tmr, failwait(++reg->failc), tmr_handler, reg); reg->resph(err, NULL, reg->arg); } static void start_outbound(struct sipreg *reg, const struct sip_msg *msg) { const struct sip_hdr *flowtimer; if (!sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "outbound")) return; flowtimer = sip_msg_hdr(msg, SIP_HDR_FLOW_TIMER); (void)sip_keepalive_start(®->ka, reg->sip, msg, flowtimer ? pl_u32(&flowtimer->val) : 0, keepalive_handler, reg); } static bool contact_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { struct sipreg *reg = arg; struct sip_addr c; struct pl transp = PL("transport"); struct pl pval; struct sa host; enum sip_transp tp; int err; if (sip_addr_decode(&c, &hdr->val)) return false; if (pl_strcmp(&c.uri.user, reg->cuser)) return false; err = sa_set(&host, &c.uri.host, c.uri.port); if (err) return false; if (!sa_cmp(&host, ®->laddr, SA_ADDR)) return false; err = uri_param_get(&c.auri, &transp, &pval); if (err) pl_set_str(&pval, "udp"); tp = sip_transp_decode(&pval); if (tp != reg->tp) return false; if (!msg_param_decode(&c.params, "expires", &pval)) { reg->wait = pl_u32(&pval); } else if (pl_isset(&msg->expires)) reg->wait = pl_u32(&msg->expires); else reg->wait = DEFAULT_EXPIRES; return true; } static void response_handler(int err, const struct sip_msg *msg, void *arg) { const struct sip_hdr *minexp; struct sipreg *reg = arg; uint16_t last_scode = reg->ls.last_scode; reg->wait = failwait(reg->failc + 1); if (err || !msg || sip_request_loops(®->ls, msg->scode)) { reg->failc++; goto out; } if (msg->scode < 200) { return; } else if (msg->scode < 300) { reg->wait = reg->expires; sip_msg_hdr_apply(msg, true, SIP_HDR_CONTACT, contact_handler, reg); reg->registered = reg->wait > 0; reg->pexpires = reg->wait; reg->wait *= reg->rwait * (1000 / 100); reg->failc = 0; if (reg->regid > 0 && !reg->terminated && !reg->ka) start_outbound(reg, msg); goto out; } if (reg->terminated && !reg->registered) goto out; switch (msg->scode) { case 401: case 407: if (reg->ls.failc > 1 && last_scode == msg->scode) { reg->failc++; goto out; } sip_auth_reset(reg->auth); err = sip_auth_authenticate(reg->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = request(reg, false); if (err) break; return; case 403: sip_auth_reset(reg->auth); break; case 423: minexp = sip_msg_hdr(msg, SIP_HDR_MIN_EXPIRES); if (!minexp || !pl_u32(&minexp->val) || !reg->expires) break; reg->expires = pl_u32(&minexp->val); err = request(reg, false); if (err) break; return; } ++reg->failc; out: if (!reg->expires) { if (msg && msg->scode >= 400 && msg->scode < 500) reg->fbregint = 0; if (reg->terminated) { mem_deref(reg); return; } if (reg->fbregint) tmr_start(®->tmr, reg->fbregint * 1000, tmr_handler, reg); else tmr_cancel(®->tmr); reg->resph(err, msg, reg->arg); } else if (reg->terminated) { if (!reg->registered || request(reg, true)) mem_deref(reg); } else { tmr_start(®->tmr, reg->wait, tmr_handler, reg); reg->resph(err, msg, reg->arg); } } static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sipreg *reg = arg; int err; (void)contp; (void)dst; reg->tp = tp; if (reg->srcport && tp != SIP_TRANSP_UDP) sa_set_port(src, reg->srcport); reg->laddr = *src; err = mbuf_printf(mb, "Contact: ;expires=%u%s%s", reg->cuser, ®->laddr, sip_transp_param(reg->tp), reg->cparams ? ";" : "", reg->cparams ? reg->cparams : "", reg->expires, reg->params ? ";" : "", reg->params ? reg->params : ""); if (reg->regid > 0) err |= mbuf_printf(mb, ";reg-id=%d", reg->regid); err |= mbuf_printf(mb, "\r\n"); return err; } static int request(struct sipreg *reg, bool reset_ls) { if (reg->terminated) reg->expires = 0; if (reset_ls) { sip_loopstate_reset(®->ls); } return sip_drequestf(®->req, reg->sip, true, "REGISTER", reg->dlg, 0, reg->auth, send_handler, response_handler, reg, "%s" "%b" "Content-Length: 0\r\n" "\r\n", reg->regid > 0 ? "Supported: gruu, outbound, path\r\n" : "", reg->hdrs ? mbuf_buf(reg->hdrs) : NULL, reg->hdrs ? mbuf_get_left(reg->hdrs) : (size_t)0); } static int vsipreg_alloc(struct sipreg **regp, struct sip *sip, const char *reg_uri, const char *to_uri, const char *from_name, const char *from_uri, uint32_t expires, const char *cuser, const char *routev[], uint32_t routec, int regid, sip_auth_h *authh, void *aarg, bool aref, sip_resp_h *resph, void *arg, const char *params, const char *fmt, va_list ap) { struct sipreg *reg; int err; if (!regp || !sip || !reg_uri || !to_uri || !from_uri || !cuser) return EINVAL; reg = mem_zalloc(sizeof(*reg), destructor); if (!reg) return ENOMEM; err = sip_dialog_alloc(®->dlg, reg_uri, to_uri, from_name, from_uri, routev, routec); if (err) goto out; err = sip_auth_alloc(®->auth, authh, aarg, aref); if (err) goto out; err = str_dup(®->cuser, cuser); if (params) err |= str_dup(®->params, params); if (err) goto out; /* Custom SIP headers */ if (fmt) { reg->hdrs = mbuf_alloc(256); if (!reg->hdrs) { err = ENOMEM; goto out; } err = mbuf_vprintf(reg->hdrs, fmt, ap); reg->hdrs->pos = 0; if (err) goto out; } reg->sip = mem_ref(sip); reg->expires = expires; reg->rwait = 90; reg->resph = resph ? resph : dummy_handler; reg->arg = arg; reg->regid = regid; out: if (err) mem_deref(reg); else *regp = reg; return err; } int sipreg_send(struct sipreg *reg) { if (!reg) return EINVAL; return request(reg, true); } /** * Unregisters SIP Registration client * * @param reg SIP Registration client */ void sipreg_unregister(struct sipreg *reg) { if (!reg) return; reg->expires = 0; (void)sipreg_send(reg); } /** * Allocate a SIP Registration client * * @param regp Pointer to allocated SIP Registration client * @param sip SIP Stack instance * @param reg_uri SIP Request URI * @param to_uri SIP To-header URI * @param from_name SIP From-header display name (optional) * @param from_uri SIP From-header URI * @param expires Registration expiry time in [seconds] * @param cuser Contact username * @param routev Optional route vector * @param routec Number of routes * @param regid Register identification * @param authh Authentication handler * @param aarg Authentication handler argument * @param aref True to ref argument * @param resph Response handler * @param arg Response handler argument * @param params Optional Contact-header parameters * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipreg_alloc(struct sipreg **regp, struct sip *sip, const char *reg_uri, const char *to_uri, const char *from_name, const char *from_uri, uint32_t expires, const char *cuser, const char *routev[], uint32_t routec, int regid, sip_auth_h *authh, void *aarg, bool aref, sip_resp_h *resph, void *arg, const char *params, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = vsipreg_alloc(regp, sip, reg_uri, to_uri, from_name, from_uri, expires, cuser, routev, routec, regid, authh, aarg, aref, resph, arg, params, fmt, ap); va_end(ap); return err; } /** * Set the relative registration interval in percent from proxy expiry time. A * value from 5-95% is accepted. * * @param reg SIP Registration client * @param rwait The relative registration interval in [%]. * * @return 0 if success, otherwise errorcode */ int sipreg_set_rwait(struct sipreg *reg, uint32_t rwait) { if (!reg || rwait < 5 || rwait > 95) return EINVAL; reg->rwait = rwait; return 0; } /** * Get the local socket address for a SIP Registration client * * @param reg SIP Registration client * * @return Local socket address */ const struct sa *sipreg_laddr(const struct sipreg *reg) { return reg ? ®->laddr : NULL; } /** * Get the proxy expires value of a SIP registration client * * @param reg SIP registration client * * @return the proxy expires value */ uint32_t sipreg_proxy_expires(const struct sipreg *reg) { return reg ? reg->pexpires : 0; } bool sipreg_registered(const struct sipreg *reg) { return reg ? reg->registered : false; } bool sipreg_failed(const struct sipreg *reg) { return reg ? reg->failc > 0 : false; } void sipreg_incfailc(struct sipreg *reg) { if (!reg) return; reg->failc++; } int sipreg_set_fbregint(struct sipreg *reg, uint32_t fbregint) { if (!reg) return EINVAL; reg->fbregint = fbregint; return 0; } /** * Set TCP source port number for the SIP registration client * * @param reg SIP registration client * @param srcport TCP source port number */ void sipreg_set_srcport(struct sipreg *reg, uint16_t srcport) { if (!reg || !reg->dlg) return; reg->srcport = srcport; sip_dialog_set_srcport(reg->dlg, srcport); } /** * Set contact URI optional parameters * * @param reg SIP registration client * @param cparams Contact URI optional parameters * * @return 0 if success, otherwise errorcode */ int sipreg_set_contact_params(struct sipreg *reg, const char *cparams) { if (!reg) return EINVAL; return str_dup(®->cparams, cparams); } ================================================ FILE: src/sipsess/accept.c ================================================ /** * @file accept.c SIP Session Accept * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static void cancel_handler(void *arg) { struct sipsess *sess = arg; const struct sip_msg *cancel_msg = sip_strans_cancel_msg(sess->st); (void)sip_treply(&sess->st, sess->sip, sess->msg, 487, "Request Terminated"); sess->peerterm = true; if (sess->terminated) return; sipsess_terminate(sess, ECONNRESET, cancel_msg); } /** * Accept an incoming SIP Session connection * * @param sessp Pointer to allocated SIP Session * @param sock SIP Session socket * @param msg Incoming SIP message * @param scode Response status code * @param reason Response reason phrase * @param rel100 Sending 1xx reliably supported, required or disabled * @param cuser Contact username or URI * @param ctype Session content-type * @param desc Content description (e.g. SDP) * @param authh SIP Authentication handler * @param aarg Authentication handler argument * @param aref True to mem_ref() aarg * @param offerh Session offer handler * @param answerh Session answer handler * @param estabh Session established handler * @param infoh Session info handler * @param referh Session refer handler * @param closeh Session close handler * @param arg Handler argument * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipsess_accept(struct sipsess **sessp, struct sipsess_sock *sock, const struct sip_msg *msg, uint16_t scode, const char *reason, enum rel100_mode rel100, const char *cuser, const char *ctype, struct mbuf *desc, sip_auth_h *authh, void *aarg, bool aref, sipsess_offer_h *offerh, sipsess_answer_h *answerh, sipsess_estab_h *estabh, sipsess_info_h *infoh, sipsess_refer_h *referh, sipsess_close_h *closeh, void *arg, const char *fmt, ...) { struct sipsess *sess; va_list ap; int err; if (!sessp || !sock || !msg || scode < 101 || scode > 299 || !cuser || !ctype) return EINVAL; err = sipsess_alloc(&sess, sock, cuser, ctype, NULL, authh, aarg, aref, NULL, offerh, answerh, NULL, estabh, infoh, referh, closeh, arg); if (err) return err; err = sip_dialog_accept(&sess->dlg, msg); if (err) goto out; hash_append(sock->ht_sess, hash_joaat_str(sip_dialog_callid(sess->dlg)), &sess->he, sess); sess->msg = mem_ref((void *)msg); err = sip_strans_alloc(&sess->st, sess->sip, msg, cancel_handler, sess); if (err) goto out; if (mbuf_get_left(msg->mb)) sess->neg_state = SDP_NEG_REMOTE_OFFER; va_start(ap, fmt); if (scode > 100 && scode < 200) { err = sipsess_reply_1xx(sess, msg, scode, reason, rel100, desc, fmt, &ap); } else if (scode >= 200) { err = sipsess_reply_2xx(sess, msg, scode, reason, desc, fmt, &ap); } va_end(ap); if (err) goto out; out: if (err) mem_deref(sess); else *sessp = sess; return err; } /** * Send progress response * * @param sess SIP Session * @param scode Response status code * @param reason Response reason phrase * @param rel100 Sending 1xx reliably supported, required or disabled * @param desc Content description (e.g. SDP) * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipsess_progress(struct sipsess *sess, uint16_t scode, const char *reason, enum rel100_mode rel100, struct mbuf *desc, const char *fmt, ...) { va_list ap; int err; if (!sess || !sess->st || !sess->msg || scode < 101 || scode > 199) return EINVAL; va_start(ap, fmt); err = sipsess_reply_1xx(sess, sess->msg, scode, reason, rel100, desc, fmt, &ap); va_end(ap); return err; } /** * Answer an incoming SIP Session connection * * @param sess SIP Session * @param scode Response status code * @param reason Response reason phrase * @param desc Content description (e.g. SDP) * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipsess_answer(struct sipsess *sess, uint16_t scode, const char *reason, struct mbuf *desc, const char *fmt, ...) { va_list ap; int err; if (!sess || !sess->st || !sess->msg || scode < 200 || scode > 299) return EINVAL; va_start(ap, fmt); err = sipsess_reply_2xx(sess, sess->msg, scode, reason, desc, fmt, &ap); va_end(ap); return err; } /** * Reject an incoming SIP Session connection * * @param sess SIP Session * @param scode Response status code * @param reason Response reason phrase * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipsess_reject(struct sipsess *sess, uint16_t scode, const char *reason, const char *fmt, ...) { va_list ap; int err; if (!sess || !sess->st || !sess->msg || scode < 300) return EINVAL; va_start(ap, fmt); err = sip_treplyf(&sess->st, NULL, sess->sip, sess->msg, false, scode, reason, fmt ? "%v" : NULL, fmt, &ap); va_end(ap); return err; } ================================================ FILE: src/sipsess/ack.c ================================================ /** * @file ack.c SIP Session ACK * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" struct sipsess_ack { struct le he; struct tmr tmr; struct sa dst; struct sip_request *req; struct sip_dialog *dlg; struct mbuf *mb; enum sip_transp tp; uint32_t cseq; }; static void destructor(void *arg) { struct sipsess_ack *ack = arg; hash_unlink(&ack->he); tmr_cancel(&ack->tmr); mem_deref(ack->req); mem_deref(ack->dlg); mem_deref(ack->mb); } static void tmr_handler(void *arg) { struct sipsess_ack *ack = arg; mem_deref(ack); } static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sipsess_ack *ack = arg; (void)src; (void)contp; mem_deref(ack->mb); ack->mb = mem_ref(mb); ack->dst = *dst; ack->tp = tp; tmr_start(&ack->tmr, 64 * SIP_T1, tmr_handler, ack); return 0; } static void resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess_ack *ack = arg; (void)err; (void)msg; mem_deref(ack); } int sipsess_ack(struct sipsess_sock *sock, struct sip_dialog *dlg, uint32_t cseq, struct sip_auth *auth, const char *ctype, struct mbuf *desc) { struct sipsess_ack *ack; int err; ack = mem_zalloc(sizeof(*ack), destructor); if (!ack) return ENOMEM; hash_append(sock->ht_ack, hash_joaat_str(sip_dialog_callid(dlg)), &ack->he, ack); ack->dlg = mem_ref(dlg); ack->cseq = cseq; err = sip_drequestf(&ack->req, sock->sip, false, "ACK", dlg, cseq, auth, send_handler, resp_handler, ack, "%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", desc ? "Content-Type: " : "", desc ? ctype : "", desc ? "\r\n" : "", desc ? mbuf_get_left(desc) : (size_t)0, desc ? mbuf_buf(desc) : NULL, desc ? mbuf_get_left(desc) : (size_t)0); if (err) goto out; out: if (err) mem_deref(ack); return err; } static bool cmp_handler(struct le *le, void *arg) { struct sipsess_ack *ack = le->data; const struct sip_msg *msg = arg; if (!sip_dialog_cmp(ack->dlg, msg)) return false; if (ack->cseq != msg->cseq.num) return false; return true; } int sipsess_ack_again(struct sipsess_sock *sock, const struct sip_msg *msg) { struct sipsess_ack *ack; ack = list_ledata(hash_lookup(sock->ht_ack, hash_joaat_pl(&msg->callid), cmp_handler, (void *)msg)); if (!ack) return ENOENT; return sip_send(sock->sip, NULL, ack->tp, &ack->dst, ack->mb); } ================================================ FILE: src/sipsess/close.c ================================================ /** * @file close.c SIP Session Close * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static void bye_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess *sess = arg; if (err || sip_request_loops(&sess->ls, msg->scode)) goto out; if (msg->scode < 200) { return; } else if (msg->scode < 300) { ; } else { if (sess->peerterm) goto out; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(sess->auth, msg); if (err) break; err = sipsess_bye(sess, false); if (err) break; return; } } out: mem_deref(sess); } int sipsess_bye(struct sipsess *sess, bool reset_ls) { if (sess->req) return EPROTO; if (reset_ls) sip_loopstate_reset(&sess->ls); return sip_drequestf(&sess->req, sess->sip, true, "BYE", sess->dlg, 0, sess->auth, NULL, bye_resp_handler, sess, "%s" "Content-Length: 0\r\n" "\r\n", sess->close_hdrs); } ================================================ FILE: src/sipsess/connect.c ================================================ /** * @file connect.c SIP Session Connect * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static int invite(struct sipsess *sess); static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sip_contact contact; struct sipsess *sess = arg; struct mbuf *desc = NULL; struct mbuf *cont = NULL; int err; if (sess->desch) { err = sess->desch(&desc, src, dst, sess->arg); if (err) return err; } sip_contact_set(&contact, sess->cuser, src, tp); err = mbuf_printf(mb, "%H", sip_contact_print, &contact); if (err) goto out; cont = mbuf_alloc(1024); if (!cont) { err = ENOMEM; goto out; } err |= mbuf_printf(cont, "%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", desc ? "Content-Type: " : "", desc ? sess->ctype : "", desc ? "\r\n" : "", mbuf_get_left(desc), mbuf_buf(desc), mbuf_get_left(desc)); cont->pos = 0; if (err) mem_deref(cont); else *contp = cont; out: if (desc) sess->neg_state = SDP_NEG_LOCAL_OFFER; mem_deref(desc); return err; } static void invite_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess *sess = arg; struct mbuf *desc = NULL; bool sdp; const struct sip_hdr *contact; struct sip_addr addr; char *uri; if (!sess) return; if (!msg || err || sip_request_loops(&sess->ls, msg->scode)) goto out; if (!sip_dialog_cmp_half(sess->dlg, msg) || sip_dialog_lseqinv(sess->dlg) != msg->cseq.num) goto out; sdp = mbuf_get_left(msg->mb) > 0; if (msg->scode < 200) { if (msg->scode == 100) return; contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); if (pl_isset(&msg->to.tag) && contact) { err = sip_dialog_established(sess->dlg) ? sip_dialog_update(sess->dlg, msg) : sip_dialog_create(sess->dlg, msg); if (err) goto out; } if (sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "100rel") && sess->rel100_supported) { if (sess->rel_seq && msg->rel_seq != (sess->rel_seq+1)) return; sess->rel_seq = msg->rel_seq; if (sess->neg_state == SDP_NEG_NONE && !sdp) goto out; sess->progrh(msg, sess->arg); if (sdp) { if (sess->neg_state == SDP_NEG_LOCAL_OFFER) { sess->neg_state = SDP_NEG_DONE; err = sess->answerh(msg, sess->arg); } else if (sess->neg_state == SDP_NEG_NONE) { sess->neg_state = SDP_NEG_REMOTE_OFFER; err = sess->offerh(&desc, msg, sess->arg); } } err |= sipsess_prack(sess, msg->cseq.num, msg->rel_seq, &msg->cseq.met, desc); if (err) goto out; if (sess->neg_state == SDP_NEG_REMOTE_OFFER && mbuf_get_left(desc)) sess->neg_state = SDP_NEG_DONE; mem_deref(desc); sess->desc = mem_deref(sess->desc); return; } sess->progrh(msg, sess->arg); if (sdp && sess->neg_state == SDP_NEG_LOCAL_OFFER) { err = sess->answerh(msg, sess->arg); if (err) goto out; } return; } else if (msg->scode < 300) { sess->established = true; sess->hdrs = mem_deref(sess->hdrs); err = sip_dialog_established(sess->dlg) ? sip_dialog_update(sess->dlg, msg) : sip_dialog_create(sess->dlg, msg); if (sdp && !err) { if (sess->neg_state == SDP_NEG_LOCAL_OFFER) { sess->neg_state = SDP_NEG_DONE; err = sess->answerh(msg, sess->arg); } else if (sess->neg_state == SDP_NEG_NONE) { sess->neg_state = SDP_NEG_REMOTE_OFFER; err = sess->offerh(&desc, msg, sess->arg); } } err |= sipsess_ack(sess->sock, sess->dlg, msg->cseq.num, sess->auth, sess->ctype, desc); if (err) goto out; if (sess->neg_state == SDP_NEG_NONE && !sdp) goto out; if (sess->neg_state == SDP_NEG_REMOTE_OFFER && mbuf_get_left(desc)) sess->neg_state = SDP_NEG_DONE; mem_deref(desc); if (err || sess->terminated) goto out; if (sess->modify_pending) (void)sipsess_reinvite(sess, true); else sess->desc = mem_deref(sess->desc); sess->estabh(msg, sess->arg); return; } else if (msg->scode < 400) { if (sess->terminated) goto out; if (sess->redirecth) { contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); if (!contact) { err = EBADMSG; goto out; } if (sip_addr_decode(&addr, &contact->val)) { err = EBADMSG; goto out; } err = pl_strdup(&uri, &addr.auri); if (err) goto out; sess->redirecth(msg, uri, sess->arg); mem_deref(uri); } else { /* Redirect to first Contact */ err = sip_dialog_update(sess->dlg, msg); if (err) goto out; err = invite(sess); if (err) goto out; return; } } else { if (sess->terminated) goto out; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(sess->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = invite(sess); if (err) break; return; } } out: if (!sess->terminated) sipsess_terminate(sess, err, msg); else mem_deref(sess); } static int invite(struct sipsess *sess) { sess->modify_pending = false; return sip_drequestf(&sess->req, sess->sip, true, "INVITE", sess->dlg, 0, sess->auth, send_handler, invite_resp_handler, sess, "%b", sess->hdrs ? mbuf_buf(sess->hdrs) : NULL, sess->hdrs ? mbuf_get_left(sess->hdrs) :(size_t)0 ); } /** * Connect to a remote SIP useragent * * @param sessp Pointer to allocated SIP Session * @param sock SIP Session socket * @param to_uri To SIP uri * @param from_name From display name * @param from_uri From SIP uri * @param cuser Contact username or URI * @param routev Outbound route vector * @param routec Outbound route vector count * @param ctype Session content-type * @param authh SIP Authentication handler * @param aarg Authentication handler argument * @param aref True to mem_ref() aarg * @param callid Call Identifier * @param desch Content description handler * @param offerh Session offer handler * @param answerh Session answer handler * @param progrh Session progress handler * @param estabh Session established handler * @param infoh Session info handler * @param referh Session refer handler * @param closeh Session close handler * @param arg Handler argument * @param fmt Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipsess_connect(struct sipsess **sessp, struct sipsess_sock *sock, const char *to_uri, const char *from_name, const char *from_uri, const char *cuser, const char *routev[], uint32_t routec, const char *ctype, sip_auth_h *authh, void *aarg, bool aref, const char *callid, sipsess_desc_h *desch, sipsess_offer_h *offerh, sipsess_answer_h *answerh, sipsess_progr_h *progrh, sipsess_estab_h *estabh, sipsess_info_h *infoh, sipsess_refer_h *referh, sipsess_close_h *closeh, void *arg, const char *fmt, ...) { struct sipsess *sess; struct pl hdrs; int err; if (!sessp || !sock || !to_uri || !from_uri || !cuser || !ctype) return EINVAL; err = sipsess_alloc(&sess, sock, cuser, ctype, NULL, authh, aarg, aref, desch, offerh, answerh, progrh, estabh, infoh, referh, closeh, arg); if (err) return err; /* Custom SIP headers */ if (fmt) { va_list ap; sess->hdrs = mbuf_alloc(256); if (!sess->hdrs) { err = ENOMEM; goto out; } va_start(ap, fmt); err = mbuf_vprintf(sess->hdrs, fmt, ap); sess->hdrs->pos = 0; va_end(ap); if (err) goto out; pl_set_mbuf(&hdrs, sess->hdrs); } sess->owner = true; sess->rel100_supported = fmt && !re_regex(hdrs.p, hdrs.l, "100rel"); err = sip_dialog_alloc(&sess->dlg, to_uri, to_uri, from_name, from_uri, routev, routec); if (err) goto out; if (str_isset(callid)) err = sip_dialog_set_callid(sess->dlg, callid); if (err) goto out; hash_append(sock->ht_sess, hash_joaat_str(sip_dialog_callid(sess->dlg)), &sess->he, sess); err = invite(sess); if (err) goto out; out: if (err) mem_deref(sess); else *sessp = sess; return err; } ================================================ FILE: src/sipsess/info.c ================================================ /** * @file info.c SIP Session Info * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static int info_request(struct sipsess_request *req); static void info_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess_request *req = arg; if (err || sip_request_loops(&req->ls, msg->scode)) goto out; if (msg->scode < 200) { return; } else if (msg->scode < 300) { ; } else { if (req->sess->terminated) goto out; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(req->sess->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = info_request(req); if (err) break; return; case 408: case 481: sipsess_terminate(req->sess, 0, msg); break; } } out: if (!req->sess->terminated) { if (err == ETIMEDOUT) sipsess_terminate(req->sess, err, NULL); else req->resph(err, msg, req->arg); } mem_deref(req); } static int info_request(struct sipsess_request *req) { return sip_drequestf(&req->req, req->sess->sip, true, "INFO", req->sess->dlg, 0, req->sess->auth, NULL, info_resp_handler, req, "Content-Type: %s\r\n" "Content-Length: %zu\r\n" "\r\n" "%b", req->ctype, mbuf_get_left(req->body), mbuf_buf(req->body), mbuf_get_left(req->body)); } /** * Send a SIP INFO request in the SIP Session * * @param sess SIP Session * @param ctype Content-type * @param body Content description (e.g. SDP) * @param resph Response handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sipsess_info(struct sipsess *sess, const char *ctype, struct mbuf *body, sip_resp_h *resph, void *arg) { struct sipsess_request *req; int err; if (!sess || sess->terminated || !ctype || !body) return EINVAL; if (!sip_dialog_established(sess->dlg)) return ENOTCONN; err = sipsess_request_alloc(&req, sess, ctype, body, resph, arg); if (err) return err; err = info_request(req); if (err) mem_deref(req); return err; } ================================================ FILE: src/sipsess/listen.c ================================================ /** * @file sipsess/listen.c SIP Session Listen * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static void destructor(void *arg) { struct sipsess_sock *sock = arg; mem_deref(sock->lsnr_resp); mem_deref(sock->lsnr_req); hash_flush(sock->ht_sess); mem_deref(sock->ht_sess); hash_flush(sock->ht_ack); mem_deref(sock->ht_ack); } static void internal_connect_handler(const struct sip_msg *msg, void *arg) { struct sipsess_sock *sock = arg; (void)sip_treply(NULL, sock->sip, msg, 486, "Busy Here"); } static void info_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { struct sip *sip = sock->sip; struct sipsess *sess; sess = sipsess_find(sock, msg); if (!sess || sess->terminated) { (void)sip_reply(sip, msg, 481, "Call Does Not Exist"); return; } if (!sip_dialog_rseq_valid(sess->dlg, msg)) { (void)sip_reply(sip, msg, 500, "Server Internal Error"); return; } if (!sess->infoh) { (void)sip_reply(sip, msg, 501, "Not Implemented"); return; } sess->infoh(sip, msg, sess->arg); } static void refer_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { struct sip *sip = sock->sip; struct sipsess *sess; sess = sipsess_find(sock, msg); if (!sess || sess->terminated) { (void)sip_reply(sip, msg, 481, "Call Does Not Exist"); return; } if (!sip_dialog_rseq_valid(sess->dlg, msg)) { (void)sip_reply(sip, msg, 500, "Server Internal Error"); return; } if (!sess->referh) { (void)sip_reply(sip, msg, 501, "Not Implemented"); return; } sess->referh(sip, msg, sess->arg); } static void bye_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { struct sip *sip = sock->sip; struct sipsess *sess; sess = sipsess_find(sock, msg); if (!sess) { (void)sip_reply(sip, msg, 481, "Call Does Not Exist"); return; } if (!sip_dialog_rseq_valid(sess->dlg, msg)) { (void)sip_reply(sip, msg, 500, "Server Internal Error"); return; } (void)sip_treplyf(NULL, NULL, sip, msg, false, 200, "OK", "%s" "Content-Length: 0\r\n" "\r\n", sess->close_hdrs); sess->peerterm = true; if (sess->terminated) return; if (sess->st) { (void)sip_treply(&sess->st, sess->sip, sess->msg, 487, "Request Terminated"); } sipsess_terminate(sess, ECONNRESET, NULL); } static void ack_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { struct sipsess *sess; int err = 0; sess = sipsess_find(sock, msg); if (!sess) return; if (sipsess_reply_ack(sess, msg)) return; if (sess->terminated) { if (!sess->replyl.head) { sess->established = true; mem_deref(sess); } return; } if (sess->neg_state == SDP_NEG_LOCAL_OFFER) { if (!mbuf_get_left(msg->mb)) { sipsess_terminate(sess, EPROTO, NULL); return; } sess->neg_state = SDP_NEG_DONE; err = sess->answerh(msg, sess->arg); } if (sess->modify_pending && !sess->replyl.head) (void)sipsess_reinvite(sess, true); if (sess->established) return; sess->msg = mem_deref((void *)sess->msg); sess->established = true; if (err) sipsess_terminate(sess, err, NULL); else sess->estabh(msg, sess->arg); } static void prack_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { bool sdp; struct sipsess *sess; struct mbuf *desc = NULL; bool awaiting_prack = false; sess = sipsess_find(sock, msg); if (!sess || sipsess_reply_prack(sess, msg, &awaiting_prack)) { (void)sip_reply(sock->sip, msg, 481, "Transaction Does Not Exist"); return; } if (sess->terminated) { if (!sess->replyl.head) { sess->established = true; mem_deref(sess); } return; } sdp = mbuf_get_left(msg->mb); if (awaiting_prack) --sess->prack_waiting_cnt; if (sess->neg_state == SDP_NEG_LOCAL_OFFER) { if (!sdp) { sipsess_terminate(sess, EPROTO, NULL); return; } sess->neg_state = SDP_NEG_DONE; (void)sess->answerh(msg, sess->arg); } else if (sess->neg_state == SDP_NEG_DONE && sdp) { sess->neg_state = SDP_NEG_REMOTE_OFFER; (void)sess->offerh(&desc, msg, sess->arg); } if (sess->prackh) sess->prackh(msg, sess->arg); (void)sipsess_reply_2xx(sess, msg, 200, "OK", desc, NULL, NULL); mem_deref(desc); } static void target_refresh_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { struct sip *sip = sock->sip; bool is_invite; bool sdp; struct sipsess *sess; struct mbuf *desc = NULL; char m[256]; int err; sess = sipsess_find(sock, msg); if (!sess || sess->terminated) { (void)sip_treply(NULL, sip, msg, 481, "Call Does Not Exist"); return; } is_invite = !pl_strcmp(&msg->met, "INVITE"); sdp = (mbuf_get_left(msg->mb) > 0); if (!sip_dialog_rseq_valid(sess->dlg, msg)) { (void)sip_treply(NULL, sip, msg, 500, "Server Internal Error"); return; } if ((is_invite && sess->st) || (sdp && sess->neg_state == SDP_NEG_LOCAL_OFFER)) { if (!sess->established) { uint32_t wait = rand_u16() % 11; (void)sip_treplyf(NULL, NULL, sip, msg, false, 500, "Server Internal Error", "Retry-After: %u\r\n" "Content-Length: 0\r\n" "\r\n", wait); } else { (void)sip_treply(NULL, sip, msg, 491, "Request Pending"); } return; } if (is_invite && sess->req) { (void)sip_treply(NULL, sip, msg, 491, "Request Pending"); return; } if (sdp && !sipsess_refresh_allowed(sess)) { (void)sip_reply(sip, msg, 488, "Not Acceptable Here"); return; } if (is_invite || sdp) { sess->neg_state = sdp ? SDP_NEG_REMOTE_OFFER : SDP_NEG_LOCAL_OFFER; err = sess->offerh(&desc, msg, sess->arg); if (err) { (void)sip_reply(sip, msg, 488, str_error(err, m, sizeof(m))); sess->neg_state = SDP_NEG_DONE; return; } } (void)sip_dialog_update(sess->dlg, msg); (void)sipsess_reply_2xx(sess, msg, 200, "OK", desc, NULL, NULL); /* pending modifications considered outdated; sdp may have changed in above exchange */ sess->desc = mem_deref(sess->desc); sess->modify_pending = false; tmr_cancel(&sess->tmr); mem_deref(desc); } static void invite_handler(struct sipsess_sock *sock, const struct sip_msg *msg) { sock->connh(msg, sock->arg); } static bool request_handler(const struct sip_msg *msg, void *arg) { struct sipsess_sock *sock = arg; if (!pl_strcmp(&msg->met, "INVITE")) { if (pl_isset(&msg->to.tag)) target_refresh_handler(sock, msg); else invite_handler(sock, msg); return true; } else if (!pl_strcmp(&msg->met, "UPDATE")) { target_refresh_handler(sock, msg); return true; } else if (!pl_strcmp(&msg->met, "ACK")) { ack_handler(sock, msg); return true; } else if (!pl_strcmp(&msg->met, "PRACK")) { prack_handler(sock, msg); return true; } else if (!pl_strcmp(&msg->met, "BYE")) { bye_handler(sock, msg); return true; } else if (!pl_strcmp(&msg->met, "INFO")) { info_handler(sock, msg); return true; } else if (!pl_strcmp(&msg->met, "REFER")) { if (!pl_isset(&msg->to.tag)) return false; refer_handler(sock, msg); return true; } return false; } static bool response_handler(const struct sip_msg *msg, void *arg) { struct sipsess_sock *sock = arg; if (pl_strcmp(&msg->cseq.met, "INVITE")) return false; if (msg->scode < 200 || msg->scode > 299) return false; (void)sipsess_ack_again(sock, msg); return true; } /** * Listen to a SIP Session socket for incoming connections * * @param sockp Pointer to allocated SIP Session socket * @param sip SIP Stack instance * @param htsize Hashtable size * @param connh Connection handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int sipsess_listen(struct sipsess_sock **sockp, struct sip *sip, int htsize, sipsess_conn_h *connh, void *arg) { struct sipsess_sock *sock; int err; if (!sockp || !sip || !htsize) return EINVAL; sock = mem_zalloc(sizeof(*sock), destructor); if (!sock) return ENOMEM; err = sip_listen(&sock->lsnr_resp, sip, false, response_handler, sock); if (err) goto out; err = sip_listen(&sock->lsnr_req, sip, true, request_handler, sock); if (err) goto out; err = hash_alloc(&sock->ht_sess, htsize); if (err) goto out; err = hash_alloc(&sock->ht_ack, htsize); if (err) goto out; sock->sip = sip; sock->connh = connh ? connh : internal_connect_handler; sock->arg = connh ? arg : sock; out: if (err) mem_deref(sock); else *sockp = sock; return err; } /** * Close all SIP Sessions * * @param sock SIP Session socket */ void sipsess_close_all(struct sipsess_sock *sock) { if (!sock) return; hash_flush(sock->ht_sess); } ================================================ FILE: src/sipsess/modify.c ================================================ /** * @file modify.c SIP Session Modify * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static void tmr_handler(void *arg) { struct sipsess *sess = arg; (void)sipsess_reinvite(sess, true); } static void reinvite_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess *sess = arg; const struct sip_hdr *hdr; struct mbuf *desc = NULL; bool sdp; if (!msg || err || sip_request_loops(&sess->ls, msg->scode)) goto out; sdp = mbuf_get_left(msg->mb) > 0; if (msg->scode < 200) { return; } else if (msg->scode < 300) { (void)sip_dialog_update(sess->dlg, msg); if (sdp) { if (sess->neg_state == SDP_NEG_LOCAL_OFFER) { sess->neg_state = SDP_NEG_DONE; err = sess->answerh(msg, sess->arg); } else if (sess->neg_state == SDP_NEG_DONE) { sess->neg_state = SDP_NEG_REMOTE_OFFER; err = sess->offerh(&desc, msg, sess->arg); } if (err) goto out; } err = sipsess_ack(sess->sock, sess->dlg, msg->cseq.num, sess->auth, sess->ctype, desc); if (err) goto out; if (sess->neg_state == SDP_NEG_REMOTE_OFFER && mbuf_get_left(desc)) sess->neg_state = SDP_NEG_DONE; mem_deref(desc); } else { sess->neg_state = SDP_NEG_DONE; if (sess->terminated) goto out; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(sess->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = sipsess_reinvite(sess, false); if (err) break; return; case 408: case 481: sipsess_terminate(sess, 0, msg); return; case 491: tmr_start(&sess->tmr, sess->owner ? 3000 : 1000, tmr_handler, sess); return; case 500: hdr = sip_msg_hdr(msg, SIP_HDR_RETRY_AFTER); if (!hdr) break; tmr_start(&sess->tmr, pl_u32(&hdr->val) * 1000, tmr_handler, sess); return; } } out: if (sess->terminated) mem_deref(sess); else if (err == ETIMEDOUT) sipsess_terminate(sess, err, NULL); else if (sess->modify_pending) (void)sipsess_reinvite(sess, true); else sess->desc = mem_deref(sess->desc); } static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sip_contact contact; struct sipsess *sess = arg; (void)dst; (void)contp; sip_contact_set(&contact, sess->cuser, src, tp); return mbuf_printf(mb, "%H", sip_contact_print, &contact); } int sipsess_reinvite(struct sipsess *sess, bool reset_ls) { int err; if (sess->req) return EPROTO; if (reset_ls) sip_loopstate_reset(&sess->ls); err = sip_drequestf(&sess->req, sess->sip, true, "INVITE", sess->dlg, 0, sess->auth, send_handler, reinvite_resp_handler, sess, "%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", sess->desc ? "Content-Type: " : "", sess->desc ? sess->ctype : "", sess->desc ? "\r\n" : "", sess->desc ? mbuf_get_left(sess->desc) :(size_t)0, sess->desc ? mbuf_buf(sess->desc) : NULL, sess->desc ? mbuf_get_left(sess->desc):(size_t)0); if (!err) { sess->modify_pending = false; if (sess->desc) sess->neg_state = SDP_NEG_LOCAL_OFFER; } return err; } /** * Modify an established SIP Session sending Re-INVITE or UPDATE * * @param sess SIP Session * @param desc Content description (e.g. SDP) * * @return 0 if success, otherwise errorcode */ int sipsess_modify(struct sipsess *sess, struct mbuf *desc) { if (!sess || sess->terminated || !sip_dialog_established(sess->dlg)) return EINVAL; if (mbuf_get_left(desc) && (sess->neg_state != SDP_NEG_DONE && sess->neg_state != SDP_NEG_NONE)) return EPROTO; mem_deref(sess->desc); sess->desc = mem_ref(desc); if (!sess->established) return sipsess_update(sess); if (sess->req || sess->tmr.th || sess->replyl.head) { sess->modify_pending = true; return 0; } return sipsess_reinvite(sess, true); } ================================================ FILE: src/sipsess/prack.c ================================================ /** * @file prack.c SIP Session PRACK (RFC 3262) * * Copyright (C) 2022 commend.com - m.fridrich@commend.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" struct sipsess_prack { uint32_t cseq; uint32_t rseq; char *met; struct sipsess_request *req; }; static int prack_request(struct sipsess_prack *prack); static void destructor(void *arg) { struct sipsess_prack *prack = arg; mem_deref(prack->met); mem_deref(prack->req); } static void tmr_handler(void *arg) { struct sipsess_prack *prack = arg; int err; if (!prack) return; err = prack_request(prack); if (err) mem_deref(prack); } static void prack_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess_prack *prack = arg; struct sipsess_request *req = prack->req; const struct sip_hdr *hdr; if (!msg || err || sip_request_loops(&req->ls, msg->scode)) goto out; if (msg->scode < 200) { return; } else if (msg->scode < 300) { (void)sip_dialog_update(req->sess->dlg, msg); if (mbuf_get_left(msg->mb)) { if (req->sess->neg_state == SDP_NEG_LOCAL_OFFER) { req->sess->neg_state = SDP_NEG_DONE; (void)req->sess->answerh(msg, req->sess->arg); } req->sess->desc = mem_deref(req->sess->desc); } } else { if (req->sess->terminated) goto out; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(req->sess->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = prack_request(prack); if (err) break; return; case 408: case 491: tmr_start(&req->tmr, req->sess->owner ? 3000 : 1000, tmr_handler, req); return; case 500: hdr = sip_msg_hdr(msg, SIP_HDR_RETRY_AFTER); if (!hdr) break; tmr_start(&req->tmr, pl_u32(&hdr->val) * 1000, tmr_handler, req); return; } } out: if (!req->sess->terminated) { if (err == ETIMEDOUT) sipsess_terminate(req->sess, err, NULL); } mem_deref(prack); } static int prack_request(struct sipsess_prack *prack) { struct sipsess_request *req = prack->req; char rack_header[256]; int err; if (!req || req->tmr.th) return EINVAL; err = re_snprintf(rack_header, sizeof(rack_header), "%d %d %s", prack->rseq, prack->cseq, prack->met); if (err == -1) return err; return sip_drequestf(&req->req, req->sess->sip, true, "PRACK", req->sess->dlg, 0, req->sess->auth, NULL, prack_resp_handler, prack, "RAck: %s\n" "%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", rack_header, req->body ? "Content-Type: " : "", req->body ? req->sess->ctype : "", req->body ? "\r\n" : "", req->body ? mbuf_get_left(req->body) : (size_t)0, req->body ? mbuf_buf(req->body) : NULL, req->body ? mbuf_get_left(req->body) : (size_t)0); } /** * Send PRACK request (RFC 3262) * * @param sess SIP Session * @param cseq CSeq number to be written in RAck header * @param rseq RSeq number to be written in RAck header * @param met Method to be written in RAck header * @param desc Content description (e.g. SDP) * * @return 0 if success, otherwise errorcode */ int sipsess_prack(struct sipsess *sess, uint32_t cseq, uint32_t rseq, const struct pl *met, struct mbuf *desc) { struct sipsess_prack *prack; int err; if (!sess || sess->terminated) return EINVAL; prack = mem_zalloc(sizeof(*prack), destructor); if (!prack) return ENOMEM; err = sipsess_request_alloc(&prack->req, sess, sess->ctype, desc, NULL, prack); if (err) goto out; prack->cseq = cseq; prack->rseq = rseq; err = pl_strdup(&prack->met, met); if (err) goto out; err = prack_request(prack); out: if (err) mem_deref(prack); return err; } ================================================ FILE: src/sipsess/reply.c ================================================ /** * @file sipsess/reply.c SIP Session Reply * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" struct sipsess_reply { struct le le; struct tmr tmr; struct tmr tmrg; const struct sip_msg *msg; struct mbuf *mb; struct sipsess *sess; bool awaiting_prack; uint16_t scode; uint32_t seq; uint32_t rel_seq; uint32_t txc; }; static void destructor(void *arg) { struct sipsess_reply *reply = arg; list_unlink(&reply->le); tmr_cancel(&reply->tmr); tmr_cancel(&reply->tmrg); mem_deref((void *)reply->msg); mem_deref(reply->mb); } static void tmr_handler(void *arg) { struct sipsess_reply *reply = arg; struct sipsess *sess = reply->sess; /* we want to send bye */ if (!sess->terminated) { if (reply->scode < 200 && !sess->established) { (void)sip_reply(sess->sip, reply->msg, 504, "Timeout"); } else { sess->established = true; mem_deref(reply); sipsess_terminate(sess, ETIMEDOUT, NULL); return; } } else { mem_deref(reply); mem_deref(sess); return; } mem_deref(reply); } static void retransmit_handler(void *arg) { struct sipsess_reply *reply = arg; uint32_t delay; (void)sip_send(reply->sess->sip, reply->msg->sock, reply->msg->tp, &reply->msg->src, reply->mb); reply->txc++; delay = !reply->rel_seq ? MIN(SIP_T1 << reply->txc, SIP_T2) : SIP_T1 << reply->txc; tmr_start(&reply->tmrg, delay, retransmit_handler, reply); } static bool cancel_1xx_timers(struct le *le, void *arg) { struct sipsess_reply *reply = le->data; (void)arg; if (reply->scode > 100 && reply->scode < 200) { tmr_cancel(&reply->tmr); tmr_cancel(&reply->tmrg); } return false; } int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg, uint16_t scode, const char *reason, struct mbuf *desc, const char *fmt, va_list *ap) { struct sipsess_reply *reply = NULL; struct sip_contact contact; int err = ENOMEM; bool sdp = mbuf_get_left(msg->mb) > 0; bool non_invite = !pl_strcmp(&msg->met, "PRACK") || !pl_strcmp(&msg->met, "UPDATE"); if (!non_invite) { if (sess->neg_state == SDP_NEG_NONE && !mbuf_get_left(desc)) return EINVAL; else if (sess->neg_state == SDP_NEG_DONE) desc = NULL; if (sess->prack_waiting_cnt > 0) return EAGAIN; reply = mem_zalloc(sizeof(*reply), destructor); if (!reply) goto out; list_append(&sess->replyl, &reply->le, reply); reply->seq = msg->cseq.num; reply->msg = mem_ref((void *)msg); reply->scode = scode; reply->sess = sess; } if (non_invite && sess->neg_state != SDP_NEG_REMOTE_OFFER) desc = NULL; sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp); err = sip_treplyf(non_invite ? NULL : &sess->st, reply ? &reply->mb : NULL, sess->sip, msg, true, scode, reason, "%H" "%v" "%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", sip_contact_print, &contact, fmt, ap, desc ? "Content-Type: " : "", desc ? sess->ctype : "", desc ? "\r\n" : "", desc ? mbuf_get_left(desc) : (size_t)0, desc ? mbuf_buf(desc) : NULL, desc ? mbuf_get_left(desc) : (size_t)0); if (err) goto out; if (!non_invite) (void)list_ledata(list_apply(&sess->replyl, false, cancel_1xx_timers, NULL)); if (mbuf_get_left(desc)) { if (sdp) sess->neg_state = SDP_NEG_DONE; else if (!non_invite) sess->neg_state = SDP_NEG_LOCAL_OFFER; } if (reply) { tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply); tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply); } out: if (err) { if (!non_invite) sess->st = mem_deref(sess->st); mem_deref(reply); } return err; } int sipsess_reply_1xx(struct sipsess *sess, const struct sip_msg *msg, uint16_t scode, const char *reason, enum rel100_mode rel100, struct mbuf *desc, const char *fmt, va_list *ap) { struct sipsess_reply *reply; struct sip_contact contact; char rseq_header[64]; bool reliably; enum rel100_mode rel100_peer = REL100_DISABLED; struct pl require_header = pl_null; int err = ENOMEM; if (sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "100rel")) rel100_peer = REL100_REQUIRED; else if (sip_msg_hdr_has_value(msg, SIP_HDR_SUPPORTED, "100rel")) rel100_peer = REL100_ENABLED; if (rel100 == REL100_REQUIRED && !rel100_peer) { (void)sip_treplyf(&sess->st, NULL, sess->sip, msg, false, 421, "Extension required", "Require: 100rel\r\n" "Content-Length: 0\r\n\r\n"); return EPROTO; } else if (rel100_peer == REL100_REQUIRED && !rel100) { (void)sip_treplyf(&sess->st, NULL, sess->sip, msg, false, 420, "Bad Extension", "Unsupported: 100rel\r\n" "Content-Length: 0\r\n\r\n"); return EPROTO; } reliably = rel100 && rel100_peer && scode != 100; if (reliably && sess->prack_waiting_cnt) return EAGAIN; if (sess->neg_state == SDP_NEG_NONE) { if (reliably && !mbuf_get_left(desc)) return EINVAL; else if (!reliably) desc = NULL; } else if (sess->neg_state == SDP_NEG_DONE || sess->neg_state == SDP_NEG_LOCAL_OFFER) { desc = NULL; } if (rel100 != REL100_REQUIRED && reliably) { pl_set_str(&require_header, "Require: 100rel\r\n"); } reply = mem_zalloc(sizeof(*reply), destructor); if (!reply) goto out; list_append(&sess->replyl, &reply->le, reply); reply->seq = msg->cseq.num; reply->msg = mem_ref((void *)msg); reply->sess = sess; reply->scode = scode; sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp); if (reliably) { sess->rel_seq = sess->rel_seq ? sess->rel_seq+1 : rand_u16(); reply->rel_seq = sess->rel_seq; re_snprintf(rseq_header, sizeof(rseq_header), "%d", reply->rel_seq); } err = sip_treplyf(&sess->st, &reply->mb, sess->sip, msg, true, scode, reason, "%H" "%v" "%s%s%s%s%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", sip_contact_print, &contact, fmt, ap, require_header.p ? require_header.p : "", reliably ? "RSeq: " : "", reliably ? rseq_header : "", reliably ? "\r\n" : "", desc ? "Content-Type: " : "", desc ? sess->ctype : "", desc ? "\r\n" : "", desc ? mbuf_get_left(desc) : (size_t)0, desc ? mbuf_buf(desc) : NULL, desc ? mbuf_get_left(desc) : (size_t)0); if (err) goto out; if (reliably) { tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply); tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply); reply->awaiting_prack = true; ++sess->prack_waiting_cnt; if (desc) { sess->neg_state = mbuf_get_left(msg->mb) ? SDP_NEG_DONE : SDP_NEG_LOCAL_OFFER; } } else { if (desc && sess->neg_state == SDP_NEG_REMOTE_OFFER) sess->neg_state = SDP_NEG_PREVIEW_ANSWER; mem_deref(reply); } out: if (err) { sess->st = mem_deref(sess->st); mem_deref(reply); } return err; } static bool cmp_handler_prack(struct le *le, void *arg) { struct sipsess_reply *reply = le->data; const struct sip_msg *msg = arg; return msg->rack.cseq == reply->seq && msg->rack.rel_seq == reply->rel_seq && !pl_cmp(&msg->rack.met, &reply->msg->met); } static bool cmp_handler(struct le *le, void *arg) { struct sipsess_reply *reply = le->data; const struct sip_msg *msg = arg; return msg->cseq.num == reply->seq; } int sipsess_reply_prack(struct sipsess *sess, const struct sip_msg *msg, bool *awaiting_prack) { struct sipsess_reply *reply; reply = list_ledata(list_apply(&sess->replyl, false, cmp_handler_prack, (void *)msg)); if (!reply) return ENOENT; *awaiting_prack = reply->awaiting_prack; mem_deref(reply); return 0; } int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg) { struct sipsess_reply *reply; reply = list_ledata(list_apply(&sess->replyl, false, cmp_handler, (void *)msg)); if (!reply) return ENOENT; mem_deref(reply); return 0; } ================================================ FILE: src/sipsess/request.c ================================================ /** * @file sipsess/request.c SIP Session Non-INVITE Request * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static void destructor(void *arg) { struct sipsess_request *req = arg; tmr_cancel(&req->tmr); list_unlink(&req->le); mem_deref(req->ctype); mem_deref(req->body); mem_deref(req->req); /* wait for pending requests */ if (req->sess->terminated && !req->sess->requestl.head) mem_deref(req->sess); } static void internal_resp_handler(int err, const struct sip_msg *msg, void *arg) { (void)err; (void)msg; (void)arg; } int sipsess_request_alloc(struct sipsess_request **reqp, struct sipsess *sess, const char *ctype, struct mbuf *body, sip_resp_h *resph, void *arg) { struct sipsess_request *req; int err = 0; if (!reqp || !sess || sess->terminated) return EINVAL; req = mem_zalloc(sizeof(*req), destructor); if (!req) return ENOMEM; list_append(&sess->requestl, &req->le, req); if (ctype) { err = str_dup(&req->ctype, ctype); if (err) goto out; } req->sess = sess; req->body = mem_ref(body); req->resph = resph ? resph : internal_resp_handler; req->arg = arg; tmr_init(&req->tmr); out: if (err) mem_deref(req); else *reqp = req; return err; } ================================================ FILE: src/sipsess/sess.c ================================================ /** * @file sipsess/sess.c SIP Session Core * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static int internal_offer_handler(struct mbuf **descp, const struct sip_msg *msg, void *arg) { (void)descp; (void)msg; (void)arg; return ENOSYS; } static int internal_answer_handler(const struct sip_msg *msg, void *arg) { (void)msg; (void)arg; return ENOSYS; } static void internal_progress_handler(const struct sip_msg *msg, void *arg) { (void)msg; (void)arg; } static void internal_establish_handler(const struct sip_msg *msg, void *arg) { (void)msg; (void)arg; } static void internal_close_handler(int err, const struct sip_msg *msg, void *arg) { (void)err; (void)msg; (void)arg; } static bool termwait(struct sipsess *sess) { bool wait = false; sess->terminated = 1; sess->desch = NULL; sess->offerh = internal_offer_handler; sess->answerh = internal_answer_handler; sess->progrh = internal_progress_handler; sess->estabh = internal_establish_handler; sess->infoh = NULL; sess->referh = NULL; sess->closeh = internal_close_handler; sess->arg = sess; tmr_cancel(&sess->tmr); if (sess->st) { (void)sip_treply(&sess->st, sess->sip, sess->msg, 486, "Busy Here"); } if (sess->req) { sip_request_cancel(sess->req); if (!sip_request_provrecv(sess->req)) { sess->req = mem_deref(sess->req); } else { mem_ref(sess); wait = true; } } if (sess->replyl.head) { mem_ref(sess); wait = true; } if (sess->requestl.head) { mem_ref(sess); wait = true; } return wait; } static bool terminate(struct sipsess *sess) { sess->terminated = 2; if (!sess->established || sess->peerterm) return false; if (sipsess_bye(sess, true)) return false; mem_ref(sess); return true; } static void destructor(void *arg) { struct sipsess *sess = arg; switch (sess->terminated) { case 0: if (termwait(sess)) return; /*@fallthrough@*/ case 1: if (terminate(sess)) return; break; } hash_unlink(&sess->he); tmr_cancel(&sess->tmr); list_flush(&sess->replyl); list_flush(&sess->requestl); mem_deref((void *)sess->msg); mem_deref(sess->req); mem_deref(sess->dlg); mem_deref(sess->auth); mem_deref(sess->cuser); mem_deref(sess->ctype); mem_deref(sess->close_hdrs); mem_deref(sess->hdrs); mem_deref(sess->desc); mem_deref(sess->sock); mem_deref(sess->sip); mem_deref(sess->st); } int sipsess_alloc(struct sipsess **sessp, struct sipsess_sock *sock, const char *cuser, const char *ctype, struct mbuf *desc, sip_auth_h *authh, void *aarg, bool aref, sipsess_desc_h *desch, sipsess_offer_h *offerh, sipsess_answer_h *answerh, sipsess_progr_h *progrh, sipsess_estab_h *estabh, sipsess_info_h *infoh, sipsess_refer_h *referh, sipsess_close_h *closeh, void *arg) { struct sipsess *sess; int err; sess = mem_zalloc(sizeof(*sess), destructor); if (!sess) return ENOMEM; err = sip_auth_alloc(&sess->auth, authh, aarg, aref); if (err) goto out; err = str_dup(&sess->cuser, cuser); if (err) goto out; err = str_dup(&sess->ctype, ctype); if (err) goto out; sess->sock = mem_ref(sock); sess->desc = mem_ref(desc); sess->sip = mem_ref(sock->sip); sess->desch = desch; sess->offerh = offerh ? offerh : internal_offer_handler; sess->answerh = answerh ? answerh : internal_answer_handler; sess->progrh = progrh ? progrh : internal_progress_handler; sess->estabh = estabh ? estabh : internal_establish_handler; sess->infoh = infoh; sess->referh = referh; sess->closeh = closeh ? closeh : internal_close_handler; sess->arg = arg; out: if (err) mem_deref(sess); else *sessp = sess; return err; } int sipsess_set_redirect_handler(struct sipsess *sess, sipsess_redirect_h *redirecth) { if (!sess || !redirecth) return EINVAL; sess->redirecth = redirecth; return 0; } int sipsess_set_prack_handler(struct sipsess *sess, sipsess_prack_h *prackh) { if (!sess || !prackh) return EINVAL; sess->prackh = prackh; return 0; } static bool cmp_handler(struct le *le, void *arg) { struct sipsess *sess = le->data; const struct sip_msg *msg = arg; return sip_dialog_cmp(sess->dlg, msg); } struct sipsess *sipsess_find(struct sipsess_sock *sock, const struct sip_msg *msg) { return list_ledata(hash_lookup(sock->ht_sess, hash_joaat_pl(&msg->callid), cmp_handler, (void *)msg)); } void sipsess_terminate(struct sipsess *sess, int err, const struct sip_msg *msg) { sipsess_close_h *closeh; void *arg; if (sess->terminated) return; closeh = sess->closeh; arg = sess->arg; if (!termwait(sess)) (void)terminate(sess); closeh(err, msg, arg); } /** * Get the SIP dialog from a SIP Session * * @param sess SIP Session * * @return SIP Dialog object */ struct sip_dialog *sipsess_dialog(const struct sipsess *sess) { return sess ? sess->dlg : NULL; } /** * Set extra SIP headers for inclusion in Session "close" messages * like BYE and 200 OK. Multiple headers can be included. * * @param sess SIP Session * @param hdrs Formatted strings with extra SIP Headers * * @return 0 if success, otherwise errorcode */ int sipsess_set_close_headers(struct sipsess *sess, const char *hdrs, ...) { int err = 0; va_list ap; if (!sess) return EINVAL; sess->close_hdrs = mem_deref(sess->close_hdrs); if (hdrs) { va_start(ap, hdrs); err = re_vsdprintf(&sess->close_hdrs, hdrs, ap); va_end(ap); } return err; } /** * Send BYE and terminate session (useful when ACK has not been received) * * @param sess SIP Session */ void sipsess_abort(struct sipsess *sess) { if (!sess) return; sipsess_bye(sess, true); sess->terminated = 2; } /** * Return true if session is waiting for a PRACK to a 1xx containing SDP * * @param sess SIP Session * * @return true if session is waiting for a PRACK to a 1xx containing SDP, * false otherwise */ bool sipsess_awaiting_prack(const struct sipsess *sess) { return sess ? sess->prack_waiting_cnt > 0 : false; } /** * Return true if a target refresh (re-INVITE or UPDATE) is currently allowed * * @param sess SIP Session * * @return True if a target refresh is currently allowed, otherwise false */ bool sipsess_refresh_allowed(const struct sipsess *sess) { if (!sess) return false; return !sess->terminated && sess->neg_state == SDP_NEG_DONE; } /** * Return true if there is an open SIP Session Reply for which an ACK is * expected * * @param sess SIP Session * * @return True if ACK is pending, otherwise false */ bool sipsess_ack_pending(const struct sipsess *sess) { return sess && sess->replyl.head ? true : false; } /** * Get the SIP message of a SIP Session * * @param sess SIP Session * @return SIP Message */ const struct sip_msg *sipsess_msg(const struct sipsess *sess) { return sess ? sess->msg : NULL; } /** * Get the SDP negotiation state of a SIP Session * * @param sess SIP Session * * @return SDP negotiation state */ enum sdp_neg_state sipsess_sdp_neg_state(const struct sipsess *sess) { return sess ? sess->neg_state : SDP_NEG_NONE; } ================================================ FILE: src/sipsess/sipsess.h ================================================ /** * @file sipsess.h SIP Session Private Interface * * Copyright (C) 2010 Creytiv.com */ struct sipsess { struct le he; struct tmr tmr; struct list replyl; struct list requestl; struct sip_loopstate ls; struct sipsess_sock *sock; const struct sip_msg *msg; struct sip_request *req; struct sip_dialog *dlg; struct sip_strans *st; struct sip_auth *auth; struct sip *sip; char *cuser; char *ctype; char *close_hdrs; struct mbuf *hdrs; struct mbuf *desc; sipsess_desc_h *desch; sipsess_offer_h *offerh; sipsess_answer_h *answerh; sipsess_progr_h *progrh; sipsess_estab_h *estabh; sipsess_info_h *infoh; sipsess_refer_h *referh; sipsess_close_h *closeh; sipsess_redirect_h *redirecth; sipsess_prack_h *prackh; void *arg; uint32_t rel_seq; bool owner; bool modify_pending; bool established; bool peerterm; bool rel100_supported; int prack_waiting_cnt; int terminated; enum sdp_neg_state neg_state; }; struct sipsess_sock { struct sip_lsnr *lsnr_resp; struct sip_lsnr *lsnr_req; struct hash *ht_sess; struct hash *ht_ack; struct sip *sip; sipsess_conn_h *connh; void *arg; }; struct sipsess_request { struct le le; struct sip_loopstate ls; struct sipsess *sess; struct sip_request *req; char *ctype; struct mbuf *body; sip_resp_h *resph; struct tmr tmr; void *arg; }; int sipsess_alloc(struct sipsess **sessp, struct sipsess_sock *sock, const char *cuser, const char *ctype, struct mbuf *desc, sip_auth_h *authh, void *aarg, bool aref, sipsess_desc_h *desch, sipsess_offer_h *offerh, sipsess_answer_h *answerh, sipsess_progr_h *progrh, sipsess_estab_h *estabh, sipsess_info_h *infoh, sipsess_refer_h *referh, sipsess_close_h *closeh, void *arg); void sipsess_terminate(struct sipsess *sess, int err, const struct sip_msg *msg); int sipsess_ack(struct sipsess_sock *sock, struct sip_dialog *dlg, uint32_t cseq, struct sip_auth *auth, const char *ctype, struct mbuf *desc); int sipsess_ack_again(struct sipsess_sock *sock, const struct sip_msg *msg); int sipsess_prack(struct sipsess *sess, uint32_t cseq, uint32_t rseq, const struct pl *met, struct mbuf *desc); int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg, uint16_t scode, const char *reason, struct mbuf *desc, const char *fmt, va_list *ap); int sipsess_reply_1xx(struct sipsess *sess, const struct sip_msg *msg, uint16_t scode, const char *reason, enum rel100_mode rel100, struct mbuf *desc, const char *fmt, va_list *ap); int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg); int sipsess_reply_prack(struct sipsess *sess, const struct sip_msg *msg, bool *awaiting_prack); int sipsess_reinvite(struct sipsess *sess, bool reset_ls); int sipsess_update(struct sipsess *sess); int sipsess_bye(struct sipsess *sess, bool reset_ls); int sipsess_request_alloc(struct sipsess_request **reqp, struct sipsess *sess, const char *ctype, struct mbuf *body, sip_resp_h *resph, void *arg); struct sipsess *sipsess_find(struct sipsess_sock *sock, const struct sip_msg *msg); ================================================ FILE: src/sipsess/update.c ================================================ /** * @file update.c SIP Session UPDATE (RFC 3311) * * Copyright (C) 2022 commend.com - m.fridrich@commend.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sipsess.h" static int update_request(struct sipsess_request *req); static void tmr_handler(void *arg) { struct sipsess_request *req = arg; int err; err = update_request(req); if (err) mem_deref(req); } static void update_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct sipsess_request *req = arg; const struct sip_hdr *hdr; if (!msg || err || sip_request_loops(&req->ls, msg->scode)) goto out; if (msg->scode < 200) { return; } else if (msg->scode < 300) { (void)sip_dialog_update(req->sess->dlg, msg); if (req->sess->neg_state == SDP_NEG_LOCAL_OFFER) { (void)req->sess->answerh(msg, req->sess->arg); req->sess->neg_state = SDP_NEG_DONE; } } else { if (req->sess->terminated) goto out; req->sess->neg_state = SDP_NEG_DONE; switch (msg->scode) { case 401: case 407: err = sip_auth_authenticate(req->sess->auth, msg); if (err) { err = (err == EAUTH) ? 0 : err; break; } err = update_request(req); if (err) break; return; case 408: case 481: sipsess_terminate(req->sess, 0, msg); break; case 491: tmr_start(&req->tmr, req->sess->owner ? 3000 : 1000, tmr_handler, req); return; case 500: hdr = sip_msg_hdr(msg, SIP_HDR_RETRY_AFTER); if (!hdr) break; tmr_start(&req->tmr, pl_u32(&hdr->val) * 1000, tmr_handler, req); return; } } out: if (!req->sess->terminated) { if (err == ETIMEDOUT) sipsess_terminate(req->sess, err, NULL); else req->resph(err, msg, req->arg); } mem_deref(req); } static int send_handler(enum sip_transp tp, struct sa *src, const struct sa *dst, struct mbuf *mb, struct mbuf **contp, void *arg) { struct sip_contact contact; struct sipsess_request *req = arg; (void)dst; (void)contp; sip_contact_set(&contact, req->sess->cuser, src, tp); return mbuf_printf(mb, "%H", sip_contact_print, &contact); } static int update_request(struct sipsess_request *req) { int err; if (!req || req->tmr.th) return -1; err = sip_drequestf(&req->req, req->sess->sip, true, "UPDATE", req->sess->dlg, 0, req->sess->auth, send_handler, update_resp_handler, req, "%s%s%s" "Content-Length: %zu\r\n" "\r\n" "%b", req->body ? "Content-Type: " : "", req->body ? req->ctype : "", req->body ? "\r\n" : "", req->body ? mbuf_get_left(req->body) :(size_t)0, req->body ? mbuf_buf(req->body) : NULL, req->body ? mbuf_get_left(req->body):(size_t)0); if (!err && req->sess->desc) req->sess->neg_state = SDP_NEG_LOCAL_OFFER; return err; } /** * Send UPDATE request (RFC 3311) * * @param sess SIP Session * * @return 0 if success, otherwise errorcode */ int sipsess_update(struct sipsess *sess) { struct sipsess_request *req; int err; if (!sess || sess->terminated || !sess->ctype || !sess->desc) return EINVAL; err = sipsess_request_alloc(&req, sess, sess->ctype, sess->desc, NULL, NULL); if (err) return err; err = update_request(req); if (err) { mem_deref(req); return err; } sess->modify_pending = false; return err; } ================================================ FILE: src/srtp/README ================================================ SRTP module ----------- The SRTP module implements Secure RTP as defined in RFC 3711. It provides a clean and user friendly API and can be used as a standalone module. Requirements and features: RFC 3711 yes RFC 6188 yes Multiple Master keys: no Key derivation rate: 0 (zero) Salting keys: yes SRTP protection: yes SRTCP protection: yes Replay protection: yes Encryption: yes Authentication: yes MKI (Master Key Identifier): no Authentication tag length: 32-bit and 80-bit ROC (Roll Over Counter): yes Master key lifetime: no Multiple SSRCs: yes Performance: better than libsrtp Cryptographic transforms: - AES in Counter mode: yes - AES in f8-mode: no - NULL Cipher: no Authentication transform: - HMAC-SHA1: yes - NULL auth: no master key lengths: - 128 bits yes - 192 bits no - 256 bits yes ================================================ FILE: src/srtp/misc.c ================================================ /** * @file srtp/misc.c SRTP functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "srtp.h" /* * Appendix A: Pseudocode for Index Determination * * In the following, signed arithmetic is assumed. */ uint64_t srtp_get_index(uint32_t roc, uint16_t s_l, uint16_t seq) { int v; if (s_l < 32768) { if ((int)seq - (int)s_l > 32768) v = (roc-1) & 0xffffffffu; else v = roc; } else { if ((int)s_l - 32768 > seq) v = (roc+1) & 0xffffffffu; else v = roc; } return seq + v*(uint64_t)65536; } int srtp_derive(uint8_t *out, size_t out_len, uint8_t label, const uint8_t *master_key, size_t key_bytes, const uint8_t *master_salt, size_t salt_bytes) { uint8_t x[AES_BLOCK_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; static const uint8_t null[AES_BLOCK_SIZE * 2]; struct aes *aes; int err; if (!out || !master_key || !master_salt) return EINVAL; if (out_len > sizeof(null) || salt_bytes > sizeof(x)) return EINVAL; memcpy(x, master_salt, salt_bytes); x[7] ^= label; /* NOTE: Counter Mode is used for both CTR and GCM */ err = aes_alloc(&aes, AES_MODE_CTR, master_key, key_bytes*8, x); if (err) return err; err = aes_encr(aes, out, null, out_len); mem_deref(aes); return err; } void srtp_iv_calc(union vect128 *iv, const union vect128 *k_s, uint32_t ssrc, uint64_t ix) { if (!iv || !k_s) return; iv->u32[0] = k_s->u32[0]; iv->u32[1] = k_s->u32[1] ^ htonl(ssrc); iv->u32[2] = k_s->u32[2] ^ htonl((uint32_t)(ix>>16)); iv->u16[6] = k_s->u16[6] ^ htons((uint16_t)ix); iv->u16[7] = 0; } /* * NOTE: The IV for AES-GCM is 12 bytes */ void srtp_iv_calc_gcm(union vect128 *iv, const union vect128 *k_s, uint32_t ssrc, uint64_t ix) { if (!iv || !k_s) return; iv->u16[0] = k_s->u16[0]; iv->u16[1] = k_s->u16[1] ^ htons(ssrc >> 16); iv->u16[2] = k_s->u16[2] ^ htons(ssrc & 0xffff); iv->u16[3] = k_s->u16[3] ^ htons((ix >> 32) & 0xffff); iv->u16[4] = k_s->u16[4] ^ htons((ix >> 16) & 0xffff); iv->u16[5] = k_s->u16[5] ^ htons(ix & 0xffff); } const char *srtp_suite_name(enum srtp_suite suite) { switch (suite) { case SRTP_AES_CM_128_HMAC_SHA1_32: return "AES_CM_128_HMAC_SHA1_32"; case SRTP_AES_CM_128_HMAC_SHA1_80: return "AES_CM_128_HMAC_SHA1_80"; case SRTP_AES_256_CM_HMAC_SHA1_32: return "AES_256_CM_HMAC_SHA1_32"; case SRTP_AES_256_CM_HMAC_SHA1_80: return "AES_256_CM_HMAC_SHA1_80"; case SRTP_AES_128_GCM: return "AEAD_AES_128_GCM"; case SRTP_AES_256_GCM: return "AEAD_AES_256_GCM"; default: return "?"; } } ================================================ FILE: src/srtp/replay.c ================================================ /** * @file srtp/replay.c SRTP replay protection * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include "srtp.h" enum { SRTP_WINDOW_SIZE = 64 }; void srtp_replay_init(struct replay *replay) { if (!replay) return; replay->bitmap = 0; replay->lix = 0; } /* * Returns false if packet disallowed, true if packet permitted */ bool srtp_replay_check(struct replay *replay, uint64_t ix) { uint64_t diff; if (!replay) return false; if (ix > replay->lix) { diff = ix - replay->lix; if (diff < SRTP_WINDOW_SIZE) { /* In window */ replay->bitmap <<= diff; replay->bitmap |= 1; /* set bit for this packet */ } else replay->bitmap = 1; replay->lix = ix; return true; } diff = replay->lix - ix; if (diff >= SRTP_WINDOW_SIZE) return false; if (replay->bitmap & (1ULL << diff)) return false; /* already seen */ /* mark as seen */ replay->bitmap |= (1ULL << diff); return true; } ================================================ FILE: src/srtp/srtcp.c ================================================ /** * @file srtcp.c Secure Real-time Transport Control Protocol (SRTCP) * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include "srtp.h" static int get_rtcp_ssrc(uint32_t *ssrc, struct mbuf *mb) { if (mbuf_get_left(mb) < 8) return EBADMSG; mbuf_advance(mb, 4); *ssrc = ntohl(mbuf_read_u32(mb)); return 0; } int srtcp_encrypt(struct srtp *srtp, struct mbuf *mb) { struct srtp_stream *strm; struct comp *rtcp; uint32_t ssrc; size_t start; uint32_t ep = 0; int err; if (!srtp || !mb) return EINVAL; rtcp = &srtp->rtcp; start = mb->pos; err = get_rtcp_ssrc(&ssrc, mb); if (err) return err; err = stream_get(&strm, srtp, ssrc); if (err) return err; strm->rtcp_index = (strm->rtcp_index+1) & 0x7fffffff; if (rtcp->aes && rtcp->mode == AES_MODE_CTR) { union vect128 iv; uint8_t *p = mbuf_buf(mb); srtp_iv_calc(&iv, &rtcp->k_s, ssrc, strm->rtcp_index); aes_set_iv(rtcp->aes, iv.u8); err = aes_encr(rtcp->aes, p, p, mbuf_get_left(mb)); if (err) return err; ep = 1; } else if (rtcp->aes && rtcp->mode == AES_MODE_GCM) { union vect128 iv; uint8_t *p = mbuf_buf(mb); uint8_t tag[GCM_TAGLEN]; const uint32_t ix_be = htonl(1L<<31 | strm->rtcp_index); srtp_iv_calc_gcm(&iv, &rtcp->k_s, ssrc, strm->rtcp_index); aes_set_iv(rtcp->aes, iv.u8); /* The RTCP Header and Index is Associated Data */ err = aes_encr(rtcp->aes, NULL, &mb->buf[start], mb->pos - start); err |= aes_encr(rtcp->aes, NULL, (void *)&ix_be, sizeof(ix_be)); if (err) return err; err = aes_encr(rtcp->aes, p, p, mbuf_get_left(mb)); if (err) return err; err = aes_get_authtag(rtcp->aes, tag, sizeof(tag)); if (err) return err; mb->pos = mb->end; err = mbuf_write_mem(mb, tag, sizeof(tag)); if (err) return err; ep = 1; } /* append E-bit and SRTCP-index */ mb->pos = mb->end; err = mbuf_write_u32(mb, htonl(ep<<31 | strm->rtcp_index)); if (err) return err; if (rtcp->hmac) { uint8_t tag[SHA_DIGEST_LENGTH] = {0}; mb->pos = start; err = hmac_digest(rtcp->hmac, tag, sizeof(tag), mbuf_buf(mb), mbuf_get_left(mb)); if (err) return err; mb->pos = mb->end; err = mbuf_write_mem(mb, tag, rtcp->tag_len); if (err) return err; } mb->pos = start; return 0; } int srtcp_decrypt(struct srtp *srtp, struct mbuf *mb) { size_t start, eix_start, pld_start; struct srtp_stream *strm; struct comp *rtcp; uint32_t v, ix; uint32_t ssrc; bool ep; int err; if (!srtp || !mb) return EINVAL; rtcp = &srtp->rtcp; start = mb->pos; err = get_rtcp_ssrc(&ssrc, mb); if (err) return err; err = stream_get(&strm, srtp, ssrc); if (err) return err; pld_start = mb->pos; if (mbuf_get_left(mb) < (4 + rtcp->tag_len)) return EBADMSG; /* Read out E-Bit, SRTCP-index and Authentication Tag */ eix_start = mb->end - (4 + rtcp->tag_len); mb->pos = eix_start; v = ntohl(mbuf_read_u32(mb)); ep = (v >> 31) & 1; ix = v & 0x7fffffff; if (rtcp->hmac) { uint8_t tag[SHA_DIGEST_LENGTH] = {0}; uint8_t tag_pkt[SHA_DIGEST_LENGTH] = {0}; const size_t tag_start = mb->pos; if (rtcp->tag_len > SHA_DIGEST_LENGTH) return ERANGE; err = mbuf_read_mem(mb, tag_pkt, rtcp->tag_len); if (err) return err; mb->pos = start; mb->end = tag_start; err = hmac_digest(rtcp->hmac, tag, sizeof(tag), mbuf_buf(mb), mbuf_get_left(mb)); if (err) return err; if (0 != memcmp(tag, tag_pkt, rtcp->tag_len)) return EAUTH; /* * SRTCP replay protection is as defined in Section 3.3.2, * but using the SRTCP index as the index i and a separate * Replay List that is specific to SRTCP. */ if (!srtp_replay_check(&strm->replay_rtcp, ix)) return EALREADY; } mb->end = eix_start; if (rtcp->aes && ep && rtcp->mode == AES_MODE_CTR) { union vect128 iv; uint8_t *p; mb->pos = pld_start; p = mbuf_buf(mb); srtp_iv_calc(&iv, &rtcp->k_s, ssrc, ix); aes_set_iv(rtcp->aes, iv.u8); err = aes_decr(rtcp->aes, p, p, mbuf_get_left(mb)); if (err) return err; } else if (rtcp->aes && ep && rtcp->mode == AES_MODE_GCM) { union vect128 iv; size_t tag_start; uint8_t *p; srtp_iv_calc_gcm(&iv, &rtcp->k_s, ssrc, ix); aes_set_iv(rtcp->aes, iv.u8); /* The RTP Header is Associated Data */ err = aes_decr(rtcp->aes, NULL, &mb->buf[start], pld_start - start); err |= aes_decr(rtcp->aes, NULL, &mb->buf[eix_start], 4); if (err) return err; mb->pos = pld_start; p = mbuf_buf(mb); if (mbuf_get_left(mb) < GCM_TAGLEN) return EBADMSG; tag_start = mb->end - GCM_TAGLEN; err = aes_decr(rtcp->aes, p, p, tag_start - pld_start); if (err) return err; err = aes_authenticate(rtcp->aes, &mb->buf[tag_start], GCM_TAGLEN); if (err) return err; mb->end = tag_start; } mb->pos = start; return 0; } ================================================ FILE: src/srtp/srtp.c ================================================ /** * @file srtp.c Secure Real-time Transport Protocol (SRTP) * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "srtp.h" /** SRTP protocol values */ enum { MAX_KEYLEN = 32, /**< Maximum keylength in bytes */ }; static inline int seq_diff(uint16_t x, uint16_t y) { return (int)y - (int)x; } static int comp_init(struct comp *c, unsigned offs, const uint8_t *key, size_t key_b, const uint8_t *s, size_t s_b, size_t tag_len, bool encrypted, bool hash, enum aes_mode mode) { uint8_t k_e[MAX_KEYLEN], k_a[SHA_DIGEST_LENGTH]; int err = 0; if (key_b > sizeof(k_e)) return EINVAL; if (tag_len > SHA_DIGEST_LENGTH) return EINVAL; c->tag_len = tag_len; c->mode = mode; err |= srtp_derive(k_e, key_b, 0x00+offs, key, key_b, s, s_b); err |= srtp_derive(k_a, sizeof(k_a), 0x01+offs, key, key_b, s, s_b); err |= srtp_derive(c->k_s.u8, 14, 0x02+offs, key, key_b, s, s_b); if (err) return err; if (encrypted) { err = aes_alloc(&c->aes, mode, k_e, key_b*8, NULL); if (err) return err; } if (hash) { err = hmac_create(&c->hmac, HMAC_HASH_SHA1, k_a, sizeof(k_a)); if (err) return err; } return err; } static void destructor(void *arg) { struct srtp *srtp = arg; mem_deref(srtp->rtp.aes); mem_deref(srtp->rtcp.aes); mem_deref(srtp->rtp.hmac); mem_deref(srtp->rtcp.hmac); list_flush(&srtp->streaml); } int srtp_alloc(struct srtp **srtpp, enum srtp_suite suite, const uint8_t *key, size_t key_bytes, int flags) { struct srtp *srtp; const uint8_t *master_salt; size_t cipher_bytes, salt_bytes, auth_bytes; enum aes_mode mode; bool hash; int err = 0; if (!srtpp || !key) return EINVAL; switch (suite) { case SRTP_AES_CM_128_HMAC_SHA1_80: mode = AES_MODE_CTR; cipher_bytes = 16; salt_bytes = 14; auth_bytes = 10; hash = true; break; case SRTP_AES_CM_128_HMAC_SHA1_32: mode = AES_MODE_CTR; cipher_bytes = 16; salt_bytes = 14; auth_bytes = 4; hash = true; break; case SRTP_AES_256_CM_HMAC_SHA1_80: mode = AES_MODE_CTR; cipher_bytes = 32; salt_bytes = 14; auth_bytes = 10; hash = true; break; case SRTP_AES_256_CM_HMAC_SHA1_32: mode = AES_MODE_CTR; cipher_bytes = 32; salt_bytes = 14; auth_bytes = 4; hash = true; break; case SRTP_AES_128_GCM: mode = AES_MODE_GCM; cipher_bytes = 16; salt_bytes = 12; auth_bytes = 0; hash = false; break; case SRTP_AES_256_GCM: mode = AES_MODE_GCM; cipher_bytes = 32; salt_bytes = 12; auth_bytes = 0; hash = false; break; default: return ENOTSUP; }; if ((cipher_bytes + salt_bytes) != key_bytes) return EINVAL; master_salt = &key[cipher_bytes]; srtp = mem_zalloc(sizeof(*srtp), destructor); if (!srtp) return ENOMEM; err |= comp_init(&srtp->rtp, 0, key, cipher_bytes, master_salt, salt_bytes, auth_bytes, true, hash, mode); err |= comp_init(&srtp->rtcp, 3, key, cipher_bytes, master_salt, salt_bytes, auth_bytes, !(flags & SRTP_UNENCRYPTED_SRTCP), hash, mode); if (err) goto out; out: if (err) mem_deref(srtp); else *srtpp = srtp; return err; } int srtp_encrypt(struct srtp *srtp, struct mbuf *mb) { struct srtp_stream *strm; struct rtp_header hdr; struct comp *comp; size_t start; uint64_t ix; int err; if (!srtp || !mb) return EINVAL; comp = &srtp->rtp; start = mb->pos; err = rtp_hdr_decode(&hdr, mb); if (err) return err; err = stream_get_seq(&strm, srtp, hdr.ssrc, hdr.seq); if (err) return err; /* Roll-Over Counter (ROC) */ if (seq_diff(strm->s_l, hdr.seq) <= -32768) { strm->roc++; strm->s_l = 0; } ix = 65536ULL * strm->roc + hdr.seq; if (comp->aes && comp->mode == AES_MODE_CTR) { union vect128 iv; uint8_t *p = mbuf_buf(mb); srtp_iv_calc(&iv, &comp->k_s, strm->ssrc, ix); aes_set_iv(comp->aes, iv.u8); err = aes_encr(comp->aes, p, p, mbuf_get_left(mb)); if (err) return err; } else if (comp->aes && comp->mode == AES_MODE_GCM) { union vect128 iv; uint8_t *p = mbuf_buf(mb); uint8_t tag[GCM_TAGLEN]; srtp_iv_calc_gcm(&iv, &comp->k_s, strm->ssrc, ix); aes_set_iv(comp->aes, iv.u8); /* The RTP Header is Associated Data */ err = aes_encr(comp->aes, NULL, &mb->buf[start], mb->pos - start); if (err) return err; err = aes_encr(comp->aes, p, p, mbuf_get_left(mb)); if (err) return err; err = aes_get_authtag(comp->aes, tag, sizeof(tag)); if (err) return err; mb->pos = mb->end; err = mbuf_write_mem(mb, tag, sizeof(tag)); if (err) return err; } if (comp->hmac) { const size_t tag_start = mb->end; uint8_t tag[SHA_DIGEST_LENGTH] = {0}; mb->pos = tag_start; err = mbuf_write_u32(mb, htonl(strm->roc)); if (err) return err; mb->pos = start; err = hmac_digest(comp->hmac, tag, sizeof(tag), mbuf_buf(mb), mbuf_get_left(mb)); if (err) return err; mb->pos = mb->end = tag_start; err = mbuf_write_mem(mb, tag, comp->tag_len); if (err) return err; } if (hdr.seq > strm->s_l) strm->s_l = hdr.seq; mb->pos = start; return 0; } int srtp_decrypt(struct srtp *srtp, struct mbuf *mb) { struct srtp_stream *strm; struct rtp_header hdr; struct comp *comp; uint64_t ix; size_t start; int diff; int err; if (!srtp || !mb) return EINVAL; comp = &srtp->rtp; start = mb->pos; err = rtp_hdr_decode(&hdr, mb); if (err) return err; err = stream_get_seq(&strm, srtp, hdr.ssrc, hdr.seq); if (err) return err; diff = seq_diff(strm->s_l, hdr.seq); if (diff > 32768) return ETIMEDOUT; /* Roll-Over Counter (ROC) */ if (diff <= -32768) { strm->roc++; strm->s_l = 0; } ix = srtp_get_index(strm->roc, strm->s_l, hdr.seq); if (comp->hmac) { uint8_t tag_calc[SHA_DIGEST_LENGTH] = {0}; uint8_t tag_pkt[SHA_DIGEST_LENGTH] = {0}; size_t pld_start, tag_start; if (mbuf_get_left(mb) < comp->tag_len) return EBADMSG; pld_start = mb->pos; tag_start = mb->end - comp->tag_len; mb->pos = tag_start; err = mbuf_read_mem(mb, tag_pkt, comp->tag_len); if (err) return err; mb->pos = mb->end = tag_start; err = mbuf_write_u32(mb, htonl(strm->roc)); if (err) return err; mb->pos = start; err = hmac_digest(comp->hmac, tag_calc, sizeof(tag_calc), mbuf_buf(mb), mbuf_get_left(mb)); if (err) return err; mb->pos = pld_start; mb->end = tag_start; if (0 != memcmp(tag_calc, tag_pkt, comp->tag_len)) return EAUTH; /* * 3.3.2. Replay Protection * * Secure replay protection is only possible when * integrity protection is present. */ if (!srtp_replay_check(&strm->replay_rtp, ix)) return EALREADY; } if (comp->aes && comp->mode == AES_MODE_CTR) { union vect128 iv; uint8_t *p = mbuf_buf(mb); srtp_iv_calc(&iv, &comp->k_s, strm->ssrc, ix); aes_set_iv(comp->aes, iv.u8); err = aes_decr(comp->aes, p, p, mbuf_get_left(mb)); if (err) return err; } else if (comp->aes && comp->mode == AES_MODE_GCM) { union vect128 iv; uint8_t *p = mbuf_buf(mb); size_t tag_start; srtp_iv_calc_gcm(&iv, &comp->k_s, strm->ssrc, ix); aes_set_iv(comp->aes, iv.u8); /* The RTP Header is Associated Data */ err = aes_decr(comp->aes, NULL, &mb->buf[start], mb->pos - start); if (err) return err; if (mbuf_get_left(mb) < GCM_TAGLEN) return EBADMSG; tag_start = mb->end - GCM_TAGLEN; err = aes_decr(comp->aes, p, p, tag_start - mb->pos); if (err) return err; err = aes_authenticate(comp->aes, &mb->buf[tag_start], GCM_TAGLEN); if (err) return err; mb->end = tag_start; /* * 3.3.2. Replay Protection * * Secure replay protection is only possible when * integrity protection is present. */ if (!srtp_replay_check(&strm->replay_rtp, ix)) return EALREADY; } if (hdr.seq > strm->s_l) strm->s_l = hdr.seq; mb->pos = start; return 0; } ================================================ FILE: src/srtp/srtp.h ================================================ /** * @file srtp.h Secure Real-time Transport Protocol (SRTP) -- internal * * Copyright (C) 2010 Creytiv.com */ /** SRTP Protocol values */ enum { GCM_TAGLEN = 16, /**< GCM taglength in bytes */ }; /** Defines a 128-bit vector in network order */ union vect128 { uint64_t u64[ 2]; uint32_t u32[ 4]; uint16_t u16[ 8]; uint8_t u8[16]; }; /** Replay protection */ struct replay { uint64_t bitmap; /**< Session state - must be 64 bits */ uint64_t lix; /**< Last received index */ }; /** SRTP stream/context -- shared state between RTP/RTCP */ struct srtp_stream { struct le le; /**< Linked-list element */ struct replay replay_rtp; /**< recv -- replay protection for RTP */ struct replay replay_rtcp; /**< recv -- replay protection for RTCP */ uint32_t ssrc; /**< SSRC -- lookup key */ uint32_t roc; /**< send/recv Roll-Over Counter (ROC) */ uint16_t s_l; /**< send/recv -- highest SEQ number */ bool s_l_set; /**< True if s_l has been set */ uint32_t rtcp_index; /**< RTCP-index for sending (31-bits) */ }; /** SRTP Session */ struct srtp { struct comp { struct aes *aes; /**< AES Context */ enum aes_mode mode; /**< AES encryption mode */ struct hmac *hmac; /**< HMAC Context */ union vect128 k_s; /**< Derived salting key (14 bytes) */ size_t tag_len; /**< CTR Auth. tag length [bytes] */ } rtp, rtcp; struct list streaml; /**< SRTP-streams (struct srtp_stream) */ }; int stream_get(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc); int stream_get_seq(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc, uint16_t seq); int srtp_derive(uint8_t *out, size_t out_len, uint8_t label, const uint8_t *master_key, size_t key_bytes, const uint8_t *master_salt, size_t salt_bytes); void srtp_iv_calc(union vect128 *iv, const union vect128 *k_s, uint32_t ssrc, uint64_t ix); void srtp_iv_calc_gcm(union vect128 *iv, const union vect128 *k_s, uint32_t ssrc, uint64_t ix); uint64_t srtp_get_index(uint32_t roc, uint16_t s_l, uint16_t seq); /* Replay protection */ void srtp_replay_init(struct replay *replay); bool srtp_replay_check(struct replay *replay, uint64_t ix); ================================================ FILE: src/srtp/stream.c ================================================ /** * @file srtp/stream.c Secure Real-time Transport Protocol (SRTP) -- stream * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include "srtp.h" /** SRTP protocol values */ #ifndef SRTP_MAX_STREAMS #define SRTP_MAX_STREAMS (8) /**< Maximum number of SRTP streams */ #endif static void stream_destructor(void *arg) { struct srtp_stream *strm = arg; list_unlink(&strm->le); } static struct srtp_stream *stream_find(struct srtp *srtp, uint32_t ssrc) { struct le *le; for (le = srtp->streaml.head; le; le = le->next) { struct srtp_stream *strm = le->data; if (strm->ssrc == ssrc) return strm; } return NULL; } static int stream_new(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc) { struct srtp_stream *strm; if (list_count(&srtp->streaml) >= SRTP_MAX_STREAMS) return ENOSR; strm = mem_zalloc(sizeof(*strm), stream_destructor); if (!strm) return ENOMEM; strm->ssrc = ssrc; srtp_replay_init(&strm->replay_rtp); srtp_replay_init(&strm->replay_rtcp); list_append(&srtp->streaml, &strm->le, strm); if (strmp) *strmp = strm; return 0; } int stream_get(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc) { struct srtp_stream *strm; if (!strmp || !srtp) return EINVAL; strm = stream_find(srtp, ssrc); if (strm) { *strmp = strm; return 0; } return stream_new(strmp, srtp, ssrc); } int stream_get_seq(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc, uint16_t seq) { struct srtp_stream *strm; int err; if (!strmp || !srtp) return EINVAL; err = stream_get(&strm, srtp, ssrc); if (err) return err; /* Set the initial sequence number once only */ if (!strm->s_l_set) { strm->s_l = seq; strm->s_l_set = true; } *strmp = strm; return 0; } ================================================ FILE: src/stun/addr.c ================================================ /** * @file stun/addr.c STUN Address encoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include "stun.h" static void in6_xor_tid(uint8_t *in6, const uint8_t *tid) { int i; /* XOR with Magic Cookie (alignment safe) */ in6[0] ^= 0x21; in6[1] ^= 0x12; in6[2] ^= 0xa4; in6[3] ^= 0x42; for (i=0; i>16) : sa_port(addr); switch (sa_af(addr)) { case AF_INET: addr4 = tid ? sa_in(addr)^STUN_MAGIC_COOKIE : sa_in(addr); err |= mbuf_write_u8(mb, 0); err |= mbuf_write_u8(mb, STUN_AF_IPv4); err |= mbuf_write_u16(mb, htons(port)); err |= mbuf_write_u32(mb, htonl(addr4)); break; case AF_INET6: sa_in6(addr, addr6); if (tid) in6_xor_tid(addr6, tid); err |= mbuf_write_u8(mb, 0); err |= mbuf_write_u8(mb, STUN_AF_IPv6); err |= mbuf_write_u16(mb, htons(port)); err |= mbuf_write_mem(mb, addr6, 16); break; default: err = EAFNOSUPPORT; break; } return err; } int stun_addr_decode(struct mbuf *mb, struct sa *addr, const uint8_t *tid) { uint8_t family, addr6[16]; uint32_t addr4; uint16_t port; if (!mb || !addr) return EINVAL; if (mbuf_get_left(mb) < 4) return EBADMSG; (void)mbuf_read_u8(mb); family = mbuf_read_u8(mb); port = ntohs(mbuf_read_u16(mb)); if (tid) port ^= STUN_MAGIC_COOKIE>>16; switch (family) { case STUN_AF_IPv4: if (mbuf_get_left(mb) < 4) return EBADMSG; addr4 = ntohl(mbuf_read_u32(mb)); if (tid) addr4 ^= STUN_MAGIC_COOKIE; sa_set_in(addr, addr4, port); break; case STUN_AF_IPv6: if (mbuf_get_left(mb) < 16) return EBADMSG; (void)mbuf_read_mem(mb, addr6, 16); if (tid) in6_xor_tid(addr6, tid); sa_set_in6(addr, addr6, port); break; default: return EAFNOSUPPORT; } return 0; } ================================================ FILE: src/stun/attr.c ================================================ /** * @file stun/attr.c STUN Attributes * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include "stun.h" static int str_decode(struct mbuf *mb, char **str, size_t len) { if (mbuf_get_left(mb) < len) return EBADMSG; return mbuf_strdup(mb, str, len); } static void destructor(void *arg) { struct stun_attr *attr = arg; switch (attr->type) { case STUN_ATTR_USERNAME: case STUN_ATTR_REALM: case STUN_ATTR_NONCE: case STUN_ATTR_SOFTWARE: mem_deref(attr->v.str); break; case STUN_ATTR_ERR_CODE: mem_deref(attr->v.err_code.reason); break; case STUN_ATTR_DATA: mem_deref(attr->v.mb.buf); break; } list_unlink(&attr->le); } int stun_attr_encode(struct mbuf *mb, uint16_t type, const void *v, const uint8_t *tid, uint8_t padding) { const struct stun_change_req *ch_req = v; const struct stun_errcode *err_code = v; const struct stun_unknown_attr *ua = v; const uint32_t *num32 = v; const uint16_t *num16 = v; const uint8_t *num8 = v; const struct mbuf *mbd = v; size_t start, len; uint32_t i, n; int err = 0; if (!mb || !v) return EINVAL; mb->pos += 4; start = mb->pos; switch (type) { case STUN_ATTR_MAPPED_ADDR: case STUN_ATTR_ALT_SERVER: case STUN_ATTR_RESP_ORIGIN: case STUN_ATTR_OTHER_ADDR: tid = NULL; /*@fallthrough@*/ case STUN_ATTR_XOR_PEER_ADDR: case STUN_ATTR_XOR_RELAY_ADDR: case STUN_ATTR_XOR_MAPPED_ADDR: err |= stun_addr_encode(mb, v, tid); break; case STUN_ATTR_CHANGE_REQ: n = (uint32_t)ch_req->ip << 2 | (uint32_t)ch_req->port << 1; err |= mbuf_write_u32(mb, htonl(n)); break; case STUN_ATTR_USERNAME: case STUN_ATTR_REALM: case STUN_ATTR_NONCE: case STUN_ATTR_SOFTWARE: err |= mbuf_write_str(mb, v); break; case STUN_ATTR_MSG_INTEGRITY: err |= mbuf_write_mem(mb, v, 20); break; case STUN_ATTR_ERR_CODE: err |= mbuf_write_u16(mb, 0x00); err |= mbuf_write_u8(mb, err_code->code/100); err |= mbuf_write_u8(mb, err_code->code%100); err |= mbuf_write_str(mb, err_code->reason); break; case STUN_ATTR_UNKNOWN_ATTR: for (i=0; itypec; i++) err |= mbuf_write_u16(mb, htons(ua->typev[i])); break; case STUN_ATTR_CHANNEL_NUMBER: case STUN_ATTR_RESP_PORT: err |= mbuf_write_u16(mb, htons(*num16)); err |= mbuf_write_u16(mb, 0x0000); break; case STUN_ATTR_LIFETIME: case STUN_ATTR_PRIORITY: case STUN_ATTR_FINGERPRINT: err |= mbuf_write_u32(mb, htonl(*num32)); break; case STUN_ATTR_DATA: if (mb == mbd) { mb->pos = mb->end; break; } err |= mbuf_write_mem(mb, mbuf_buf(mbd), mbuf_get_left(mbd)); break; case STUN_ATTR_REQ_ADDR_FAMILY: case STUN_ATTR_REQ_TRANSPORT: err |= mbuf_write_u8(mb, *num8); err |= mbuf_write_u8(mb, 0x00); err |= mbuf_write_u16(mb, 0x0000); break; case STUN_ATTR_EVEN_PORT: err |= mbuf_write_u8(mb, ((struct stun_even_port *)v)->r << 7); break; case STUN_ATTR_DONT_FRAGMENT: case STUN_ATTR_USE_CAND: /* no value */ break; case STUN_ATTR_RSV_TOKEN: case STUN_ATTR_CONTROLLED: case STUN_ATTR_CONTROLLING: err |= mbuf_write_u64(mb, sys_htonll(*(uint64_t *)v)); break; default: err = EINVAL; break; } /* header */ len = mb->pos - start; mb->pos = start - 4; err |= mbuf_write_u16(mb, htons(type)); err |= mbuf_write_u16(mb, htons((uint16_t)len)); mb->pos += len; /* padding */ while ((mb->pos - start) & 0x03) err |= mbuf_write_u8(mb, padding); return err; } int stun_attr_decode(struct stun_attr **attrp, struct mbuf *mb, const uint8_t *tid, struct stun_unknown_attr *ua) { struct stun_attr *attr; size_t start, len; uint32_t i, n; int err = 0; if (!mb || !attrp) return EINVAL; if (mbuf_get_left(mb) < 4) return EBADMSG; attr = mem_zalloc(sizeof(*attr), destructor); if (!attr) return ENOMEM; attr->type = ntohs(mbuf_read_u16(mb)); len = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < len) goto badmsg; start = mb->pos; switch (attr->type) { case STUN_ATTR_MAPPED_ADDR: case STUN_ATTR_ALT_SERVER: case STUN_ATTR_RESP_ORIGIN: case STUN_ATTR_OTHER_ADDR: tid = NULL; /*@fallthrough@*/ case STUN_ATTR_XOR_PEER_ADDR: case STUN_ATTR_XOR_RELAY_ADDR: case STUN_ATTR_XOR_MAPPED_ADDR: err = stun_addr_decode(mb, &attr->v.sa, tid); break; case STUN_ATTR_CHANGE_REQ: if (len != 4) goto badmsg; n = ntohl(mbuf_read_u32(mb)); attr->v.change_req.ip = (n >> 2) & 0x1; attr->v.change_req.port = (n >> 1) & 0x1; break; case STUN_ATTR_USERNAME: case STUN_ATTR_REALM: case STUN_ATTR_NONCE: case STUN_ATTR_SOFTWARE: err = str_decode(mb, &attr->v.str, len); break; case STUN_ATTR_MSG_INTEGRITY: if (len != 20) goto badmsg; err = mbuf_read_mem(mb, attr->v.msg_integrity, 20); break; case STUN_ATTR_ERR_CODE: if (len < 4) goto badmsg; mb->pos += 2; attr->v.err_code.code = (mbuf_read_u8(mb) & 0x7) * 100; attr->v.err_code.code += mbuf_read_u8(mb); err = str_decode(mb, &attr->v.err_code.reason, len - 4); break; case STUN_ATTR_UNKNOWN_ATTR: for (i=0; i= RE_ARRAY_SIZE(attr->v.unknown_attr.typev)) continue; attr->v.unknown_attr.typev[i] = type; attr->v.unknown_attr.typec++; } break; case STUN_ATTR_CHANNEL_NUMBER: case STUN_ATTR_RESP_PORT: if (len < 2) goto badmsg; attr->v.uint16 = ntohs(mbuf_read_u16(mb)); break; case STUN_ATTR_LIFETIME: case STUN_ATTR_PRIORITY: case STUN_ATTR_FINGERPRINT: if (len != 4) goto badmsg; attr->v.uint32 = ntohl(mbuf_read_u32(mb)); break; case STUN_ATTR_DATA: attr->v.mb.buf = mem_ref(mb->buf); attr->v.mb.size = mb->size; attr->v.mb.pos = mb->pos; attr->v.mb.end = mb->pos + len; mb->pos += len; break; case STUN_ATTR_REQ_ADDR_FAMILY: case STUN_ATTR_REQ_TRANSPORT: if (len < 1) goto badmsg; attr->v.uint8 = mbuf_read_u8(mb); break; case STUN_ATTR_EVEN_PORT: if (len < 1) goto badmsg; attr->v.even_port.r = (mbuf_read_u8(mb) >> 7) & 0x1; break; case STUN_ATTR_DONT_FRAGMENT: case STUN_ATTR_USE_CAND: if (len > 0) goto badmsg; /* no value */ break; case STUN_ATTR_RSV_TOKEN: case STUN_ATTR_CONTROLLING: case STUN_ATTR_CONTROLLED: if (len != 8) goto badmsg; attr->v.uint64 = sys_ntohll(mbuf_read_u64(mb)); break; default: mb->pos += len; if (attr->type >= 0x8000) break; if (ua && ua->typec < RE_ARRAY_SIZE(ua->typev)) ua->typev[ua->typec++] = attr->type; break; } if (err) goto error; /* padding */ while (((mb->pos - start) & 0x03) && mbuf_get_left(mb)) ++mb->pos; *attrp = attr; return 0; badmsg: err = EBADMSG; error: mem_deref(attr); return err; } /** * Get the name of a STUN attribute * * @param type STUN attribute type * * @return String with attribute name */ const char *stun_attr_name(uint16_t type) { switch (type) { case STUN_ATTR_MAPPED_ADDR: return "MAPPED-ADDRESS"; case STUN_ATTR_CHANGE_REQ: return "CHANGE-REQUEST"; case STUN_ATTR_USERNAME: return "USERNAME"; case STUN_ATTR_MSG_INTEGRITY: return "MESSAGE-INTEGRITY"; case STUN_ATTR_ERR_CODE: return "ERROR-CODE"; case STUN_ATTR_UNKNOWN_ATTR: return "UNKNOWN-ATTRIBUTE"; case STUN_ATTR_CHANNEL_NUMBER: return "CHANNEL-NUMBER"; case STUN_ATTR_LIFETIME: return "LIFETIME"; case STUN_ATTR_XOR_PEER_ADDR: return "XOR-PEER-ADDRESS"; case STUN_ATTR_DATA: return "DATA"; case STUN_ATTR_REALM: return "REALM"; case STUN_ATTR_NONCE: return "NONCE"; case STUN_ATTR_XOR_RELAY_ADDR: return "XOR-RELAYED-ADDRESS"; case STUN_ATTR_REQ_ADDR_FAMILY: return "REQUESTED-ADDRESS-FAMILY"; case STUN_ATTR_EVEN_PORT: return "EVEN_PORT"; case STUN_ATTR_REQ_TRANSPORT: return "REQUESTED-TRANSPORT"; case STUN_ATTR_DONT_FRAGMENT: return "DONT-FRAGMENT"; case STUN_ATTR_XOR_MAPPED_ADDR: return "XOR-MAPPED-ADDRESS"; case STUN_ATTR_RSV_TOKEN: return "RESERVATION-TOKEN"; case STUN_ATTR_PRIORITY: return "PRIORITY"; case STUN_ATTR_USE_CAND: return "USE-CANDIDATE"; case STUN_ATTR_RESP_PORT: return "RESPONSE-PORT"; case STUN_ATTR_SOFTWARE: return "SOFTWARE"; case STUN_ATTR_ALT_SERVER: return "ALTERNATE-SERVER"; case STUN_ATTR_FINGERPRINT: return "FINGERPRINT"; case STUN_ATTR_CONTROLLING: return "ICE-CONTROLLING"; case STUN_ATTR_CONTROLLED: return "ICE-CONTROLLED"; case STUN_ATTR_RESP_ORIGIN: return "RESPONSE-ORIGIN"; case STUN_ATTR_OTHER_ADDR: return "OTHER-ADDR"; default: return "???"; } } void stun_attr_dump(const struct stun_attr *a) { uint32_t i; size_t len; if (!a) return; (void)re_printf(" %-25s", stun_attr_name(a->type)); switch (a->type) { case STUN_ATTR_MAPPED_ADDR: case STUN_ATTR_XOR_PEER_ADDR: case STUN_ATTR_XOR_RELAY_ADDR: case STUN_ATTR_XOR_MAPPED_ADDR: case STUN_ATTR_ALT_SERVER: case STUN_ATTR_RESP_ORIGIN: case STUN_ATTR_OTHER_ADDR: (void)re_printf("%J", &a->v.sa); break; case STUN_ATTR_CHANGE_REQ: (void)re_printf("ip=%u port=%u", a->v.change_req.ip, a->v.change_req.port); break; case STUN_ATTR_USERNAME: case STUN_ATTR_REALM: case STUN_ATTR_NONCE: case STUN_ATTR_SOFTWARE: (void)re_printf("%s", a->v.str); break; case STUN_ATTR_MSG_INTEGRITY: (void)re_printf("%w", a->v.msg_integrity, sizeof(a->v.msg_integrity)); break; case STUN_ATTR_ERR_CODE: (void)re_printf("%u %s", a->v.err_code.code, a->v.err_code.reason); break; case STUN_ATTR_UNKNOWN_ATTR: for (i=0; iv.unknown_attr.typec; i++) (void)re_printf("0x%04x ", a->v.unknown_attr.typev[i]); break; case STUN_ATTR_CHANNEL_NUMBER: (void)re_printf("0x%04x", a->v.uint16); break; case STUN_ATTR_LIFETIME: case STUN_ATTR_PRIORITY: (void)re_printf("%u", a->v.uint32); break; case STUN_ATTR_DATA: len = min(mbuf_get_left(&a->v.mb), 16); (void)re_printf("%w%s (%zu bytes)", mbuf_buf(&a->v.mb), len, mbuf_get_left(&a->v.mb) > 16 ? "..." : "", mbuf_get_left(&a->v.mb)); break; case STUN_ATTR_REQ_ADDR_FAMILY: case STUN_ATTR_REQ_TRANSPORT: (void)re_printf("%u", a->v.uint8); break; case STUN_ATTR_EVEN_PORT: (void)re_printf("r=%u", a->v.even_port.r); break; case STUN_ATTR_DONT_FRAGMENT: case STUN_ATTR_USE_CAND: /* no value */ break; case STUN_ATTR_RSV_TOKEN: (void)re_printf("0x%016llx", a->v.rsv_token); break; case STUN_ATTR_RESP_PORT: (void)re_printf("%u", a->v.uint16); break; case STUN_ATTR_FINGERPRINT: (void)re_printf("0x%08x", a->v.fingerprint); break; case STUN_ATTR_CONTROLLING: case STUN_ATTR_CONTROLLED: (void)re_printf("%llu", a->v.uint64); break; default: (void)re_printf("???"); break; } (void)re_printf("\n"); } ================================================ FILE: src/stun/ctrans.c ================================================ /** * @file stun/ctrans.c STUN Client transactions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "stun.h" struct stun_ctrans { struct le le; struct tmr tmr; struct sa dst; uint8_t tid[STUN_TID_SIZE]; struct stun_ctrans **ctp; uint8_t *key; size_t keylen; void *sock; struct mbuf *mb; size_t pos; struct stun *stun; stun_resp_h *resph; void *arg; int proto; uint32_t txc; uint32_t ival; uint16_t met; }; static void completed(struct stun_ctrans *ct, int err, uint16_t scode, const char *reason, const struct stun_msg *msg) { stun_resp_h *resph = ct->resph; void *arg = ct->arg; list_unlink(&ct->le); tmr_cancel(&ct->tmr); if (ct->ctp) { *ct->ctp = NULL; ct->ctp = NULL; } ct->resph = NULL; /* must be destroyed before calling handler */ mem_deref(ct); if (resph) resph(err, scode, reason, msg, arg); } static void destructor(void *arg) { struct stun_ctrans *ct = arg; list_unlink(&ct->le); tmr_cancel(&ct->tmr); mem_deref(ct->key); mem_deref(ct->sock); mem_deref(ct->mb); } static void timeout_handler(void *arg) { struct stun_ctrans *ct = arg; const struct stun_conf *cfg = stun_conf(ct->stun); int err = ETIMEDOUT; if (ct->txc++ >= cfg->rc) goto error; ct->mb->pos = ct->pos; err = stun_send(ct->proto, ct->sock, &ct->dst, ct->mb); if (err) goto error; ct->ival = (ct->txc >= cfg->rc) ? cfg->rto * cfg->rm : ct->ival * 2; tmr_start(&ct->tmr, ct->ival, timeout_handler, ct); return; error: completed(ct, err, 0, NULL, NULL); } static bool match_handler(struct le *le, void *arg) { struct stun_ctrans *ct = le->data; struct stun_msg *msg = arg; if (ct->met != stun_msg_method(msg)) return false; if (memcmp(ct->tid, stun_msg_tid(msg), STUN_TID_SIZE)) return false; return true; } static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { struct stun *stun = arg; (void)src; (void)stun_recv(stun, mb); } static void tcp_recv_handler(struct mbuf *mb, void *arg) { struct stun_ctrans *ct = arg; (void)stun_recv(ct->stun, mb); } static void tcp_estab_handler(void *arg) { struct stun_ctrans *ct = arg; int err; err = tcp_send(ct->sock, ct->mb); if (!err) return; completed(ct, err, 0, NULL, NULL); } static void tcp_close_handler(int err, void *arg) { struct stun_ctrans *ct = arg; completed(ct, err, 0, NULL, NULL); } /** * Handle an incoming STUN message to a Client Transaction * * @param stun STUN instance * @param msg STUN message * @param ua Unknown attributes * * @return 0 if success, otherwise errorcode */ int stun_ctrans_recv(struct stun *stun, const struct stun_msg *msg, const struct stun_unknown_attr *ua) { struct stun_errcode ec = {0, "OK"}; struct stun_attr *errcode; struct stun_ctrans *ct; int err = 0, herr = 0; if (!stun || !msg || !ua) return EINVAL; switch (stun_msg_class(msg)) { case STUN_CLASS_ERROR_RESP: errcode = stun_msg_attr(msg, STUN_ATTR_ERR_CODE); if (!errcode) herr = EPROTO; else ec = errcode->v.err_code; /*@fallthrough@*/ case STUN_CLASS_SUCCESS_RESP: ct = list_ledata(list_apply(&stun->ctl, true, match_handler, (void *)msg)); if (!ct) { err = ENOENT; break; } switch (ec.code) { case 401: case 438: break; default: if (!ct->key) break; err = stun_msg_chk_mi(msg, ct->key, ct->keylen); break; } if (err) break; if (!herr && ua->typec > 0) herr = EPROTO; completed(ct, herr, ec.code, ec.reason, msg); break; default: break; } return err; } int stun_ctrans_request(struct stun_ctrans **ctp, struct stun *stun, int proto, void *sock, const struct sa *dst, struct mbuf *mb, const uint8_t tid[], uint16_t met, const uint8_t *key, size_t keylen, stun_resp_h *resph, void *arg) { struct stun_ctrans *ct; int err = 0; if (!stun || !mb) return EINVAL; ct = mem_zalloc(sizeof(*ct), destructor); if (!ct) return ENOMEM; list_append(&stun->ctl, &ct->le, ct); memcpy(ct->tid, tid, STUN_TID_SIZE); ct->proto = proto; ct->sock = mem_ref(sock); ct->mb = mem_ref(mb); ct->pos = mb->pos; ct->stun = stun; ct->met = met; if (key) { ct->key = mem_alloc(keylen, NULL); if (!ct->key) { err = ENOMEM; goto out; } memcpy(ct->key, key, keylen); ct->keylen = keylen; } switch (proto) { case IPPROTO_UDP: if (!dst) { err = EINVAL; break; } ct->dst = *dst; ct->ival = stun_conf(stun)->rto; tmr_start(&ct->tmr, ct->ival, timeout_handler, ct); if (!sock) { err = udp_listen((struct udp_sock **)&ct->sock, NULL, udp_recv_handler, stun); if (err) break; } ct->txc = 1; err = udp_send(ct->sock, dst, mb); break; case IPPROTO_TCP: ct->txc = stun_conf(stun)->rc; tmr_start(&ct->tmr, stun_conf(stun)->ti, timeout_handler, ct); if (sock) { err = tcp_send(sock, mb); break; } err = tcp_connect((struct tcp_conn **)&ct->sock, dst, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, ct); break; #ifdef USE_DTLS case STUN_TRANSP_DTLS: if (!sock) { err = EINVAL; break; } ct->ival = stun_conf(stun)->rto; tmr_start(&ct->tmr, ct->ival, timeout_handler, ct); ct->txc = 1; err = dtls_send(ct->sock, mb); break; #endif default: err = EPROTONOSUPPORT; break; } out: if (!err) { if (ctp) { ct->ctp = ctp; *ctp = ct; } ct->resph = resph; ct->arg = arg; } else mem_deref(ct); return err; } static bool close_handler(struct le *le, void *arg) { struct stun_ctrans *ct = le->data; (void)arg; completed(ct, ECONNABORTED, 0, NULL, NULL); return false; } void stun_ctrans_close(struct stun *stun) { if (!stun) return; (void)list_apply(&stun->ctl, true, close_handler, NULL); } static bool debug_handler(struct le *le, void *arg) { struct stun_ctrans *ct = le->data; struct re_printf *pf = arg; int err = 0; err |= re_hprintf(pf, " method=%s", stun_method_name(ct->met)); err |= re_hprintf(pf, " tid=%w", ct->tid, sizeof(ct->tid)); err |= re_hprintf(pf, " rto=%ums", stun_conf(ct->stun)->rto); err |= re_hprintf(pf, " tmr=%llu", tmr_get_expire(&ct->tmr)); err |= re_hprintf(pf, " n=%u", ct->txc); err |= re_hprintf(pf, " interval=%u", ct->ival); err |= re_hprintf(pf, "\n"); return 0 != err; } int stun_ctrans_debug(struct re_printf *pf, const struct stun *stun) { int err; if (!stun) return 0; err = re_hprintf(pf, "STUN client transactions: (%u)\n", list_count(&stun->ctl)); (void)list_apply(&stun->ctl, true, debug_handler, pf); return err; } ================================================ FILE: src/stun/dnsdisc.c ================================================ /** * @file dnsdisc.c DNS Discovery of a STUN Server * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #define DEBUG_MODULE "dnsdisc" #define DEBUG_LEVEL 5 #include /** DNS Query */ struct stun_dns { char domain[256]; /**< Cached domain name */ stun_dns_h *dnsh; /**< DNS Response handler */ void *arg; /**< Handler argument */ struct sa srv; /**< Resolved server address */ struct dnsc *dnsc; /**< DNS Client */ struct dns_query *dq; /**< Current DNS query */ int af; /**< Preferred Address family*/ uint16_t port; /**< Default Port */ }; const char *stun_proto_udp = "udp"; /**< UDP Protocol */ const char *stun_proto_tcp = "tcp"; /**< TCP Protocol */ const char *stun_usage_binding = "stun"; /**< Binding usage */ const char *stuns_usage_binding = "stuns"; /**< Binding usage TLS */ const char *stun_usage_relay = "turn"; const char *stuns_usage_relay = "turns"; static void resolved(const struct stun_dns *dns, int err) { stun_dns_h *dnsh = dns->dnsh; void *dnsh_arg = dns->arg; DEBUG_INFO("resolved: %J (%m)\n", &dns->srv, err); dnsh(err, &dns->srv, dnsh_arg); } static void a_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct stun_dns *dns = arg; struct dnsrr *rr; (void)hdr; (void)authl; (void)addl; /* Find A answers */ rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_A, DNS_CLASS_IN, false); if (!rr) { err = err ? err : EDESTADDRREQ; goto out; } sa_set_in(&dns->srv, rr->rdata.a.addr, sa_port(&dns->srv)); DEBUG_INFO("A answer: %j\n", &dns->srv); out: resolved(dns, err); } static void aaaa_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct stun_dns *dns = arg; struct dnsrr *rr; (void)hdr; (void)authl; (void)addl; /* Find A answers */ rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_AAAA, DNS_CLASS_IN, false); if (!rr) { err = err ? err : EDESTADDRREQ; goto out; } sa_set_in6(&dns->srv, rr->rdata.aaaa.addr, sa_port(&dns->srv)); DEBUG_INFO("AAAA answer: %j\n", &dns->srv); out: resolved(dns, err); } static int a_or_aaaa_query(struct stun_dns *dns, const char *name) { dns->dq = mem_deref(dns->dq); switch (dns->af) { case AF_INET: return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_A, DNS_CLASS_IN, true, a_handler, dns); case AF_INET6: return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_AAAA, DNS_CLASS_IN, true, aaaa_handler, dns); default: return EAFNOSUPPORT; } } static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct stun_dns *dns = arg; struct dnsrr *rr, *arr; (void)hdr; (void)authl; dns_rrlist_sort(ansl, DNS_TYPE_SRV, (size_t)dns->arg); /* Find SRV answers */ rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false); if (!rr) { DEBUG_INFO("no SRV entry, trying A lookup on \"%s\"\n", dns->domain); sa_set_in(&dns->srv, 0, dns->port); err = a_or_aaaa_query(dns, dns->domain); if (err) goto out; return; } DEBUG_INFO("SRV answer: %s:%u\n", rr->rdata.srv.target, rr->rdata.srv.port); /* Look for Additional information */ switch (dns->af) { case AF_INET: arr = dns_rrlist_find(addl, rr->rdata.srv.target, DNS_TYPE_A, DNS_CLASS_IN, true); if (arr) { sa_set_in(&dns->srv, arr->rdata.a.addr, rr->rdata.srv.port); DEBUG_INFO("additional A: %j\n", &dns->srv); goto out; } break; case AF_INET6: arr = dns_rrlist_find(addl, rr->rdata.srv.target, DNS_TYPE_AAAA, DNS_CLASS_IN, true); if (arr) { sa_set_in6(&dns->srv, arr->rdata.aaaa.addr, rr->rdata.srv.port); DEBUG_INFO("additional AAAA: %j\n", &dns->srv); goto out; } break; } sa_set_in(&dns->srv, 0, rr->rdata.srv.port); err = a_or_aaaa_query(dns, rr->rdata.srv.target); if (err) { DEBUG_WARNING("SRV: A lookup failed (%m)\n", err); goto out; } DEBUG_INFO("SRV handler: doing A/AAAA lookup..\n"); return; out: resolved(dns, err); } static void dnsdisc_destructor(void *data) { struct stun_dns *dns = data; mem_deref(dns->dq); } /** * Do a DNS Discovery of a STUN Server * * @param dnsp Pointer to allocated DNS Discovery object * @param dnsc DNS Client * @param service Name of service to discover (e.g. "stun") * @param proto Transport protocol (e.g. "udp") * @param af Preferred Address Family * @param domain Domain name or IP address of STUN server * @param port Port number (if 0 do SRV lookup) * @param dnsh DNS Response handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc, const char *service, const char *proto, int af, const char *domain, uint16_t port, stun_dns_h *dnsh, void *arg) { struct stun_dns *dns; int err; if (!dnsp || !service || !proto || !domain || !domain[0] || !dnsh) return EINVAL; dns = mem_zalloc(sizeof(*dns), dnsdisc_destructor); if (!dns) return ENOMEM; dns->port = service[strlen(service)-1] == 's' ? STUNS_PORT : STUN_PORT; dns->dnsh = dnsh; dns->arg = arg; dns->dnsc = dnsc; dns->af = af; /* Numeric IP address - no lookup */ if (0 == sa_set_str(&dns->srv, domain, port ? port : dns->port)) { DEBUG_INFO("IP (%s)\n", domain); resolved(dns, 0); err = 0; goto out; /* free now */ } /* Port specified - use AAAA or A lookup */ else if (port) { sa_set_in(&dns->srv, 0, port); DEBUG_INFO("resolving A query: (%s)\n", domain); err = a_or_aaaa_query(dns, domain); if (err) { DEBUG_WARNING("%s: A/AAAA lookup failed (%m)\n", domain, err); goto out; } } /* SRV lookup */ else { char q[256]; str_ncpy(dns->domain, domain, sizeof(dns->domain)); (void)re_snprintf(q, sizeof(q), "_%s._%s.%s", service, proto, domain); DEBUG_INFO("resolving SRV query: (%s)\n", q); err = dnsc_query(&dns->dq, dnsc, q, DNS_TYPE_SRV, DNS_CLASS_IN, true, srv_handler, dns); if (err) { DEBUG_WARNING("%s: SRV lookup failed (%m)\n", q, err); goto out; } } *dnsp = dns; return 0; out: mem_deref(dns); return err; } ================================================ FILE: src/stun/hdr.c ================================================ /** * @file stun/hdr.c STUN Header encoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include "stun.h" int stun_hdr_encode(struct mbuf *mb, const struct stun_hdr *hdr) { int err = 0; if (!mb || !hdr) return EINVAL; err |= mbuf_write_u16(mb, htons(hdr->type & 0x3fff)); err |= mbuf_write_u16(mb, htons(hdr->len)); err |= mbuf_write_u32(mb, htonl(hdr->cookie)); err |= mbuf_write_mem(mb, hdr->tid, sizeof(hdr->tid)); return err; } int stun_hdr_decode(struct mbuf *mb, struct stun_hdr *hdr) { if (!mb || !hdr) return EINVAL; if (mbuf_get_left(mb) < STUN_HEADER_SIZE) return EBADMSG; hdr->type = ntohs(mbuf_read_u16(mb)); if (hdr->type & 0xc000) return EBADMSG; hdr->len = ntohs(mbuf_read_u16(mb)); if (hdr->len & 0x3) return EBADMSG; hdr->cookie = ntohl(mbuf_read_u32(mb)); (void)mbuf_read_mem(mb, hdr->tid, sizeof(hdr->tid)); if (mbuf_get_left(mb) < hdr->len) return EBADMSG; return 0; } const char *stun_class_name(uint16_t class) { switch (class) { case STUN_CLASS_REQUEST: return "Request"; case STUN_CLASS_INDICATION: return "Indication"; case STUN_CLASS_SUCCESS_RESP: return "Success Response"; case STUN_CLASS_ERROR_RESP: return "Error Response"; default: return "???"; } } const char *stun_method_name(uint16_t method) { switch (method) { case STUN_METHOD_BINDING: return "Binding"; case STUN_METHOD_ALLOCATE: return "Allocate"; case STUN_METHOD_REFRESH: return "Refresh"; case STUN_METHOD_SEND: return "Send"; case STUN_METHOD_DATA: return "Data"; case STUN_METHOD_CREATEPERM: return "CreatePermission"; case STUN_METHOD_CHANBIND: return "ChannelBind"; default: return "???"; } } void stun_generate_tid(uint8_t tid[STUN_TID_SIZE]) { rand_bytes(tid, STUN_TID_SIZE); } ================================================ FILE: src/stun/ind.c ================================================ /** * @file ind.c STUN Indication * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include "stun.h" /** * Send a STUN Indication message * * @param proto Transport Protocol * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn) * @param dst Destination network address * @param presz Number of bytes in preamble, if sending over TURN * @param method STUN Method * @param key Authentication key (optional) * @param keylen Number of bytes in authentication key * @param fp Use STUN Fingerprint attribute * @param attrc Number of attributes to encode (variable arguments) * @param ... Variable list of attribute-tuples * Each attribute has 2 arguments, attribute type and value * * @return 0 if success, otherwise errorcode */ int stun_indication(int proto, void *sock, const struct sa *dst, size_t presz, uint16_t method, const uint8_t *key, size_t keylen, bool fp, uint32_t attrc, ...) { uint8_t tid[STUN_TID_SIZE]; struct mbuf *mb; va_list ap; int err; if (!sock) return EINVAL; mb = mbuf_alloc(2048); if (!mb) return ENOMEM; stun_generate_tid(tid); va_start(ap, attrc); mb->pos = presz; err = stun_msg_vencode(mb, method, STUN_CLASS_INDICATION, tid, NULL, key, keylen, fp, 0x00, attrc, ap); va_end(ap); if (err) goto out; mb->pos = presz; err = stun_send(proto, sock, dst, mb); out: mem_deref(mb); return err; } ================================================ FILE: src/stun/keepalive.c ================================================ /** * @file stun/keepalive.c STUN usage for NAT Keepalives * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #define DEBUG_MODULE "keepalive" #define DEBUG_LEVEL 5 #include /** Defines a STUN Keepalive session */ struct stun_keepalive { struct stun_ctrans *ct; /**< STUN client transaction */ struct stun *stun; /**< STUN instance */ struct udp_helper *uh; /**< UDP Helper */ int proto; /**< Transport protocol */ void *sock; /**< Transport Socket */ struct sa dst; /**< Destination network address */ struct tmr tmr; /**< Refresh timer */ uint32_t interval; /**< Refresh interval in seconds */ stun_mapped_addr_h *mah; /**< Mapped address handler */ void *arg; /**< Handler argument */ struct sa curmap; /**< Currently mapped IP address and port */ }; static void timeout(void *arg); static void keepalive_destructor(void *data) { struct stun_keepalive *ska = data; tmr_cancel(&ska->tmr); mem_deref(ska->ct); mem_deref(ska->uh); mem_deref(ska->sock); mem_deref(ska->stun); } static void call_handler(struct stun_keepalive *ska, int err, const struct sa *map) { if (ska->mah) ska->mah(err, map, ska->arg); } static void stun_response_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct stun_keepalive *ska = arg; struct stun_attr *attr; (void)reason; /* Restart timer */ if (ska->interval > 0) tmr_start(&ska->tmr, ska->interval*1000, timeout, ska); if (err || scode) { /* Clear current mapped addr to force new notification */ sa_set_in(&ska->curmap, 0, 0); goto out; } attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr) attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR); if (!attr) { err = ENOENT; goto out; } if (!sa_cmp(&ska->curmap, &attr->v.sa, SA_ALL)) { ska->curmap = attr->v.sa; call_handler(ska, 0, &ska->curmap); } out: if (err) call_handler(ska, err, NULL); } static void timeout(void *arg) { struct stun_keepalive *ska = arg; int err; if (ska->ct) ska->ct = mem_deref(ska->ct); err = stun_request(&ska->ct, ska->stun, ska->proto, ska->sock, &ska->dst, 0, STUN_METHOD_BINDING, NULL, 0, false, stun_response_handler, ska, 1, STUN_ATTR_SOFTWARE, stun_software); if (0 == err) return; /* Restart timer */ if (ska->interval > 0) tmr_start(&ska->tmr, ska->interval*1000, timeout, ska); /* Error */ call_handler(ska, err, NULL); } static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg) { struct stun_keepalive *ska = arg; struct stun_unknown_attr ua; struct stun_msg *msg; size_t pos = mb->pos; bool hdld; if (!sa_cmp(&ska->dst, src, SA_ALL)) return false; if (stun_msg_decode(&msg, mb, &ua)) return false; if (stun_msg_method(msg) != STUN_METHOD_BINDING) { hdld = false; mb->pos = pos; goto out; } switch (stun_msg_class(msg)) { case STUN_CLASS_ERROR_RESP: case STUN_CLASS_SUCCESS_RESP: (void)stun_ctrans_recv(ska->stun, msg, &ua); hdld = true; break; default: hdld = false; mb->pos = pos; break; } out: mem_deref(msg); return hdld; } /** * Allocate a new STUN keepalive session * * @param skap Pointer to keepalive object * @param proto Transport protocol * @param sock Socket * @param layer Protocol layer * @param dst Destination address * @param conf Configuration * @param mah Mapped address handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int stun_keepalive_alloc(struct stun_keepalive **skap, int proto, void *sock, int layer, const struct sa *dst, const struct stun_conf *conf, stun_mapped_addr_h *mah, void *arg) { struct stun_keepalive *ska; int err; if (!skap) return EINVAL; ska = mem_zalloc(sizeof(*ska), keepalive_destructor); if (!ska) return ENOMEM; err = stun_alloc(&ska->stun, conf, NULL, NULL); if (err) goto out; tmr_init(&ska->tmr); ska->proto = proto; ska->sock = mem_ref(sock); ska->mah = mah; ska->arg = arg; if (dst) ska->dst = *dst; switch (proto) { case IPPROTO_UDP: err = udp_register_helper(&ska->uh, sock, layer, NULL, udp_recv_handler, ska); break; default: err = 0; break; } out: if (err) mem_deref(ska); else *skap = ska; return err; } /** * Enable or disable keepalive timer * * @param ska Keepalive object * @param interval Interval in seconds (0 to disable) */ void stun_keepalive_enable(struct stun_keepalive *ska, uint32_t interval) { if (!ska) return; ska->interval = interval; tmr_cancel(&ska->tmr); if (interval > 0) tmr_start(&ska->tmr, 1, timeout, ska); } ================================================ FILE: src/stun/msg.c ================================================ /** * @file stun/msg.c STUN message encoding * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "stun.h" enum { MI_SIZE = 24, FP_SIZE = 8 }; /** Defines a STUN Message object

   .---------------------.      /|\           /|\
   | STUN Header         |       |             |
   |---------------------|       |             |
   |         ....        |       |--------.    |
   |      N Attributes   |       |        |    |----.
   |         ....        |      \|/       |    |    |
   |---------------------|                |    |    |
   |  MESSAGE-INTEGRITY  | <-(HMAC-SHA1)--'   \|/   |
   |---------------------|                          |
   |     FINGERPRINT     | <-(CRC-32)---------------'
   '---------------------'
   
*/ struct stun_msg { struct stun_hdr hdr; struct list attrl; struct mbuf *mb; size_t start; }; static uint32_t fingerprint(const uint8_t *buf, size_t len) { return re_crc32(0, buf, (unsigned int)len) ^ 0x5354554e; } static void destructor(void *arg) { struct stun_msg *msg = arg; list_flush(&msg->attrl); mem_deref(msg->mb); } /** * Decode a buffer to a STUN Message * * @param msgpp Pointer to allocation STUN message * @param mb Buffer containing the raw STUN packet * @param ua Unknown attributes (optional) * * @return 0 if success, otherwise errorcode * * @note `mb` will be referenced */ int stun_msg_decode(struct stun_msg **msgpp, struct mbuf *mb, struct stun_unknown_attr *ua) { struct stun_msg *msg; struct stun_hdr hdr; size_t start, extra; int err; if (!msgpp || !mb) return EINVAL; start = mb->pos; err = stun_hdr_decode(mb, &hdr); if (err) { mb->pos = start; return err; } msg = mem_zalloc(sizeof(*msg), destructor); if (!msg) { mb->pos = start; return ENOMEM; } msg->hdr = hdr; msg->mb = mem_ref(mb); msg->start = start; if (ua) ua->typec = 0; /* mbuf_get_left(mb) >= hdr.len checked in stun_hdr_decode() above */ extra = mbuf_get_left(mb) - hdr.len; while (mbuf_get_left(mb) - extra >= 4) { struct stun_attr *attr; err = stun_attr_decode(&attr, mb, hdr.tid, ua); if (err) break; list_append(&msg->attrl, &attr->le, attr); } if (err) mem_deref(msg); else *msgpp = msg; mb->pos = start; return err; } /** * Get the STUN message type * * @param msg STUN Message * * @return STUN Message type */ uint16_t stun_msg_type(const struct stun_msg *msg) { return msg ? msg->hdr.type : 0; } /** * Get the STUN message class * * @param msg STUN Message * * @return STUN Message class */ uint16_t stun_msg_class(const struct stun_msg *msg) { return STUN_CLASS(stun_msg_type(msg)); } /** * Get the STUN message method * * @param msg STUN Message * * @return STUN Message method */ uint16_t stun_msg_method(const struct stun_msg *msg) { return STUN_METHOD(stun_msg_type(msg)); } /** * Get the STUN message Transaction-ID * * @param msg STUN Message * * @return STUN Message Transaction-ID */ const uint8_t *stun_msg_tid(const struct stun_msg *msg) { return msg ? msg->hdr.tid : NULL; } /** * Check if a STUN Message has the magic cookie * * @param msg STUN Message * * @return true if Magic Cookie, otherwise false */ bool stun_msg_mcookie(const struct stun_msg *msg) { return msg && (STUN_MAGIC_COOKIE == msg->hdr.cookie); } /** * Lookup a STUN attribute in a STUN message * * @param msg STUN Message * @param type STUN Attribute type * * @return STUN Attribute if found, otherwise NULL */ struct stun_attr *stun_msg_attr(const struct stun_msg *msg, uint16_t type) { struct le *le = msg ? list_head(&msg->attrl) : NULL; while (le) { struct stun_attr *attr = le->data; le = le->next; if (attr->type == type) return attr; } return NULL; } /** * Apply a function handler to all STUN attribute * * @param msg STUN Message * @param h Attribute handler * @param arg Handler argument * * @return STUN attribute if handler returned true, otherwise NULL */ struct stun_attr *stun_msg_attr_apply(const struct stun_msg *msg, stun_attr_h *h, void *arg) { struct le *le = msg ? list_head(&msg->attrl) : NULL; while (le) { struct stun_attr *attr = le->data; le = le->next; if (h && h(attr, arg)) return (attr); } return NULL; } /** * Encode a STUN message * * @param mb Buffer to encode message into * @param method STUN Method * @param class STUN Method class * @param tid Transaction ID * @param ec STUN error code (optional) * @param key Authentication key (optional) * @param keylen Number of bytes in authentication key * @param fp Use STUN Fingerprint attribute * @param padding Padding byte * @param attrc Number of attributes to encode (variable arguments) * @param ap Variable list of attribute-tuples * Each attribute has 2 arguments, attribute type and value * * @return 0 if success, otherwise errorcode */ int stun_msg_vencode(struct mbuf *mb, uint16_t method, uint8_t class, const uint8_t *tid, const struct stun_errcode *ec, const uint8_t *key, size_t keylen, bool fp, uint8_t padding, uint32_t attrc, va_list ap) { struct stun_hdr hdr; size_t start, len; int err = 0; uint32_t i; if (!mb || !tid) return EINVAL; start = mb->pos; mb->pos += STUN_HEADER_SIZE; hdr.type = STUN_TYPE(method, class); hdr.cookie = STUN_MAGIC_COOKIE; memcpy(hdr.tid, tid, STUN_TID_SIZE); if (ec) err |= stun_attr_encode(mb, STUN_ATTR_ERR_CODE, ec, NULL, padding); for (i=0; ipos - start - STUN_HEADER_SIZE + (key ? MI_SIZE : 0); hdr.len = (uint16_t)len; mb->pos = start; err |= stun_hdr_encode(mb, &hdr); mb->pos += hdr.len - (key ? MI_SIZE : 0); if (key) { uint8_t mi[20]; mb->pos = start; hmac_sha1(key, keylen, mbuf_buf(mb), mbuf_get_left(mb), mi, sizeof(mi)); mb->pos += STUN_HEADER_SIZE + hdr.len - MI_SIZE; err |= stun_attr_encode(mb, STUN_ATTR_MSG_INTEGRITY, mi, NULL, padding); } if (fp) { uint32_t fprnt; /* header */ len = mb->pos - start - STUN_HEADER_SIZE + FP_SIZE; hdr.len = (uint16_t)len; mb->pos = start; err |= stun_hdr_encode(mb, &hdr); mb->pos = start; fprnt = fingerprint(mbuf_buf(mb), mbuf_get_left(mb)); mb->pos += STUN_HEADER_SIZE + hdr.len - FP_SIZE; err |= stun_attr_encode(mb, STUN_ATTR_FINGERPRINT, &fprnt, NULL, padding); } return err; } /** * Encode a STUN message * * @param mb Buffer to encode message into * @param method STUN Method * @param class STUN Method class * @param tid Transaction ID * @param ec STUN error code (optional) * @param key Authentication key (optional) * @param keylen Number of bytes in authentication key * @param fp Use STUN Fingerprint attribute * @param padding Padding byte * @param attrc Number of attributes to encode (variable arguments) * @param ... Variable list of attribute-tuples * Each attribute has 2 arguments, attribute type and value * * @return 0 if success, otherwise errorcode */ int stun_msg_encode(struct mbuf *mb, uint16_t method, uint8_t class, const uint8_t *tid, const struct stun_errcode *ec, const uint8_t *key, size_t keylen, bool fp, uint8_t padding, uint32_t attrc, ...) { va_list ap; int err; va_start(ap, attrc); err = stun_msg_vencode(mb, method, class, tid, ec, key, keylen, fp, padding, attrc, ap); va_end(ap); return err; } /** * Verify the Message-Integrity of a STUN message * * @param msg STUN Message * @param key Authentication key * @param keylen Number of bytes in authentication key * * @return 0 if verified, otherwise errorcode */ int stun_msg_chk_mi(const struct stun_msg *msg, const uint8_t *key, size_t keylen) { uint8_t hmac[SHA_DIGEST_LENGTH] = {0}; struct stun_attr *mi, *fp; if (!msg) return EINVAL; mi = stun_msg_attr(msg, STUN_ATTR_MSG_INTEGRITY); if (!mi) return EPROTO; msg->mb->pos = msg->start; fp = stun_msg_attr(msg, STUN_ATTR_FINGERPRINT); if (fp) { ((struct stun_msg *)msg)->hdr.len -= FP_SIZE; (void)stun_hdr_encode(msg->mb, &msg->hdr); msg->mb->pos -= STUN_HEADER_SIZE; } hmac_sha1(key, keylen, mbuf_buf(msg->mb), STUN_HEADER_SIZE + msg->hdr.len - MI_SIZE, hmac, sizeof(hmac)); if (fp) { ((struct stun_msg *)msg)->hdr.len += FP_SIZE; (void)stun_hdr_encode(msg->mb, &msg->hdr); msg->mb->pos -= STUN_HEADER_SIZE; } if (memcmp(mi->v.msg_integrity, hmac, SHA_DIGEST_LENGTH)) return EBADMSG; return 0; } /** * Check the Fingerprint of a STUN message * * @param msg STUN Message * * @return 0 if fingerprint matches, otherwise errorcode */ int stun_msg_chk_fingerprint(const struct stun_msg *msg) { struct stun_attr *fp; uint32_t fprnt; if (!msg) return EINVAL; fp = stun_msg_attr(msg, STUN_ATTR_FINGERPRINT); if (!fp) return EPROTO; msg->mb->pos = msg->start; fprnt = fingerprint(mbuf_buf(msg->mb), STUN_HEADER_SIZE + msg->hdr.len - FP_SIZE); if (fprnt != fp->v.fingerprint) return EBADMSG; return 0; } static bool attr_print(const struct stun_attr *attr, void *arg) { (void)arg; stun_attr_dump(attr); return false; } /** * Print a STUN message to STDOUT * * @param msg STUN Message */ void stun_msg_dump(const struct stun_msg *msg) { if (!msg) return; (void)re_printf("%s %s (len=%u cookie=%08x tid=%w)\n", stun_method_name(stun_msg_method(msg)), stun_class_name(stun_msg_class(msg)), msg->hdr.len, msg->hdr.cookie, msg->hdr.tid, sizeof(msg->hdr.tid)); stun_msg_attr_apply(msg, attr_print, NULL); } ================================================ FILE: src/stun/rep.c ================================================ /** * @file stun/rep.c STUN reply * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include "stun.h" /** * Send a STUN response message * * @param proto Transport Protocol * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn) * @param dst Destination network address * @param presz Number of bytes in preamble, if sending over TURN * @param req Matching STUN request * @param key Authentication key (optional) * @param keylen Number of bytes in authentication key * @param fp Use STUN Fingerprint attribute * @param attrc Number of attributes to encode (variable arguments) * @param ... Variable list of attribute-tuples * Each attribute has 2 arguments, attribute type and value * * @return 0 if success, otherwise errorcode */ int stun_reply(int proto, void *sock, const struct sa *dst, size_t presz, const struct stun_msg *req, const uint8_t *key, size_t keylen, bool fp, uint32_t attrc, ...) { struct mbuf *mb = NULL; int err = ENOMEM; va_list ap; if (!sock || !req) return EINVAL; mb = mbuf_alloc(256); if (!mb) goto out; va_start(ap, attrc); mb->pos = presz; err = stun_msg_vencode(mb, stun_msg_method(req), STUN_CLASS_SUCCESS_RESP, stun_msg_tid(req), NULL, key, keylen, fp, 0x00, attrc, ap); va_end(ap); if (err) goto out; mb->pos = presz; err = stun_send(proto, sock, dst, mb); out: mem_deref(mb); return err; } /** * Send a STUN error response * * @param proto Transport Protocol * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn) * @param dst Destination network address * @param presz Number of bytes in preamble, if sending over TURN * @param req Matching STUN request * @param scode Status code * @param reason Reason string * @param key Authentication key (optional) * @param keylen Number of bytes in authentication key * @param fp Use STUN Fingerprint attribute * @param attrc Number of attributes to encode (variable arguments) * @param ... Variable list of attribute-tuples * Each attribute has 2 arguments, attribute type and value * * @return 0 if success, otherwise errorcode */ int stun_ereply(int proto, void *sock, const struct sa *dst, size_t presz, const struct stun_msg *req, uint16_t scode, const char *reason, const uint8_t *key, size_t keylen, bool fp, uint32_t attrc, ...) { struct stun_errcode ec; struct mbuf *mb = NULL; int err = ENOMEM; va_list ap; if (!sock || !req || !scode || !reason) return EINVAL; mb = mbuf_alloc(256); if (!mb) goto out; ec.code = scode; ec.reason = (char *)reason; va_start(ap, attrc); mb->pos = presz; err = stun_msg_vencode(mb, stun_msg_method(req), STUN_CLASS_ERROR_RESP, stun_msg_tid(req), &ec, key, keylen, fp, 0x00, attrc, ap); va_end(ap); if (err) goto out; mb->pos = presz; err = stun_send(proto, sock, dst, mb); out: mem_deref(mb); return err; } ================================================ FILE: src/stun/req.c ================================================ /** * @file stun/req.c STUN request * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include "stun.h" /** * Send a STUN request using a client transaction * * @param ctp Pointer to allocated client transaction (optional) * @param stun STUN Instance * @param proto Transport Protocol * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn) * @param dst Destination network address * @param presz Number of bytes in preamble, if sending over TURN * @param method STUN Method * @param key Authentication key (optional) * @param keylen Number of bytes in authentication key * @param fp Use STUN Fingerprint attribute * @param resph Response handler * @param arg Response handler argument * @param attrc Number of attributes to encode (variable arguments) * @param ... Variable list of attribute-tuples * Each attribute has 2 arguments, attribute type and value * * @return 0 if success, otherwise errorcode */ int stun_request(struct stun_ctrans **ctp, struct stun *stun, int proto, void *sock, const struct sa *dst, size_t presz, uint16_t method, const uint8_t *key, size_t keylen, bool fp, stun_resp_h *resph, void *arg, uint32_t attrc, ...) { uint8_t tid[STUN_TID_SIZE]; struct mbuf *mb; va_list ap; int err; if (!stun) return EINVAL; mb = mbuf_alloc(512); if (!mb) return ENOMEM; stun_generate_tid(tid); va_start(ap, attrc); mb->pos = presz; err = stun_msg_vencode(mb, method, STUN_CLASS_REQUEST, tid, NULL, key, keylen, fp, 0x00, attrc, ap); va_end(ap); if (err) goto out; mb->pos = presz; err = stun_ctrans_request(ctp, stun, proto, sock, dst, mb, tid, method, key, keylen, resph, arg); if (err) goto out; out: mem_deref(mb); return err; } ================================================ FILE: src/stun/stun.c ================================================ /** * @file stun.c STUN stack * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "stun.h" const char *stun_software = "libre v" RE_VERSION " (" ARCH "/" OS ")"; static const struct stun_conf conf_default = { STUN_DEFAULT_RTO, STUN_DEFAULT_RC, STUN_DEFAULT_RM, STUN_DEFAULT_TI, 0x00 }; static void destructor(void *arg) { struct stun *stun = arg; stun_ctrans_close(stun); } /** * Allocate a new STUN instance * * @param stunp Pointer to allocated STUN instance * @param conf STUN configuration (optional) * @param indh STUN Indication handler (optional) * @param arg STUN Indication handler argument * * @return 0 if success, otherwise errorcode */ int stun_alloc(struct stun **stunp, const struct stun_conf *conf, stun_ind_h *indh, void *arg) { struct stun *stun; if (!stunp) return EINVAL; stun = mem_zalloc(sizeof(*stun), destructor); if (!stun) return ENOMEM; stun->conf = conf ? *conf : conf_default; stun->indh = indh; stun->arg = arg; *stunp = stun; return 0; } /** * Get STUN configuration object * * @param stun STUN Instance * * @return STUN configuration */ struct stun_conf *stun_conf(struct stun *stun) { return stun ? &stun->conf : NULL; } /** * Send a STUN message * * @param proto Transport protocol (IPPROTO_UDP or IPPROTO_TCP) * @param sock Socket, UDP (struct udp_sock) or TCP (struct tcp_conn) * @param dst Destination network address (UDP only) * @param mb Buffer containing the STUN message * * @return 0 if success, otherwise errorcode */ int stun_send(int proto, void *sock, const struct sa *dst, struct mbuf *mb) { int err; if (!sock || !mb) return EINVAL; switch (proto) { case IPPROTO_UDP: err = udp_send(sock, dst, mb); break; case IPPROTO_TCP: err = tcp_send(sock, mb); break; #ifdef USE_DTLS case STUN_TRANSP_DTLS: err = dtls_send(sock, mb); break; #endif default: err = EPROTONOSUPPORT; break; } return err; } /** * Receive a STUN message * * @param stun STUN Instance * @param mb Buffer containing STUN message * * @return 0 if success, otherwise errorcode */ int stun_recv(struct stun *stun, struct mbuf *mb) { struct stun_unknown_attr ua; struct stun_msg *msg; int err; if (!stun || !mb) return EINVAL; err = stun_msg_decode(&msg, mb, &ua); if (err) return err; switch (stun_msg_class(msg)) { case STUN_CLASS_INDICATION: if (ua.typec > 0) break; if (stun->indh) stun->indh(msg, stun->arg); break; case STUN_CLASS_ERROR_RESP: case STUN_CLASS_SUCCESS_RESP: err = stun_ctrans_recv(stun, msg, &ua); break; default: break; } mem_deref(msg); return err; } /** * Print STUN instance debug information * * @param pf Print function * @param stun STUN Instance * * @return 0 if success, otherwise errorcode */ int stun_debug(struct re_printf *pf, const struct stun *stun) { if (!stun) return 0; return re_hprintf(pf, "STUN debug:\n%H", stun_ctrans_debug, stun); } ================================================ FILE: src/stun/stun.h ================================================ /** * @file stun.h Internal STUN interface * * Copyright (C) 2010 Creytiv.com */ /** STUN Protocol values */ enum { STUN_MAGIC_COOKIE = 0x2112a442 /**< Magic Cookie for 3489bis */ }; /** Calculate STUN message type from method and class */ #define STUN_TYPE(method, class) \ ((method)&0x0f80) << 2 | \ ((method)&0x0070) << 1 | \ ((method)&0x000f) << 0 | \ ((class)&0x2) << 7 | \ ((class)&0x1) << 4 #define STUN_CLASS(type) \ ((type >> 7 | type >> 4) & 0x3) #define STUN_METHOD(type) \ ((type&0x3e00)>>2 | (type&0x00e0)>>1 | (type&0x000f)) struct stun_hdr { uint16_t type; /**< Message type */ uint16_t len; /**< Payload length */ uint32_t cookie; /**< Magic cookie */ uint8_t tid[STUN_TID_SIZE]; /**< Transaction ID */ }; struct stun { struct list ctl; struct stun_conf conf; stun_ind_h *indh; void *arg; }; int stun_hdr_encode(struct mbuf *mb, const struct stun_hdr *hdr); int stun_hdr_decode(struct mbuf *mb, struct stun_hdr *hdr); int stun_attr_encode(struct mbuf *mb, uint16_t type, const void *v, const uint8_t *tid, uint8_t padding); int stun_attr_decode(struct stun_attr **attrp, struct mbuf *mb, const uint8_t *tid, struct stun_unknown_attr *ua); void stun_attr_dump(const struct stun_attr *a); int stun_addr_encode(struct mbuf *mb, const struct sa *addr, const uint8_t *tid); int stun_addr_decode(struct mbuf *mb, struct sa *addr, const uint8_t *tid); int stun_ctrans_request(struct stun_ctrans **ctp, struct stun *stun, int proto, void *sock, const struct sa *dst, struct mbuf *mb, const uint8_t tid[], uint16_t met, const uint8_t *key, size_t keylen, stun_resp_h *resph, void *arg); void stun_ctrans_close(struct stun *stun); int stun_ctrans_debug(struct re_printf *pf, const struct stun *stun); ================================================ FILE: src/stun/stunstr.c ================================================ /** * @file stunstr.c STUN Strings * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include /* STUN Reason Phrase */ const char *stun_reason_300 = "Try Alternate"; const char *stun_reason_400 = "Bad Request"; const char *stun_reason_401 = "Unauthorized"; const char *stun_reason_403 = "Forbidden"; const char *stun_reason_420 = "Unknown Attribute"; const char *stun_reason_437 = "Allocation Mismatch"; const char *stun_reason_438 = "Stale Nonce"; const char *stun_reason_440 = "Address Family not Supported"; const char *stun_reason_441 = "Wrong Credentials"; const char *stun_reason_442 = "Unsupported Transport Protocol"; const char *stun_reason_443 = "Peer Address Family Mismatch"; const char *stun_reason_486 = "Allocation Quota Reached"; const char *stun_reason_500 = "Server Error"; const char *stun_reason_508 = "Insufficient Capacity"; /** * Get the name of a given STUN Transport * * @param tp STUN Transport * * @return Name of the corresponding STUN Transport */ const char *stun_transp_name(enum stun_transp tp) { switch (tp) { case STUN_TRANSP_UDP: return "UDP"; case STUN_TRANSP_TCP: return "TCP"; case STUN_TRANSP_DTLS: return "DTLS"; default: return "???"; } } ================================================ FILE: src/sys/daemon.c ================================================ /** * @file daemon.c Daemonize process * * Copyright (C) 2010 Creytiv.com */ #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include /** * Daemonize process * * @return 0 if success, otherwise errorcode */ int sys_daemon(void) { #ifdef HAVE_FORK pid_t pid; pid = fork(); if (-1 == pid) return errno; else if (pid > 0) exit(0); if (-1 == setsid()) return errno; (void)signal(SIGHUP, SIG_IGN); pid = fork(); if (-1 == pid) return errno; else if (pid > 0) exit(0); if (-1 == chdir("/")) return errno; (void)umask(0); /* Redirect standard files to /dev/null */ if (freopen("/dev/null", "r", stdin) == NULL) return errno; if (freopen("/dev/null", "w", stdout) == NULL) return errno; if (freopen("/dev/null", "w", stderr) == NULL) return errno; return 0; #else return ENOSYS; #endif } ================================================ FILE: src/sys/endian.c ================================================ /** * @file endian.c Endianness converting routines * * Copyright (C) 2010 Creytiv.com */ #include #include #include /* * These routes are working on both little-endian and big-endian platforms. */ /** * Convert a 16-bit value from host order to little endian * * @param v 16-bit in host order * * @return 16-bit little endian value */ uint16_t sys_htols(uint16_t v) { uint8_t *p = (uint8_t *)&v; uint16_t l = 0; l |= (uint16_t)*p++ << 0; l |= (uint16_t)*p << 8; return l; } /** * Convert a 32-bit value from host order to little endian * * @param v 32-bit in host order * * @return 32-bit little endian value */ uint32_t sys_htoll(uint32_t v) { uint8_t *p = (uint8_t *)&v; uint32_t l = 0; l |= (uint32_t)*p++ << 0; l |= (uint32_t)*p++ << 8; l |= (uint32_t)*p++ << 16; l |= (uint32_t)*p << 24; return l; } /** * Convert a 16-bit value from little endian to host order * * @param v 16-bit little endian value * * @return 16-bit value in host order */ uint16_t sys_ltohs(uint16_t v) { uint16_t s; uint8_t *p = (uint8_t *)&s; *p++ = v>>0 & 0xff; *p = v>>8 & 0xff; return s; } /** * Convert a 32-bit value from little endian to host order * * @param v 32-bit little endian value * * @return 32-bit value in host order */ uint32_t sys_ltohl(uint32_t v) { uint32_t h; uint8_t *p = (uint8_t *)&h; *p++ = v>>0 & 0xff; *p++ = v>>8 & 0xff; *p++ = v>>16 & 0xff; *p = v>>24 & 0xff; return h; } /** * Convert a 64-bit value from host to network byte-order * * @param v 64-bit host byte-order value * * @return 64-bit value in network byte-order */ uint64_t sys_htonll(uint64_t v) { uint64_t h = 0; uint8_t *p = (uint8_t *)&v; h |= (uint64_t)*p++ << 56; h |= (uint64_t)*p++ << 48; h |= (uint64_t)*p++ << 40; h |= (uint64_t)*p++ << 32; h |= (uint64_t)*p++ << 24; h |= (uint64_t)*p++ << 16; h |= (uint64_t)*p++ << 8; h |= (uint64_t)*p << 0; return h; } /** * Convert a 64-bit value from network to host byte-order * * @param v 64-bit network byte-order value * * @return 64-bit value in host byte-order */ uint64_t sys_ntohll(uint64_t v) { uint64_t h; uint8_t *p = (uint8_t *)&h; *p++ = (uint8_t) (v>>56 & 0xff); *p++ = (uint8_t) (v>>48 & 0xff); *p++ = (uint8_t) (v>>40 & 0xff); *p++ = (uint8_t) (v>>32 & 0xff); *p++ = (uint8_t) (v>>24 & 0xff); *p++ = (uint8_t) (v>>16 & 0xff); *p++ = (uint8_t) (v>>8 & 0xff); *p = (uint8_t) (v>>0 & 0xff); return h; } ================================================ FILE: src/sys/fs.c ================================================ /** * @file fs.c File-system functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_PWD_H #include #endif #ifdef WIN32 #include #include #include #include #endif #ifdef HAVE_IO_H #include #endif #include #include #include #include #include #define DEBUG_MODULE "fs" #define DEBUG_LEVEL 5 #include #ifdef WIN32 #define open _open #define read _read #define close _close #define dup _dup #define dup2 _dup2 #define fileno _fileno #endif #define MINBUF_SIZE 1024 static int dup_stdout = -1; static int dup_stderr = -1; /** * Create a directory with full path * * @param path Directory path * @param mode Access permissions * * @return 0 if success, otherwise errorcode */ int fs_mkdir(const char *path, uint16_t mode) { int ret; if (!path) return EINVAL; #if defined (WIN32) (void)mode; ret = _mkdir(path); #else ret = mkdir(path, mode); #endif if (ret < 0) return errno; return 0; } /** * Get the home directory for the current user * * @param path String to write home directory * @param sz Size of path string * * @return 0 if success, otherwise errorcode */ int fs_gethome(char *path, size_t sz) { #ifdef WIN32 char win32_path[MAX_PATH]; if (!path || !sz) return EINVAL; if (S_OK != SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, win32_path)) { return ENOENT; } str_ncpy(path, win32_path, sz); return 0; #elif defined(HAVE_PWD_H) const char *loginname; struct passwd *pw; if (!path || !sz) return EINVAL; loginname = sys_username(); if (!loginname) #ifdef HAVE_UNISTD_H pw = getpwuid(getuid()); #else return ENOENT; #endif else pw = getpwnam(loginname); if (!pw) return errno; str_ncpy(path, pw->pw_dir, sz); return 0; #else (void)path; (void)sz; return ENOSYS; #endif } /** * Check if given path is directory * * @param path Directory * * @return True if directory, False if not */ bool fs_isdir(const char *path) { struct stat st; if (!path) return false; if (stat(path, &st) < 0) return false; if ((st.st_mode & S_IFMT) != S_IFDIR) return false; return true; } /** * Check if given file exists and is a regular file * * @param file Filepath * * @return True if exists and is regular file, False if not */ bool fs_isfile(const char *file) { struct stat st; if (!file) return false; if (stat(file, &st) < 0) return false; if ((st.st_mode & S_IFMT) != S_IFREG) return false; return true; } /** * Open file with security enhancements (like fopen_s). * The file is created with mode 0600 if it does not exist * * @param fp FILE pointer for allocation * @param file Pathname * @param mode fopen mode * * @return 0 if success, otherwise errorcode * */ int fs_fopen(FILE **fp, const char *file, const char *mode) { #ifdef WIN32 return fopen_s(fp, file, mode); #else FILE *pfile; int fd; if (!fp || !file || !str_isset(mode)) return EINVAL; if (mode[0] == 'r' || fs_isfile(file)) goto fopen; fd = open(file, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR); if (fd == -1) return errno; (void)close(fd); fopen: pfile = fopen(file, mode); if (!pfile) return errno; *fp = pfile; return 0; #endif } /** * Hide/Close stdout and stderr output (no THREAD-SAFETY) */ void fs_stdio_hide(void) { dup_stdout = dup(fileno(stdout)); dup_stderr = dup(fileno(stderr)); #ifdef WIN32 int fd = open("nul", O_WRONLY); #else int fd = open("/dev/null", O_WRONLY); #endif if (fd < 0) return; (void)dup2(fd, fileno(stdout)); (void)dup2(fd, fileno(stderr)); close(fd); } /** * Restore stdout and stderr output (no THREAD-SAFETY) */ void fs_stdio_restore(void) { if (dup_stdout < 0 || dup_stderr < 0) return; (void)dup2(dup_stdout, fileno(stdout)); (void)dup2(dup_stderr, fileno(stderr)); } int fs_fread(struct mbuf **mbp, const char *path) { FILE *f = NULL; size_t n = 0; void *buf = NULL; struct mbuf *mb = NULL; int err; if (!mbp || !path) return EINVAL; err = fs_fopen(&f, path, "r"); if (err || !f) { DEBUG_WARNING("Could not open file '%s'\n", path); return err; } mb = mbuf_alloc(MINBUF_SIZE); buf = mem_zalloc(MINBUF_SIZE, NULL); if (!mb || !buf) { err = ENOMEM; goto out; } while (1) { n = fread(buf, 1, MINBUF_SIZE, f); if (!n) goto out; err = mbuf_write_mem(mb, buf, n); if (err) goto out; /* EOF */ if (n < MINBUF_SIZE) goto out; } out: if (!err && ferror(f)) err = EIO; fclose(f); mem_deref(buf); if (err) { DEBUG_WARNING("Error reading file '%s' (%m)\n", path, err); mem_deref(mb); } else *mbp = mb; return err; } ================================================ FILE: src/sys/rand.c ================================================ /** * @file rand.c Random generator * * Copyright (C) 2010 Creytiv.com */ #include #ifdef USE_OPENSSL #include #include #endif #include #include #include #include #include #define DEBUG_MODULE "rand" #define DEBUG_LEVEL 5 #include static const char alphanum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789"; #if !defined(USE_OPENSSL) && !defined(HAVE_ARC4RANDOM) static bool inited = false; /** * Initialise random number generator */ static void rand_init(void) { srand((uint32_t) tmr_jiffies()); inited = true; } #endif /** * Generate an unsigned 16-bit random value * * @return 16-bit random value */ uint16_t rand_u16(void) { /* Use higher-order bits (see man 3 rand) */ return rand_u32() >> 16; } /** * Generate an unsigned 32-bit random value * * @return 32-bit random value */ uint32_t rand_u32(void) { uint32_t v; #ifdef USE_OPENSSL v = 0; if (RAND_bytes((unsigned char *)&v, sizeof(v)) <= 0) { DEBUG_WARNING("RAND_bytes() error: %i\n", ERR_GET_REASON(ERR_get_error())); ERR_clear_error(); } #elif defined(HAVE_ARC4RANDOM) v = arc4random(); #elif defined(WIN32) if (!inited) rand_init(); v = (rand() << 16) + rand(); /* note: 16-bit rand */ #else if (!inited) rand_init(); v = rand(); #endif return v; } /** * Generate an unsigned 64-bit random value * * @return 64-bit random value */ uint64_t rand_u64(void) { return (uint64_t)rand_u32()<<32 | rand_u32(); } /** * Generate a random printable character * * @return Random printable character */ char rand_char(void) { char s[2]; rand_str(s, sizeof(s)); return s[0]; } /** * Generate a string of random characters * * @param str Pointer to string * @param size Size of string */ void rand_str(char *str, size_t size) { size_t i; if (!str || !size) return; --size; rand_bytes((uint8_t *)str, size); for (i=0; i #include #include #ifdef WIN32 #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SELECT_H #include #endif /** * Blocking sleep for [us] number of microseconds * * @param us Number of microseconds to sleep */ void sys_usleep(unsigned int us) { if (!us) return; #ifdef WIN32 Sleep(us / 1000); #elif defined(HAVE_SELECT) do { struct timeval tv; tv.tv_sec = us / 1000000; tv.tv_usec = us % 1000000; (void)select(0, NULL, NULL, NULL, &tv); } while (0); #else (void)usleep(us); #endif } ================================================ FILE: src/sys/sys.c ================================================ /** * @file sys.c System information * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_UNAME #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SETRLIMIT #include #endif #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #endif #ifdef WIN32 enum { MAX_ENVSZ = 32767 }; #endif /** * Get kernel name and version * * @param pf Print function for output * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int sys_kernel_get(struct re_printf *pf, void *unused) { #ifdef HAVE_UNAME struct utsname u; (void)unused; if (0 != uname(&u)) return errno; return re_hprintf(pf, "%s %s %s %s %s", u.sysname, u.nodename, u.release, u.version, u.machine); #else const char *str; (void)unused; #if defined(WIN32) str = "Win32"; #else str = "?"; #endif return re_hprintf(pf, "%s", str); #endif } /** * Get build info * * @param pf Print function for output * @param unused Unused parameter * * @return 0 if success, otherwise errorcode */ int sys_build_get(struct re_printf *pf, void *unused) { const unsigned int bus_width = 8*sizeof(void *); const char *endian = "unknown"; const uint32_t a = 0x12345678; const uint8_t b0 = ((uint8_t *)&a)[0]; const uint8_t b1 = ((uint8_t *)&a)[1]; const uint8_t b2 = ((uint8_t *)&a)[2]; const uint8_t b3 = ((uint8_t *)&a)[3]; (void)unused; if (0x12==b0 && 0x34==b1 && 0x56==b2 && 0x78==b3) endian = "big"; else if (0x12==b3 && 0x34==b2 && 0x56==b1 && 0x78==b0) endian = "little"; return re_hprintf(pf, "%u-bit %s endian", bus_width, endian); } /** * Get architecture * * @return Architecture string */ const char *sys_arch_get(void) { #ifdef ARCH return ARCH; #else return "?"; #endif } /** * Get name of Operating System * * @return Operating System string */ const char *sys_os_get(void) { #ifdef OS return OS; #else return "?"; #endif } /** * Get libre version * * @return libre version string */ const char *sys_libre_version_get(void) { #ifdef RE_VERSION return RE_VERSION; #else return "?"; #endif } /** * Return the username (login name) for the current user * * @return Username or NULL if not available */ const char *sys_username(void) { #ifdef HAVE_PWD_H char *login; login = getenv("LOGNAME"); if (!login) login = getenv("USER"); #ifdef HAVE_UNISTD_H if (!login) { login = getlogin(); } #endif return str_isset(login) ? login : NULL; #else return NULL; #endif } /** * Enable or disable coredump * * @param enable true to enable, false to disable coredump * * @return 0 if success, otherwise errorcode */ int sys_coredump_set(bool enable) { #ifdef HAVE_SETRLIMIT const struct rlimit rlim = { enable ? RLIM_INFINITY : 0, enable ? RLIM_INFINITY : 0 }; return 0 == setrlimit(RLIMIT_CORE, &rlim) ? 0 : errno; #else (void)enable; return ENOSYS; #endif } /** * Get an environment variable * * @param env Pointer to destination env var * @param name Environment variable name * * @return 0 if success, otherwise errorcode */ int sys_getenv(char **env, const char *name) { if (!env || !name) return EINVAL; #ifdef WIN32 uint32_t rc = 1; uint32_t bufsz = rc; char *buf; buf = mem_zalloc(bufsz, NULL); if (!buf) return ENOMEM; while (1) { rc = GetEnvironmentVariableA(name, buf, bufsz); if (!rc || rc == bufsz || rc > MAX_ENVSZ) { mem_deref(buf); return ENODATA; } /* success */ if (rc < bufsz) { *env = buf; return 0; } /* failed, getenv needs more space */ bufsz = rc; buf = mem_realloc(buf, bufsz); if (!buf) { mem_deref(buf); return ENOMEM; } } #else char *tmp = getenv(name); if (!tmp) return ENODATA; return str_dup(env, tmp); #endif } ================================================ FILE: src/tcp/tcp.c ================================================ /** * @file tcp.c Transport Control Protocol * * Copyright (C) 2010 Creytiv.com */ #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_IO_H #include #endif #if !defined(WIN32) #include #endif #include #include #include #include #include #include #include #include #include #include #define DEBUG_MODULE "tcp" #define DEBUG_LEVEL 5 #include /** Platform independent buffer type cast */ #ifdef WIN32 #define BUF_CAST (char *) #define SIZ_CAST (int) #define close closesocket #else #define BUF_CAST #define SIZ_CAST #endif enum { TCP_TXQSZ_DEFAULT = 524288, TCP_RXSZ_DEFAULT = 8192 }; /** Defines a listening TCP socket */ struct tcp_sock { struct re_fhs *fhs; re_sock_t fd; /**< Listening file descriptor */ re_sock_t fdc; /**< Cached connection file descriptor */ tcp_conn_h *connh; /**< TCP Connect handler */ void *arg; /**< Handler argument */ uint8_t tos; /**< Type-of-service field */ }; /** Defines a TCP connection */ struct tcp_conn { struct list helpers; /**< List of TCP-helpers */ struct list sendq; /**< Sending queue */ struct re_fhs *fhs; re_sock_t fdc; /**< Connection file descriptor */ tcp_estab_h *estabh; /**< Connection established handler */ tcp_send_h *sendh; /**< Data send handler */ tcp_recv_h *recvh; /**< Data receive handler */ tcp_close_h *closeh; /**< Connection close handler */ void *arg; /**< Handler argument */ size_t rxsz; /**< Maximum receive chunk size */ size_t txqsz; size_t txqsz_max; bool active; /**< We are connecting flag */ bool connected; /**< Connection is connected flag */ uint8_t tos; /**< Type-of-service field */ }; /** Defines a TCP-Connection Helper */ struct tcp_helper { struct le le; int layer; tcp_helper_estab_h *estabh; tcp_helper_send_h *sendh; tcp_helper_recv_h *recvh; void *arg; }; struct tcp_qent { struct le le; struct mbuf mb; }; static void tcp_recv_handler(int flags, void *arg); static bool helper_estab_handler(int *err, bool active, void *arg) { (void)err; (void)active; (void)arg; return false; } static bool helper_send_handler(int *err, struct mbuf *mb, void *arg) { (void)err; (void)mb; (void)arg; return false; } static bool helper_recv_handler(int *err, struct mbuf *mb, bool *estab, void *arg) { (void)err; (void)mb; (void)estab; (void)arg; return false; } static void sock_destructor(void *data) { struct tcp_sock *ts = data; if (ts->fd != RE_BAD_SOCK) { ts->fhs = fd_close(ts->fhs); (void)close(ts->fd); } if (ts->fdc != RE_BAD_SOCK) (void)close(ts->fdc); } static struct tcp_sock *sock_constructor(void) { struct tcp_sock *ts; ts = mem_zalloc(sizeof(*ts), sock_destructor); if (!ts) return NULL; ts->fhs = NULL; ts->fd = RE_BAD_SOCK; ts->fdc = RE_BAD_SOCK; return ts; } static void conn_destructor(void *data) { struct tcp_conn *tc = data; list_flush(&tc->helpers); list_flush(&tc->sendq); if (tc->fdc != RE_BAD_SOCK) { tc->fhs = fd_close(tc->fhs); (void)close(tc->fdc); } } static void helper_destructor(void *data) { struct tcp_helper *th = data; list_unlink(&th->le); } static void qent_destructor(void *arg) { struct tcp_qent *qe = arg; list_unlink(&qe->le); mem_deref(qe->mb.buf); } static int enqueue(struct tcp_conn *tc, struct mbuf *mb) { const size_t n = mbuf_get_left(mb); struct tcp_qent *qe; int err; if (tc->txqsz + n > tc->txqsz_max) return ENOSPC; if (!tc->sendq.head && !tc->sendh) { err = fd_listen(&tc->fhs, tc->fdc, FD_READ | FD_WRITE, tcp_recv_handler, tc); if (err) return err; } qe = mem_zalloc(sizeof(*qe), qent_destructor); if (!qe) return ENOMEM; list_append(&tc->sendq, &qe->le, qe); mbuf_init(&qe->mb); err = mbuf_write_mem(&qe->mb, mbuf_buf(mb), n); qe->mb.pos = 0; if (err) mem_deref(qe); else tc->txqsz += qe->mb.end; return err; } static int dequeue(struct tcp_conn *tc) { struct tcp_qent *qe = list_ledata(tc->sendq.head); ssize_t n; int err; #ifdef MSG_NOSIGNAL const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */ #else const int flags = 0; #endif if (!qe) { if (tc->sendh) tc->sendh(tc->arg); return 0; } n = send(tc->fdc, BUF_CAST mbuf_buf(&qe->mb), SIZ_CAST (qe->mb.end - qe->mb.pos), flags); if (n < 0) { err = RE_ERRNO_SOCK; if (err == EAGAIN) return 0; #ifdef WIN32 if (err == WSAEWOULDBLOCK) return 0; #endif return err; } tc->txqsz -= n; qe->mb.pos += n; if (qe->mb.pos >= qe->mb.end) mem_deref(qe); return 0; } static void conn_close(struct tcp_conn *tc, int err) { list_flush(&tc->sendq); tc->txqsz = 0; /* Stop polling */ if (tc->fdc != RE_BAD_SOCK) { tc->fhs = fd_close(tc->fhs); (void)close(tc->fdc); tc->fdc = RE_BAD_SOCK; } if (tc->closeh) tc->closeh(err, tc->arg); } static void tcp_recv_handler(int flags, void *arg) { struct tcp_conn *tc = arg; struct mbuf *mb = NULL; bool hlp_estab = false; struct le *le; ssize_t n; int err = 0; socklen_t err_len = sizeof(err); if (flags & FD_EXCEPT) { DEBUG_INFO("recv handler: got FD_EXCEPT on fd=%d\n", tc->fdc); } /* check for connection errors */ if (tc->active && !tc->connected) { if (-1 == getsockopt(tc->fdc, SOL_SOCKET, SO_ERROR, BUF_CAST &err, &err_len)) { DEBUG_WARNING("recv handler: getsockopt: (%m)\n", RE_ERRNO_SOCK); return; } } #if 0 if (EINPROGRESS != err && EALREADY != err) { DEBUG_WARNING("recv handler: Socket error (%m)\n", err); return; } #endif if (err) { conn_close(tc, err); return; } if (flags & FD_WRITE) { if (tc->connected) { uint32_t nrefs; mem_ref(tc); err = dequeue(tc); nrefs = mem_nrefs(tc); mem_deref(tc); /* check if connection was deref'd from send handler */ if (nrefs == 1) return; if (err) { conn_close(tc, err); return; } if (!tc->sendq.head && !tc->sendh) { err = fd_listen(&tc->fhs, tc->fdc, FD_READ, tcp_recv_handler, tc); if (err) { conn_close(tc, err); return; } } if (flags & FD_READ) goto read; return; } tc->connected = true; err = fd_listen(&tc->fhs, tc->fdc, FD_READ, tcp_recv_handler, tc); if (err) { DEBUG_WARNING("recv handler: fd_listen(): %m\n", err); conn_close(tc, err); return; } le = tc->helpers.head; while (le) { struct tcp_helper *th = le->data; le = le->next; if (th->estabh(&err, tc->active, th->arg) || err) { if (err) conn_close(tc, err); return; } } if (tc->estabh) tc->estabh(tc->arg); return; } read: mb = mbuf_alloc(tc->rxsz); if (!mb) return; n = recv(tc->fdc, BUF_CAST mb->buf, SIZ_CAST mb->size, 0); if (0 == n) { mem_deref(mb); conn_close(tc, 0); return; } else if (n < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("recv handler: recv(): %m\n", err); #ifdef WIN32 if (err == WSAECONNRESET || err == WSAECONNABORTED) { mem_deref(mb); conn_close(tc, err); return; } #endif goto out; } mb->end = n; le = tc->helpers.head; while (le) { struct tcp_helper *th = le->data; bool hdld = false; le = le->next; if (hlp_estab) { hdld |= th->estabh(&err, tc->active, th->arg); if (err) { conn_close(tc, err); goto out; } } if (mb->pos < mb->end) { hdld |= th->recvh(&err, mb, &hlp_estab, th->arg); if (err) { conn_close(tc, err); goto out; } } if (hdld) goto out; } mbuf_trim(mb); if (hlp_estab && tc->estabh) { uint32_t nrefs; mem_ref(tc); tc->estabh(tc->arg); nrefs = mem_nrefs(tc); mem_deref(tc); /* check if connection was deref'ed from establish handler */ if (nrefs == 1) goto out; } if (mb->pos < mb->end && tc->recvh) { tc->recvh(mb, tc->arg); } out: mem_deref(mb); } static struct tcp_conn *conn_alloc(tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg) { struct tcp_conn *tc; tc = mem_zalloc(sizeof(*tc), conn_destructor); if (!tc) return NULL; list_init(&tc->helpers); tc->fhs = NULL; tc->fdc = RE_BAD_SOCK; tc->rxsz = TCP_RXSZ_DEFAULT; tc->txqsz_max = TCP_TXQSZ_DEFAULT; tc->estabh = eh; tc->recvh = rh; tc->closeh = ch; tc->arg = arg; return tc; } static int tcp_sock_setopt(struct tcp_sock *ts, int level, int optname, const void *optval, uint32_t optlen) { int err = 0; if (!ts) return EINVAL; if (ts->fdc != RE_BAD_SOCK) { if (0 != setsockopt(ts->fdc, level, optname, BUF_CAST optval, optlen)) err |= RE_ERRNO_SOCK; } if (ts->fd != RE_BAD_SOCK) { if (0 != setsockopt(ts->fd, level, optname, BUF_CAST optval, optlen)) err |= RE_ERRNO_SOCK; } return err; } /** * Handler for incoming TCP connections. * * @param flags Event flags. * @param arg Handler argument. */ static void tcp_conn_handler(int flags, void *arg) { struct sa peer; struct tcp_sock *ts = arg; (void)flags; sa_init(&peer, AF_UNSPEC); if (ts->fdc != RE_BAD_SOCK) (void)close(ts->fdc); #ifdef HAVE_ACCEPT4 ts->fdc = accept4(ts->fd, &peer.u.sa, &peer.len, SOCK_NONBLOCK); if (ts->fdc == RE_BAD_SOCK) { return; } #else ts->fdc = accept(ts->fd, &peer.u.sa, &peer.len); if (ts->fdc == RE_BAD_SOCK) { return; } int err = net_sockopt_blocking_set(ts->fdc, false); if (err) { DEBUG_WARNING("conn handler: nonblock set: %m\n", err); (void)close(ts->fdc); ts->fdc = RE_BAD_SOCK; return; } #endif if (ts->connh) ts->connh(&peer, ts->arg); } /** * Create a TCP Socket with fd * * @param tsp Pointer to returned TCP Socket * @param fd File descriptor * @param ch Incoming connection handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_sock_alloc_fd(struct tcp_sock **tsp, re_sock_t fd, tcp_conn_h *ch, void *arg) { struct tcp_sock *ts = NULL; if (!tsp || fd == RE_BAD_SOCK) return EINVAL; ts = sock_constructor(); if (!ts) return ENOMEM; ts->fd = fd; ts->fdc = RE_BAD_SOCK; ts->connh = ch; ts->arg = arg; *tsp = ts; return fd_listen(&ts->fhs, ts->fd, FD_READ, tcp_conn_handler, ts); } /** * Create a TCP Socket * * @param tsp Pointer to returned TCP Socket * @param local Local listen address (NULL for any) * @param ch Incoming connection handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_sock_alloc(struct tcp_sock **tsp, const struct sa *local, tcp_conn_h *ch, void *arg) { struct addrinfo hints, *res = NULL, *r; char addr[64] = ""; char serv[6] = "0"; struct tcp_sock *ts = NULL; int error, err; if (!tsp) return EINVAL; ts = sock_constructor(); if (!ts) return ENOMEM; ts->fd = RE_BAD_SOCK; ts->fdc = RE_BAD_SOCK; if (local) { (void)re_snprintf(addr, sizeof(addr), "%H", sa_print_addr, local); (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local)); } memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res); if (error) { #ifdef WIN32 DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n", WSAGetLastError()); #endif DEBUG_WARNING("listen: getaddrinfo: %s:%s error=%d (%s)\n", addr, serv, error, gai_strerror(error)); err = EADDRNOTAVAIL; goto out; } err = EINVAL; for (r = res; r; r = r->ai_next) { re_sock_t fd = RE_BAD_SOCK; if (ts->fd != RE_BAD_SOCK) continue; fd = socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP); if (fd == RE_BAD_SOCK) { err = RE_ERRNO_SOCK; continue; } (void)net_sockopt_reuse_set(fd, true); err = net_sockopt_blocking_set(fd, false); if (err) { DEBUG_WARNING("listen: nonblock set: %m\n", err); (void)close(fd); continue; } /* OK */ ts->fd = fd; err = 0; break; } freeaddrinfo(res); if (ts->fd == RE_BAD_SOCK) goto out; ts->connh = ch; ts->arg = arg; out: if (err) mem_deref(ts); else *tsp = ts; return err; } /** * Duplicate TCP socket * * @param tso TCP Socket to duplicate * * @return Duplicated TCP Socket if success, otherwise NULL */ struct tcp_sock *tcp_sock_dup(struct tcp_sock *tso) { struct tcp_sock *ts; if (!tso) return NULL; ts = sock_constructor(); if (!ts) return NULL; ts->fd = RE_BAD_SOCK; ts->fdc = tso->fdc; tso->fdc = RE_BAD_SOCK; return ts; } /** * Bind to a TCP Socket * * @param ts TCP Socket * @param local Local bind address * * @return 0 if success, otherwise errorcode */ int tcp_sock_bind(struct tcp_sock *ts, const struct sa *local) { struct addrinfo hints, *res = NULL, *r; char addr[64] = ""; char serv[NI_MAXSERV] = "0"; int error, err; if (!ts || ts->fd == RE_BAD_SOCK) return EINVAL; if (local) { (void)re_snprintf(addr, sizeof(addr), "%H", sa_print_addr, local); (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local)); } memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res); if (error) { #ifdef WIN32 DEBUG_WARNING("sock_bind: getaddrinfo: wsaerr=%d\n", WSAGetLastError()); #endif DEBUG_WARNING("sock_bind: getaddrinfo: %s:%s error=%d (%s)\n", addr, serv, error, gai_strerror(error)); return EADDRNOTAVAIL; } err = EINVAL; for (r = res; r; r = r->ai_next) { /* use dual socket */ if (r->ai_family == AF_INET6) (void)net_sockopt_v6only(ts->fd, false); if (bind(ts->fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("sock_bind: bind: %m (af=%d, %J)\n", err, r->ai_family, local); continue; } /* OK */ err = 0; break; } freeaddrinfo(res); return err; } /** * Listen on a TCP Socket * * @param ts TCP Socket * @param backlog Maximum length the queue of pending connections * * @return 0 if success, otherwise errorcode */ int tcp_sock_listen(struct tcp_sock *ts, int backlog) { int err; if (!ts) return EINVAL; if (ts->fd == RE_BAD_SOCK) { DEBUG_WARNING("sock_listen: invalid fd\n"); return EBADF; } if (listen(ts->fd, backlog) < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("sock_listen: listen(): %m\n", err); return err; } return fd_listen(&ts->fhs, ts->fd, FD_READ, tcp_conn_handler, ts); } /** * Accept an incoming TCP Connection * * @param tcp Returned TCP Connection object * @param ts Corresponding TCP Socket * @param eh TCP Connection Established handler * @param rh TCP Connection Receive data handler * @param ch TCP Connection close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_accept(struct tcp_conn **tcp, struct tcp_sock *ts, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg) { struct tcp_conn *tc; int err; if (!tcp || !ts || ts->fdc == RE_BAD_SOCK) return EINVAL; tc = conn_alloc(eh, rh, ch, arg); if (!tc) return ENOMEM; /* Transfer ownership to TCP connection */ tc->fdc = ts->fdc; ts->fdc = RE_BAD_SOCK; err = fd_listen(&tc->fhs, tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT, tcp_recv_handler, tc); if (err) { DEBUG_WARNING("accept: fd_listen(): %m\n", err); } if (err) mem_deref(tc); else *tcp = tc; return err; } /** * Reject an incoming TCP Connection * * @param ts Corresponding TCP Socket */ void tcp_reject(struct tcp_sock *ts) { if (!ts) return; if (ts->fdc != RE_BAD_SOCK) { (void)close(ts->fdc); ts->fdc = RE_BAD_SOCK; } } /** * Allocate a TCP Connection * * @param tcp Returned TCP Connection object * @param peer Network address of peer * @param eh TCP Connection Established handler * @param rh TCP Connection Receive data handler * @param ch TCP Connection close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_conn_alloc(struct tcp_conn **tcp, const struct sa *peer, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg) { struct tcp_conn *tc; struct addrinfo hints, *res = NULL, *r; char addr[64]; char serv[NI_MAXSERV] = "0"; int error, err; if (!tcp || !sa_isset(peer, SA_ALL)) return EINVAL; tc = conn_alloc(eh, rh, ch, arg); if (!tc) return ENOMEM; memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; (void)re_snprintf(addr, sizeof(addr), "%H", sa_print_addr, peer); (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer)); error = getaddrinfo(addr, serv, &hints, &res); if (error) { DEBUG_WARNING("connect: getaddrinfo(): (%s)\n", gai_strerror(error)); err = EADDRNOTAVAIL; goto out; } err = EINVAL; for (r = res; r; r = r->ai_next) { tc->fdc = socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP); if (tc->fdc == RE_BAD_SOCK) { err = RE_ERRNO_SOCK; continue; } err = net_sockopt_blocking_set(tc->fdc, false); if (err) { DEBUG_WARNING("connect: nonblock set: %m\n", err); (void)close(tc->fdc); tc->fdc = RE_BAD_SOCK; continue; } err = 0; break; } freeaddrinfo(res); out: if (err) mem_deref(tc); else *tcp = tc; return err; } /** * Bind a TCP Connection to a local address * * @param tc TCP Connection object * @param local Local bind address * * @return 0 if success, otherwise errorcode */ int tcp_conn_bind(struct tcp_conn *tc, const struct sa *local) { struct addrinfo hints, *res = NULL, *r; char addr[64] = ""; char serv[NI_MAXSERV] = "0"; int error, err; if (!tc) return EINVAL; if (local) { (void)re_snprintf(addr, sizeof(addr), "%H", sa_print_addr, local); (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local)); } memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res); if (error) { DEBUG_WARNING("conn_bind: getaddrinfo(): (%s)\n", gai_strerror(error)); return EADDRNOTAVAIL; } err = EINVAL; for (r = res; r; r = r->ai_next) { (void)net_sockopt_reuse_set(tc->fdc, true); /* use dual socket */ if (r->ai_family == AF_INET6) (void)net_sockopt_v6only(tc->fdc, false); /* bind to local address */ if (bind(tc->fdc, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("conn_bind: bind(): %J: %m\n", local, err); continue; } /* OK */ err = 0; break; } freeaddrinfo(res); if (err) { DEBUG_WARNING("conn_bind failed: %J (%m)\n", local, err); } return err; } /** * Connect to a remote peer * * @param tc TCP Connection object * @param peer Network address of peer * * @return 0 if success, otherwise errorcode */ int tcp_conn_connect(struct tcp_conn *tc, const struct sa *peer) { struct addrinfo hints, *res = NULL, *r; char addr[64]; char serv[NI_MAXSERV]; int error, err = 0; if (!tc || !sa_isset(peer, SA_ALL)) return EINVAL; tc->active = true; if (tc->fdc == RE_BAD_SOCK) { DEBUG_WARNING("invalid fd\n"); return EBADF; } memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; (void)re_snprintf(addr, sizeof(addr), "%H", sa_print_addr, peer); (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer)); error = getaddrinfo(addr, serv, &hints, &res); if (error) { DEBUG_WARNING("connect: getaddrinfo(): (%s)\n", gai_strerror(error)); return EADDRNOTAVAIL; } for (r = res; r; r = r->ai_next) { struct sockaddr *sa = r->ai_addr; again: if (0 == connect(tc->fdc, sa, SIZ_CAST r->ai_addrlen)) { err = 0; goto out; } else { #ifdef WIN32 /* Special error handling for Windows */ if (WSAEWOULDBLOCK == WSAGetLastError()) { err = 0; goto out; } #endif /* Special case for mingw32/wine */ if (0 == errno) { err = 0; goto out; } if (EINTR == errno) goto again; if (EINPROGRESS != errno && EALREADY != errno) { err = errno; DEBUG_INFO("connect: connect() %J: %m\n", peer, err); } } } out: freeaddrinfo(res); if (err) return err; return fd_listen(&tc->fhs, tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT, tcp_recv_handler, tc); } static int tcp_send_internal(struct tcp_conn *tc, struct mbuf *mb, struct le *le) { int err = 0; ssize_t n; #ifdef MSG_NOSIGNAL const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */ #else const int flags = 0; #endif if (tc->fdc == RE_BAD_SOCK) return ENOTCONN; if (!mbuf_get_left(mb)) { DEBUG_WARNING("send: empty mbuf (pos=%u end=%u)\n", mb->pos, mb->end); return EINVAL; } /* call helpers in reverse order */ while (le) { struct tcp_helper *th = le->data; le = le->prev; if (th->sendh(&err, mb, th->arg) || err) return err; } if (tc->sendq.head) return enqueue(tc, mb); n = send(tc->fdc, BUF_CAST mbuf_buf(mb), SIZ_CAST (mb->end - mb->pos), flags); if (n < 0) { err = RE_ERRNO_SOCK; if (err == EAGAIN) return enqueue(tc, mb); #ifdef WIN32 if (err == WSAEWOULDBLOCK) return enqueue(tc, mb); #endif DEBUG_WARNING("send: write(): %m (fdc=%d)\n", err, tc->fdc); return err; } if ((size_t)n < mb->end - mb->pos) { mb->pos += n; err = enqueue(tc, mb); mb->pos -= n; return err; } return 0; } /** * Send data on a TCP Connection to a remote peer * * @param tc TCP Connection * @param mb Buffer to send * * @return 0 if success, otherwise errorcode */ int tcp_send(struct tcp_conn *tc, struct mbuf *mb) { if (!tc || !mb) return EINVAL; return tcp_send_internal(tc, mb, tc->helpers.tail); } /** * Send data on a TCP Connection to a remote peer bypassing this * helper and the helpers above it. * * @param tc TCP Connection * @param mb Buffer to send * @param th TCP Helper * * @return 0 if success, otherwise errorcode */ int tcp_send_helper(struct tcp_conn *tc, struct mbuf *mb, struct tcp_helper *th) { if (!tc || !mb || !th) return EINVAL; return tcp_send_internal(tc, mb, th->le.prev); } /** * Set the send handler on a TCP Connection, which will be called * every time it is ready to send data * * @param tc TCP Connection * @param sendh TCP Send handler * * @return 0 if success, otherwise errorcode */ int tcp_set_send(struct tcp_conn *tc, tcp_send_h *sendh) { if (!tc) return EINVAL; tc->sendh = sendh; if (tc->sendq.head || !sendh) return 0; return fd_listen(&tc->fhs, tc->fdc, FD_READ | FD_WRITE, tcp_recv_handler, tc); } /** * Set handlers on a TCP Connection * * @param tc TCP Connection * @param eh TCP Connection Established handler * @param rh TCP Connection Receive data handler * @param ch TCP Connection Close handler * @param arg Handler argument */ void tcp_set_handlers(struct tcp_conn *tc, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg) { if (!tc) return; tc->estabh = eh; tc->recvh = rh; tc->closeh = ch; tc->arg = arg; } /** * Get local network address of TCP Socket * * @param ts TCP Socket * @param local Returned local network address * * @return 0 if success, otherwise errorcode */ int tcp_sock_local_get(const struct tcp_sock *ts, struct sa *local) { int err; if (!ts || !local) return EINVAL; sa_init(local, AF_UNSPEC); err = getsockname(ts->fd, &local->u.sa, &local->len); if (err < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("local get: getsockname(): %m\n", err); } return err; } /** * Get local network address of TCP Connection * * @param tc TCP Connection * @param local Returned local network address * * @return 0 if success, otherwise errorcode */ int tcp_conn_local_get(const struct tcp_conn *tc, struct sa *local) { int err; if (!tc || !local) return EINVAL; sa_init(local, AF_UNSPEC); err = getsockname(tc->fdc, &local->u.sa, &local->len); if (err < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("conn local get: getsockname(): %m\n", err); } return err; } /** * Get remote peer network address of TCP Connection * * @param tc TCP Connection * @param peer Returned remote peer network address * * @return 0 if success, otherwise errorcode */ int tcp_conn_peer_get(const struct tcp_conn *tc, struct sa *peer) { int err; if (!tc || !peer) return EINVAL; sa_init(peer, AF_UNSPEC); err = getpeername(tc->fdc, &peer->u.sa, &peer->len); if (err < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("conn peer get: getpeername(): %m\n", err); } return err; } /** * Set the maximum receive chunk size on a TCP Connection * * @param tc TCP Connection * @param rxsz Maximum receive chunk size */ void tcp_conn_rxsz_set(struct tcp_conn *tc, size_t rxsz) { if (!tc) return; tc->rxsz = rxsz; } /** * Set the maximum send queue size on a TCP Connection * * @param tc TCP Connection * @param txqsz Maximum send queue size */ void tcp_conn_txqsz_set(struct tcp_conn *tc, size_t txqsz) { if (!tc) return; tc->txqsz_max = txqsz; } /** * Get the current length of the transmit queue on a TCP Connection * * @param tc TCP-Connection * * @return Current transmit queue length, or 0 if errors */ size_t tcp_conn_txqsz(const struct tcp_conn *tc) { return tc ? tc->txqsz : 0; } static bool sort_handler(struct le *le1, struct le *le2, void *arg) { struct tcp_helper *th1 = le1->data, *th2 = le2->data; (void)arg; return th1->layer <= th2->layer; } /** * Register a new TCP-helper on a TCP-Connection * * @param thp Pointer to allocated TCP helper * @param tc TCP Connection * @param layer Protocol layer; higher number means higher up in stack * @param eh Established handler * @param sh Send handler * @param rh Receive handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_register_helper(struct tcp_helper **thp, struct tcp_conn *tc, int layer, tcp_helper_estab_h *eh, tcp_helper_send_h *sh, tcp_helper_recv_h *rh, void *arg) { struct tcp_helper *th; if (!tc) return EINVAL; th = mem_zalloc(sizeof(*th), helper_destructor); if (!th) return ENOMEM; list_append(&tc->helpers, &th->le, th); th->layer = layer; th->estabh = eh ? eh : helper_estab_handler; th->sendh = sh ? sh : helper_send_handler; th->recvh = rh ? rh : helper_recv_handler; th->arg = arg; list_sort(&tc->helpers, sort_handler, NULL); if (thp) *thp = th; return 0; } int tcp_settos(struct tcp_sock *ts, uint32_t tos) { int err = 0; int v = tos; struct sa sa; if (!ts) return EINVAL; ts->tos = tos; err = tcp_local_get(ts, &sa); if (err) return err; if (sa_af(&sa) == AF_INET) { err = tcp_sock_setopt(ts, IPPROTO_IP, IP_TOS, &v, sizeof(v)); } #if defined(IPV6_TCLASS) && !defined(WIN32) else if (sa_af(&sa) == AF_INET6) { err = tcp_sock_setopt(ts, IPPROTO_IPV6, IPV6_TCLASS, &v, sizeof(v)); } #endif return err; } int tcp_conn_settos(struct tcp_conn *tc, uint32_t tos) { int err = 0; int v = tos; struct sa sa; if (!tc) return EINVAL; tc->tos = tos; if (tc->fdc == RE_BAD_SOCK) return err; err = tcp_conn_local_get(tc, &sa); if (err) return err; if (sa_af(&sa) == AF_INET) { if (0 != setsockopt(tc->fdc, IPPROTO_IP, IP_TOS, BUF_CAST &v, sizeof(v))) err = RE_ERRNO_SOCK; } #if defined(IPV6_TCLASS) && !defined(WIN32) else if (sa_af(&sa) == AF_INET6) { if (0 != setsockopt(tc->fdc, IPPROTO_IPV6, IPV6_TCLASS, BUF_CAST &v, sizeof(v))) err = RE_ERRNO_SOCK; } #endif return err; } bool tcp_sendq_used(struct tcp_conn *tc) { return tc->sendq.head != NULL; } ================================================ FILE: src/tcp/tcp_high.c ================================================ /** * @file tcp_high.c High-level TCP functions * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #ifndef RE_TCP_BACKLOG #define RE_TCP_BACKLOG 5 #endif /** * Create and listen on a TCP Socket * * @param tsp Pointer to returned TCP Socket * @param local Local listen address (NULL for any) * @param ch Incoming connection handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_listen(struct tcp_sock **tsp, const struct sa *local, tcp_conn_h *ch, void *arg) { struct tcp_sock *ts = NULL; int err; if (!tsp) return EINVAL; err = tcp_sock_alloc(&ts, local, ch, arg); if (err) goto out; err = tcp_sock_bind(ts, local); if (err) goto out; err = tcp_sock_listen(ts, RE_TCP_BACKLOG); if (err) goto out; out: if (err) ts = mem_deref(ts); else *tsp = ts; return err; } /** * Make a TCP Connection to a remote peer * * @param tcp Returned TCP Connection object * @param peer Network address of peer * @param eh TCP Connection Established handler * @param rh TCP Connection Receive data handler * @param ch TCP Connection close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_connect(struct tcp_conn **tcp, const struct sa *peer, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg) { struct tcp_conn *tc = NULL; int err; if (!tcp || !peer) return EINVAL; err = tcp_conn_alloc(&tc, peer, eh,rh, ch, arg); if (err) goto out; err = tcp_conn_connect(tc, peer); if (err) goto out; out: if (err) tc = mem_deref(tc); else *tcp = tc; return err; } /** * Make a TCP Connection to a remote peer * * @param tcp Returned TCP Connection object * @param peer Network address of peer * @param eh TCP Connection Established handler * @param rh TCP Connection Receive data handler * @param ch TCP Connection close handler * @param local Bind to local address * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tcp_connect_bind(struct tcp_conn **tcp, const struct sa *peer, tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, const struct sa *local, void *arg) { struct tcp_conn *tc = NULL; int err; if (!tcp || !peer) return EINVAL; err = tcp_conn_alloc(&tc, peer, eh,rh, ch, arg); if (err) goto out; err = tcp_conn_bind(tc, local); if (err) goto out; err = tcp_conn_connect(tc, peer); if (err) goto out; out: if (err) tc = mem_deref(tc); else *tcp = tc; return err; } /** * Get local network address of TCP Socket * * @param ts TCP Socket * @param local Returned local network address * * @return 0 if success, otherwise errorcode */ int tcp_local_get(const struct tcp_sock *ts, struct sa *local) { return tcp_sock_local_get(ts, local); } ================================================ FILE: src/telev/telev.c ================================================ /** * @file telev.c Telephony Events implementation (RFC 4733) * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include /* * Implements a subset of RFC 4733, * "RTP Payload for DTMF Digits, Telephony Tones, and Telephony Signals" * * Call telev_send() to send telephony events, and telev_recv() to receive. * The function telev_poll() must be called periodically, with a minimum * and stable interval of less than or equal to packet time (e.g. 50ms) * * NOTE: RTP timestamp and sequence number is kept in application * * * Example sending: * * ms M ts seq ev dur End * * press X --> * 50 1 0 1 X 400 0 * 100 0 0 2 X 800 0 * 150 0 0 3 X 1200 0 * 200 0 0 4 X 1600 0 * ... . . . . .... . * release X --> * 250 0 0 5 X 1600 1 * 300 0 0 6 X 1600 1 * press Y --> * 350 1 2000 7 Y 400 0 * ... . .... . . ... . */ enum { TXC_DIGIT_MIN = 9, TXC_END = 3, EVENT_END = 0xff, PAYLOAD_SIZE = 4, VOLUME = 10, QUEUE_SIZE = 8192, }; enum state { IDLE, SENDING, ENDING, }; struct telev_fmt { uint8_t event; bool end; uint8_t vol; uint16_t dur; }; /** Defines a Telephony Events state */ struct telev { /* tx */ struct mbuf *mb; uint32_t ptime; uint16_t pdur; enum state state; int event; uint16_t dur; uint32_t txc; /* rx */ int rx_event; bool rx_end; }; const char telev_rtpfmt[] = "telephone-event"; static int payload_encode(struct mbuf *mb, int event, bool end, uint16_t dur) { size_t pos; int err; if (!mb) return EINVAL; pos = mb->pos; err = mbuf_write_u8(mb, event); err |= mbuf_write_u8(mb, (end ? 0x80 : 0x00) | (VOLUME & 0x3f)); err |= mbuf_write_u16(mb, htons(dur)); if (err) mb->pos = pos; return err; } static int payload_decode(struct mbuf *mb, struct telev_fmt *fmt) { uint8_t b; if (!mb || !fmt) return EINVAL; if (mbuf_get_left(mb) < PAYLOAD_SIZE) return ENOENT; fmt->event = mbuf_read_u8(mb); b = mbuf_read_u8(mb); fmt->end = (b & 0x80) == 0x80; fmt->vol = (b & 0x3f); fmt->dur = ntohs(mbuf_read_u16(mb)); return 0; } static void destructor(void *arg) { struct telev *t = arg; mem_deref(t->mb); } /** * Allocate a new Telephony Events state * * @param tp Pointer to allocated object * @param ptime Packet time in [ms] * * @return 0 if success, otherwise errorcode */ int telev_alloc(struct telev **tp, uint32_t ptime) { struct telev *t; int err = 0; if (!tp || !ptime) return EINVAL; t = mem_zalloc(sizeof(*t), destructor); if (!t) return ENOMEM; t->mb = mbuf_alloc(16); if (!t->mb) { err = ENOMEM; goto out; } t->state = IDLE; t->ptime = ptime; t->pdur = ptime * 8; t->rx_event = -1; out: if (err) mem_deref(t); else *tp = t; return err; } /** * Sets the sampling rate * * @param tel Telephony Event state * @param srate Sampling rate in [Hz] * * @return 0 if success, otherwise errorcode */ int telev_set_srate(struct telev *tel, uint32_t srate) { if (!tel || !srate) return EINVAL; tel->pdur = tel->ptime * srate / 1000; return 0; } /** * Send a Telephony Event * * @param tel Telephony Event state * @param event The Event to send * @param end End-of-event flag * * @return 0 if success, otherwise errorcode */ int telev_send(struct telev *tel, int event, bool end) { size_t pos; int err; if (!tel) return EINVAL; if (tel->mb->end >= QUEUE_SIZE) return EOVERFLOW; pos = tel->mb->pos; tel->mb->pos = tel->mb->end; err = mbuf_write_u8(tel->mb, end ? EVENT_END : event); tel->mb->pos = pos; return err; } /** * Receive a Telephony Event * * @param tel Telephony Event state * @param mb Buffer to decode * @param event The received event, set on return * @param end End-of-event flag, set on return * * @return 0 if success, otherwise errorcode */ int telev_recv(struct telev *tel, struct mbuf *mb, int *event, bool *end) { struct telev_fmt fmt; int err; if (!tel || !mb || !event || !end) return EINVAL; err = payload_decode(mb, &fmt); if (err) return err; if (fmt.end) { if (tel->rx_end) return EALREADY; *event = fmt.event; *end = true; tel->rx_event = -1; tel->rx_end = true; return 0; } if (fmt.event == tel->rx_event) return EALREADY; *event = tel->rx_event = fmt.event; *end = tel->rx_end = false; return 0; } /** * Poll a Telephony Event state for sending * * @param tel Telephony Event state * @param marker Marker bit, set on return * @param mb Buffer with encoded data to send * * @return 0 if success, otherwise errorcode */ int telev_poll(struct telev *tel, bool *marker, struct mbuf *mb) { bool mrk = false; int err = ENOENT; if (!tel || !marker || !mb) return EINVAL; switch (tel->state) { case IDLE: if (!mbuf_get_left(tel->mb)) break; mrk = true; tel->event = mbuf_read_u8(tel->mb); tel->dur = tel->pdur; tel->state = SENDING; tel->txc = 1; err = payload_encode(mb, tel->event, false, tel->dur); break; case SENDING: tel->dur += tel->pdur; err = payload_encode(mb, tel->event, false, tel->dur); if (++tel->txc < TXC_DIGIT_MIN) break; if (!mbuf_get_left(tel->mb)) break; if (mbuf_read_u8(tel->mb) != EVENT_END) tel->mb->pos--; tel->state = ENDING; tel->txc = 0; break; case ENDING: err = payload_encode(mb, tel->event, true, tel->dur); if (++tel->txc < TXC_END) break; if (!mbuf_get_left(tel->mb)) tel->mb->pos = tel->mb->end = 0; tel->state = IDLE; break; } if (!err) *marker = mrk; return err; } bool telev_is_empty(const struct telev *tel) { if (!tel) return true; return tel->state == IDLE && !mbuf_get_left(tel->mb); } /** * Convert DTMF digit to Event code * * @param digit DTMF Digit * * @return Event code, or -1 if error */ int telev_digit2code(int digit) { if (isdigit(digit)) return digit - '0'; else if (digit == '*') return 10; else if (digit == '#') return 11; else if ('a' <= digit && digit <= 'd') return digit - 'a' + 12; else if ('A' <= digit && digit <= 'D') return digit - 'A' + 12; else return -1; } /** * Convert Event code to DTMF digit * * @param code Event code * * @return DTMF Digit, or -1 if error */ int telev_code2digit(int code) { static const char map[] = "0123456789*#ABCD"; if (code < 0 || code >= 16) return -1; return map[code]; } ================================================ FILE: src/thread/posix.c ================================================ /** * @file posix.c Pthread C11 thread implementation * * Copyright (C) 2022 Sebastian Reimers */ #include #include #include struct thread { thrd_start_t func; void *arg; }; static void *handler(void *p) { struct thread *th = p; int ret = th->func(th->arg); mem_deref(th); return (void *)(intptr_t)ret; } int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { struct thread *th; int err; if (!thr || !func) return thrd_error; th = mem_alloc(sizeof(struct thread), NULL); if (!th) return thrd_nomem; th->func = func; th->arg = arg; err = pthread_create(thr, NULL, handler, th); if (err) { mem_deref(th); return thrd_error; } return thrd_success; } int thrd_equal(thrd_t lhs, thrd_t rhs) { return pthread_equal(lhs, rhs); } thrd_t thrd_current(void) { return pthread_self(); } int thrd_detach(thrd_t thr) { return (pthread_detach(thr) == 0) ? thrd_success : thrd_error; } int thrd_join(thrd_t thr, int *res) { void *code; int err; err = pthread_join(thr, &code); if (res) *res = (int)(intptr_t)code; return err; } void call_once(once_flag *flag, void (*func)(void)) { pthread_once(flag, func); } void thrd_exit(int res) { pthread_exit((void *)(intptr_t)res); } int cnd_init(cnd_t *cnd) { if (!cnd) return thrd_error; return (pthread_cond_init(cnd, NULL) == 0) ? thrd_success : thrd_error; } int cnd_signal(cnd_t *cnd) { if (!cnd) return thrd_error; return (pthread_cond_signal(cnd) == 0) ? thrd_success : thrd_error; } int cnd_broadcast(cnd_t *cnd) { if (!cnd) return thrd_error; return (pthread_cond_broadcast(cnd) == 0) ? thrd_success : thrd_error; } int cnd_wait(cnd_t *cnd, mtx_t *mtx) { if (!cnd || !mtx) return thrd_error; return (pthread_cond_wait(cnd, mtx) == 0) ? thrd_success : thrd_error; } int cnd_timedwait(cnd_t *cnd, mtx_t *mtx, const struct timespec *abstime) { if (!cnd || !mtx || !abstime) return thrd_error; int ret = pthread_cond_timedwait(cnd, mtx, abstime); if (ret == ETIMEDOUT) return thrd_timedout; return (ret == 0) ? thrd_success : thrd_error; } void cnd_destroy(cnd_t *cnd) { if (!cnd) return; pthread_cond_destroy(cnd); } int mtx_init(mtx_t *mtx, int type) { pthread_mutexattr_t attr; if (!mtx) return thrd_error; if ((type & mtx_recursive) == 0) { pthread_mutex_init(mtx, NULL); return thrd_success; } pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(mtx, &attr); pthread_mutexattr_destroy(&attr); return thrd_success; } int mtx_lock(mtx_t *mtx) { if (!mtx) return thrd_error; return (pthread_mutex_lock(mtx) == 0) ? thrd_success : thrd_error; } int mtx_trylock(mtx_t *mtx) { if (!mtx) return thrd_error; return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; } int mtx_unlock(mtx_t *mtx) { if (!mtx) return thrd_error; return (pthread_mutex_unlock(mtx) == 0) ? thrd_success : thrd_error; } void mtx_destroy(mtx_t *mtx) { if (!mtx) return; pthread_mutex_destroy(mtx); } int tss_create(tss_t *key, tss_dtor_t destructor) { if (!key) return thrd_error; return (pthread_key_create(key, destructor) == 0) ? thrd_success : thrd_error; } void *tss_get(tss_t key) { return pthread_getspecific(key); } int tss_set(tss_t key, void *val) { return (pthread_setspecific(key, val) == 0) ? thrd_success : thrd_error; } void tss_delete(tss_t key) { pthread_key_delete(key); } ================================================ FILE: src/thread/thread.c ================================================ #include #include #include #include #include #ifdef HAVE_PRCTL #include #endif #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #endif #ifdef HAVE_PTHREAD #include #ifdef OPENBSD #include #endif #endif struct thread { thrd_t *thr; const char *name; thrd_start_t func; void *arg; }; static void mutex_destructor(void *data) { mtx_t *mtx = data; mtx_destroy(mtx); } static int _mutex_alloc(mtx_t **mtx, int type) { mtx_t *m; int err; if (!mtx) return EINVAL; m = mem_alloc(sizeof(mtx_t), NULL); if (!m) return ENOMEM; err = mtx_init(m, type) != thrd_success; if (err) { err = ENOMEM; goto out; } mem_destructor(m, mutex_destructor); *mtx = m; out: if (err) mem_deref(m); return err; } int mutex_alloc(mtx_t **mtx) { return _mutex_alloc(mtx, mtx_plain); } int mutex_alloc_tp(mtx_t **mtx, int type) { return _mutex_alloc(mtx, type); } static int handler(void *p) { struct thread th = *(struct thread *)p; mem_deref(p); #ifdef HAVE_PRCTL (void)prctl(PR_SET_NAME, th.name); #elif defined(WIN32) /* Not implemented */ #elif defined(DARWIN) (void)pthread_setname_np(th.name); #elif defined(HAVE_PTHREAD) #if defined(OPENBSD) (void)pthread_set_name_np(*th.thr, th.name); #elif defined(__NetBSD__) (void)pthread_setname_np(*th.thr, "%s", th.name); #else (void)pthread_setname_np(*th.thr, th.name); #endif #endif RE_TRACE_THREAD_NAME(th.name); return th.func(th.arg); } int thread_create_name(thrd_t *thr, const char *name, thrd_start_t func, void *arg) { struct thread *th; int ret; if (!thr || !func) return EINVAL; th = mem_alloc(sizeof(struct thread), NULL); if (!th) return ENOMEM; th->thr = thr; th->name = name; th->func = func; th->arg = arg; ret = thrd_create(thr, handler, th); if (ret == thrd_success) return 0; mem_deref(th); if (ret == thrd_nomem) return ENOMEM; return EAGAIN; } ================================================ FILE: src/thread/win32.c ================================================ /** * @file posix.c WIN32 thread implementation * * Copyright (C) 2022 Sebastian Reimers */ #include #include #include #include #include #include #include #define DEBUG_MODULE "thread" #define DEBUG_LEVEL 5 #include #define TSS_DESTRUCTOR_MAX 64 static struct tss_dtor_entry { tss_t key; tss_dtor_t dtor; } tss_dtor_tbl[TSS_DESTRUCTOR_MAX]; struct thread { thrd_start_t func; void *arg; }; static int dtor_register(tss_t key, tss_dtor_t dtor) { int i; for (i = 0; i < TSS_DESTRUCTOR_MAX; i++) { if (!tss_dtor_tbl[i].dtor) break; } if (i >= TSS_DESTRUCTOR_MAX) { DEBUG_WARNING("thread: max tss destructors reached\n"); return ENOMEM; } tss_dtor_tbl[i].key = key; tss_dtor_tbl[i].dtor = dtor; return 0; } static void tss_dtor_destruct(void) { void *val; for (int i = 0; i < TSS_DESTRUCTOR_MAX; i++) { if (!tss_dtor_tbl[i].dtor) continue; val = tss_get(tss_dtor_tbl[i].key); if (val) { tss_dtor_tbl[i].dtor(val); tss_dtor_tbl[i].dtor = NULL; } } } static unsigned __stdcall thrd_handler(void *p) { struct thread th = *(struct thread *)p; int err; mem_deref(p); err = th.func(th.arg); tss_dtor_destruct(); return err; } int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { struct thread *th; int err = thrd_success; uintptr_t handle; if (!thr || !func) return thrd_error; th = mem_alloc(sizeof(struct thread), NULL); if (!th) return thrd_nomem; th->func = func; th->arg = arg; handle = _beginthreadex(NULL, 0, thrd_handler, th, 0, NULL); if (handle == 0) { if (errno == EAGAIN || errno == EACCES) { err = thrd_nomem; goto out; } err = thrd_error; goto out; } thr->hdl = (HANDLE)handle; thr->id = GetThreadId((HANDLE)handle); out: if (err) mem_deref(th); return err; } int thrd_equal(thrd_t lhs, thrd_t rhs) { return lhs.id == rhs.id; } thrd_t thrd_current(void) { /* GetCurrentThread() returns only a pseudo handle and can not used * within other threads */ thrd_t t = {.hdl = GetCurrentThread(), .id = GetCurrentThreadId()}; return t; } int thrd_detach(thrd_t thr) { CloseHandle(thr.hdl); return thrd_success; } int thrd_join(thrd_t thr, int *res) { DWORD w, code; w = WaitForSingleObject(thr.hdl, INFINITE); if (w != WAIT_OBJECT_0) return thrd_error; if (res) { if (!GetExitCodeThread(thr.hdl, &code)) { CloseHandle(thr.hdl); return thrd_error; } *res = (int)code; } CloseHandle(thr.hdl); return thrd_success; } struct impl_call_once_param { void (*func)(void); }; static BOOL CALLBACK call_once_callback(PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context) { struct impl_call_once_param *param = Parameter; (void)InitOnce; (void)Context; param->func(); return true; } void call_once(once_flag *flag, void (*func)(void)) { struct impl_call_once_param param; param.func = func; InitOnceExecuteOnce(flag, call_once_callback, (PVOID)¶m, NULL); } void thrd_exit(int res) { _endthreadex((unsigned)res); } int cnd_init(cnd_t *cnd) { if (!cnd) return thrd_error; InitializeConditionVariable(cnd); return thrd_success; } int cnd_signal(cnd_t *cnd) { if (!cnd) return thrd_error; WakeConditionVariable(cnd); return thrd_success; } int cnd_broadcast(cnd_t *cnd) { if (!cnd) return thrd_error; WakeAllConditionVariable(cnd); return thrd_success; } int cnd_wait(cnd_t *cnd, mtx_t *mtx) { if (!cnd || !mtx) return thrd_error; SleepConditionVariableCS(cnd, mtx, INFINITE); return thrd_success; } static uint32_t abs2ms(const struct timespec *ts) { struct timespec now; if (!ts) return 0; uint64_t abs_ms = (ts->tv_sec * 1000U) + (ts->tv_nsec / 1000000L); tmr_timespec_get(&now, 0); uint64_t now_ms = (now.tv_sec * 1000U) + (now.tv_nsec / 1000000L); return (abs_ms > now_ms) ? (uint32_t)(abs_ms - now_ms) : 0; } int cnd_timedwait(cnd_t *cnd, mtx_t *mtx, const struct timespec *abstime) { if (!cnd || !mtx || !abstime) return thrd_error; if (SleepConditionVariableCS(cnd, mtx, abs2ms(abstime))) return thrd_success; return (GetLastError() == ERROR_TIMEOUT) ? thrd_timedout : thrd_error; } void cnd_destroy(cnd_t *cnd) { if (!cnd) return; /* do nothing */ } int mtx_init(mtx_t *mtx, int type) { (void)type; if (!mtx) return thrd_error; InitializeCriticalSection(mtx); return thrd_success; } int mtx_lock(mtx_t *mtx) { if (!mtx) return thrd_error; EnterCriticalSection(mtx); return thrd_success; } int mtx_trylock(mtx_t *mtx) { if (!mtx) return thrd_error; return TryEnterCriticalSection(mtx) ? thrd_success : thrd_busy; } int mtx_unlock(mtx_t *mtx) { if (!mtx) return thrd_error; LeaveCriticalSection(mtx); return thrd_success; } void mtx_destroy(mtx_t *mtx) { if (!mtx) return; DeleteCriticalSection(mtx); } int tss_create(tss_t *key, tss_dtor_t destructor) { int err; if (!key) return thrd_error; *key = TlsAlloc(); if (*key == TLS_OUT_OF_INDEXES) return thrd_error; if (!destructor) return thrd_success; err = dtor_register(*key, destructor); if (err) { TlsFree(*key); return thrd_error; } return thrd_success; } void *tss_get(tss_t key) { return TlsGetValue(key); } int tss_set(tss_t key, void *val) { return TlsSetValue(key, val) ? thrd_success : thrd_error; } void tss_delete(tss_t key) { TlsFree(key); } ================================================ FILE: src/tls/openssl/sni.c ================================================ /** * @file openssl/sni.c Server Name Indication API * * Copyright (C) 2022 Commend.com - c.spielberger@commend.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tls.h" #define DEBUG_MODULE "tls/sni" #define DEBUG_LEVEL 5 #include struct tls_conn; static bool x509_match_common_name(X509 *x509, const char *sni) { const X509_NAME *nm = X509_get_subject_name(x509); int lastpos = -1; for (;;) { lastpos = X509_NAME_get_index_by_NID(nm, NID_commonName, lastpos); if (lastpos == -1) break; const X509_NAME_ENTRY *e = X509_NAME_get_entry(nm, lastpos); const ASN1_STRING *astr = X509_NAME_ENTRY_get_data(e); if (!astr) continue; struct pl cn = {(char *)ASN1_STRING_get0_data(astr), ASN1_STRING_length(astr)}; if (!pl_strcasecmp(&cn, sni)) return true; } return false; } static bool x509_match_alt_name(X509 *x509, const char *sni) { GENERAL_NAMES *gs = NULL; ASN1_STRING *astr = NULL; ASN1_OCTET_STRING *octet = NULL; bool match = false; gs = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL); if (!gs) return false; for (int i = 0; i < sk_GENERAL_NAME_num(gs); i++) { GENERAL_NAME *g = sk_GENERAL_NAME_value(gs, i); if (g->type == GEN_DNS) { astr = ASN1_IA5STRING_new(); if (!astr) goto out; if (!ASN1_STRING_set(astr, sni, -1)) goto out; if (!ASN1_STRING_cmp(astr, g->d.dNSName)) { match = true; break; } } else if (g->type == GEN_IPADD) { octet = a2i_IPADDRESS(sni); if (!octet) break; if (!ASN1_OCTET_STRING_cmp(octet, g->d.iPAddress)) { match = true; break; } ASN1_OCTET_STRING_free(octet); } } out: GENERAL_NAMES_free(gs); ASN1_IA5STRING_free(astr); ASN1_OCTET_STRING_free(octet); return match; } /** * Finds a TLS certificate that matches a given Server Name Indication (SNI) * * @param tls TLS Context * @param sni Server Name Indication * * @return TLS certificate or NULL if not found */ struct tls_cert *tls_cert_for_sni(const struct tls *tls, const char *sni) { struct tls_cert *tls_cert = NULL; struct le *le; const struct list *certs = tls_certs(tls); if (!list_head(certs)) return NULL; if (!str_isset(sni)) return list_head(certs)->data; LIST_FOREACH(certs, le) { X509 *x509; tls_cert = le->data; x509 = tls_cert_x509(tls_cert); if (!x509) { tls_cert = NULL; continue; } if (x509_match_common_name(x509, sni)) break; if (x509_match_alt_name(x509, sni)) break; tls_cert = NULL; } ERR_clear_error(); return tls_cert; } static int ssl_servername_handler(SSL *ssl, int *al, void *arg) { struct tls *tls = arg; struct tls_cert *uc = NULL; const char *sni; if (!SSL_is_server(ssl)) return SSL_TLSEXT_ERR_OK; sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!str_isset(sni)) return SSL_TLSEXT_ERR_OK; /* find and apply matching certificate */ uc = tls_cert_for_sni(tls, sni); if (!uc) { *al = SSL_AD_UNRECOGNIZED_NAME; return SSL_TLSEXT_ERR_ALERT_FATAL; } DEBUG_INFO("found cert for sni %s\n", sni); if (SSL_set_SSL_CTX(ssl, tls_cert_ctx(uc)) == NULL) { *al = SSL_AD_INTERNAL_ERROR; return SSL_TLSEXT_ERR_ALERT_FATAL; } return SSL_TLSEXT_ERR_OK; } /** * Enables SNI handling on the given TLS context for incoming TLS connections * * @param tls TLS Context */ void tls_enable_sni(struct tls *tls) { SSL_CTX_set_tlsext_servername_callback(tls_ssl_ctx(tls), ssl_servername_handler); SSL_CTX_set_tlsext_servername_arg(tls_ssl_ctx(tls), tls); } ================================================ FILE: src/tls/openssl/tls.c ================================================ /** * @file openssl/tls.c TLS backend using OpenSSL * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tls.h" #define DEBUG_MODULE "tls" #define DEBUG_LEVEL 5 #include #include #include struct session_reuse { bool enabled; struct hash *ht_sessions; }; struct tls { SSL_CTX *ctx; X509 *cert; char *pass; /**< password for private key */ bool verify_server; /**< Enable SIP TLS server verification */ bool verify_client; /**< Enable SIP TLS client verification */ struct session_reuse reuse; struct list certs; /**< Certificates for SNI selection */ }; /** * A TLS certificate with private key, certificate chain and a host name that * is passed to OpenSSL for the host name check * */ struct tls_cert { struct le le; SSL_CTX *ctx; char *host; }; #if defined(TRACE_SSL) /** * Global flag if key material must be appended to file */ static bool fresh_keylog_file = true; /** * SSL Key logger callback function * * @param ssl OpenSSL SSL object * @param line Key material in NSS format */ static void tls_keylogger_cb(const SSL *ssl, const char *line) { FILE *f = NULL; (void) ssl; if (fresh_keylog_file) { f = fopen(TRACE_SSL, "w"); fresh_keylog_file = false; } else { f = fopen(TRACE_SSL, "a"); } if (!f) return; (void)re_fprintf(f, "%s\n", line); if (f) (void)fclose(f); } #endif /* NOTE: shadow struct defined in tls_*.c */ struct tls_conn { SSL *ssl; struct tls *tls; struct tls_conn_d cd; }; static void destructor(void *data) { struct tls *tls = data; if (tls->ctx) { SSL_CTX_sess_set_new_cb(tls->ctx, NULL); SSL_CTX_sess_set_remove_cb(tls->ctx, NULL); SSL_CTX_free(tls->ctx); } if (tls->cert) X509_free(tls->cert); hash_flush(tls->reuse.ht_sessions); mem_deref(tls->reuse.ht_sessions); mem_deref(tls->pass); list_flush(&tls->certs); } /*The password code is not thread safe*/ static int password_cb(char *buf, int size, int rwflag, void *userdata) { struct tls *tls = userdata; (void)rwflag; DEBUG_NOTICE("password callback\n"); if (size < (int)strlen(tls->pass)+1) return 0; strncpy(buf, tls->pass, size); return (int)strlen(tls->pass); } /** * OpenSSL verify handler for debugging purposes. Prints only warnings in the * default build * * @param ok Verification result of OpenSSL * @param ctx OpenSSL X509 store context set by OpenSSL * * @return passes parameter ok unchanged */ static int tls_verify_handler(int ok, X509_STORE_CTX *ctx) { int err, depth; err = X509_STORE_CTX_get_error(ctx); #if (DEBUG_LEVEL >= 6) char buf[128]; X509 *err_cert; err_cert = X509_STORE_CTX_get_current_cert(ctx); X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 128); DEBUG_INFO("%s: subject_name = %s\n", __func__, buf); X509_NAME_oneline(X509_get_issuer_name(err_cert), buf, 128); DEBUG_INFO("%s: issuer_name = %s\n", __func__, buf); #endif if (err) { depth = X509_STORE_CTX_get_error_depth(ctx); DEBUG_WARNING("%s: err = %d\n", __func__, err); DEBUG_WARNING("%s: error_string = %s\n", __func__, X509_verify_cert_error_string(err)); DEBUG_WARNING("%s: depth = %d\n", __func__, depth); } #if (DEBUG_LEVEL >= 6) DEBUG_INFO("tls verify ok = %d\n", ok); #endif return ok; } static int tls_verify_idx = -1; static once_flag oflag = ONCE_FLAG_INIT; static void tls_init_verify_idx(void) { if (tls_verify_idx > -1) return; tls_verify_idx = SSL_get_ex_new_index(0, "tls verify ud", NULL, NULL, NULL); } static int tls_ctx_alloc(SSL_CTX **ctxp, enum tls_method method, const char *certf, const char *pwd, struct tls *tls) { int err = 0; int r; SSL_CTX *ctx; int min_proto = 0; switch (method) { case TLS_METHOD_TLS: case TLS_METHOD_SSLV23: ctx = SSL_CTX_new(TLS_method()); min_proto = TLS1_2_VERSION; break; case TLS_METHOD_DTLS: case TLS_METHOD_DTLSV1: case TLS_METHOD_DTLSV1_2: ctx = SSL_CTX_new(DTLS_method()); break; default: DEBUG_WARNING("tls method %d not supported\n", method); return ENOSYS; } if (!ctx) { ERR_clear_error(); return ENOMEM; } SSL_CTX_set_min_proto_version(ctx, min_proto); if (!certf) goto out; /* Load our keys and certificates */ if (pwd && tls) { err = str_dup(&tls->pass, pwd); if (err) goto out; SSL_CTX_set_default_passwd_cb(ctx, password_cb); SSL_CTX_set_default_passwd_cb_userdata(ctx, tls); } r = SSL_CTX_use_certificate_chain_file(ctx, certf); if (r <= 0) { DEBUG_WARNING("Can't read certificate file: %s (%d)\n", certf, r); ERR_clear_error(); err = EINVAL; goto out; } r = SSL_CTX_use_PrivateKey_file(ctx, certf, SSL_FILETYPE_PEM); if (r <= 0) { DEBUG_WARNING("Can't read key file: %s (%d)\n", certf, r); ERR_clear_error(); err = EINVAL; goto out; } out: if (err) SSL_CTX_free(ctx); else *ctxp = ctx; return err; } /** * Allocate a new TLS context * * @param tlsp Pointer to allocated TLS context * @param method TLS method * @param keyfile Optional private key file * @param pwd Optional password * * @return 0 if success, otherwise errorcode */ int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile, const char *pwd) { struct tls *tls; int err; if (!tlsp) return EINVAL; tls = mem_zalloc(sizeof(*tls), destructor); if (!tls) return ENOMEM; err = tls_ctx_alloc(&tls->ctx, method, keyfile, pwd, tls); if (err) goto out; tls->verify_server = true; #if defined(TRACE_SSL) SSL_CTX_set_keylog_callback(tls->ctx, tls_keylogger_cb); #endif err = hash_alloc(&tls->reuse.ht_sessions, 256); if (err) goto out; call_once(&oflag, tls_init_verify_idx); err = 0; out: if (err) mem_deref(tls); else *tlsp = tls; return err; } /** * Set default locations for trusted CA certificates * * @param tls TLS Context * @param cafile PEM file with CA certificates * * @return 0 if success, otherwise errorcode */ int tls_add_ca(struct tls *tls, const char *cafile) { return tls_add_cafile_path(tls, cafile, NULL); } /** * Set default file and path for trusted CA certificates * * @param tls TLS Context * @param cafile PEM file with CA certificate(s) * @param capath Path containing CA certificates files * * @return 0 if success, otherwise errorcode */ int tls_add_cafile_path(struct tls *tls, const char *cafile, const char *capath) { if (!tls || (!cafile && !capath) || !tls->ctx) return EINVAL; if (capath && !fs_isdir(capath)) { return ENOTDIR; } /* Load the CAs we trust */ if (!(SSL_CTX_load_verify_locations(tls->ctx, cafile, capath))) { ERR_clear_error(); return ENOENT; } return 0; } /** * Add trusted CA certificates given as string * * @param tls TLS Context * @param capem Trusted CA as null-terminated string given in PEM format * * @return 0 if success, otherwise errorcode */ int tls_add_capem(const struct tls *tls, const char *capem) { X509_STORE *store; X509 *x509; BIO *bio; int ok; int err = 0; if (!tls || !capem || !tls->ctx) return EINVAL; store = SSL_CTX_get_cert_store(tls->ctx); if (!store) return EINVAL; bio = BIO_new_mem_buf((char *)capem, (int)strlen(capem)); if (!bio) return EINVAL; x509 = PEM_read_bio_X509(bio, NULL, 0, NULL); if (!x509) { err = EINVAL; DEBUG_WARNING("Could not read certificate capem\n"); goto out; } ok = X509_STORE_add_cert(store, x509); if (!ok) { err = EINVAL; DEBUG_WARNING("Could not add certificate capem\n"); } out: X509_free(x509); BIO_free(bio); return err; } /** * Add trusted CRL certificates given as string * * @param tls TLS Context * @param pem Trusted CRL as null-terminated string given in PEM format * * @return 0 if success, otherwise errorcode */ int tls_add_crlpem(const struct tls *tls, const char *pem) { X509_STORE *store; X509_CRL *crl; BIO *bio; int ok; int err = 0; if (!tls || !pem || !tls->ctx) return EINVAL; store = SSL_CTX_get_cert_store(tls->ctx); if (!store) return EINVAL; bio = BIO_new_mem_buf(pem, (int)strlen(pem)); if (!bio) return EINVAL; crl = PEM_read_bio_X509_CRL(bio, NULL, 0, NULL); if (!crl) { err = EINVAL; DEBUG_WARNING("Could not read certificate crlpem\n"); goto out; } ok = X509_STORE_add_crl(store, crl); if (!ok) { err = EINVAL; DEBUG_WARNING("Could not add certificate crlpem\n"); } out: X509_CRL_free(crl); BIO_free(bio); return err; } /** * Set SSL verification of the certificate purpose * * @param tls TLS Context * @param purpose Certificate purpose as string * * @return int 0 if success, errorcode otherwise */ int tls_set_verify_purpose(struct tls *tls, const char *purpose) { int err; int i; const X509_PURPOSE *xptmp; if (!tls || !purpose) return EINVAL; i = X509_PURPOSE_get_by_sname(purpose); if (i < 0) return EINVAL; /* purpose index -> purpose object */ /* purpose object -> purpose value */ xptmp = X509_PURPOSE_get0(i); i = X509_PURPOSE_get_id(xptmp); err = SSL_CTX_set_purpose(tls->ctx, i); return err == 1 ? 0 : EINVAL; } static int tls_generate_cert(X509 **pcert, const char *cn) { X509 *cert = NULL; X509_NAME *subj = NULL; int e = 0; if (!pcert || !cn) goto err; cert = X509_new(); if (!cert) goto err; if (!X509_set_version(cert, 2)) goto err; if (!ASN1_INTEGER_set(X509_get_serialNumber(cert), rand_u32())) goto err; subj = X509_NAME_new(); if (!subj) goto err; if (!X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC, (unsigned char *)cn, (int)strlen(cn), -1, 0)) goto err; if (!X509_set_issuer_name(cert, subj) || !X509_set_subject_name(cert, subj)) goto err; if (!X509_gmtime_adj(X509_getm_notBefore(cert), -3600*24*365) || !X509_gmtime_adj(X509_getm_notAfter(cert), 3600*24*365*10)) goto err; goto out; err: e = 1; out: if (e) X509_free(cert); else *pcert = cert; X509_NAME_free(subj); return e; } /** * Create a selfsigned X509 certificate using EC * * @param tls TLS Context * @param cn Common Name * @param curve_n Known EC curve name * * @return 0 if success, otherwise errorcode */ int tls_set_selfsigned_ec(struct tls *tls, const char *cn, const char *curve_n) { #ifndef OPENSSL_VERSION_MAJOR EC_KEY *eckey = NULL; int eccgrp; #endif EVP_PKEY *key = NULL; X509 *cert = NULL; int r, err = ENOMEM; if (!tls || !cn) return EINVAL; #if OPENSSL_VERSION_MAJOR >= 3 key = EVP_EC_gen(curve_n); if (!key) { err = ENOTSUP; goto out; } #else eccgrp = OBJ_txt2nid(curve_n); if (eccgrp == NID_undef) return ENOTSUP; eckey = EC_KEY_new_by_curve_name(eccgrp); if (!eckey) goto out; if (!EC_KEY_generate_key(eckey)) goto out; EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); key = EVP_PKEY_new(); if (!key) goto out; if (!EVP_PKEY_set1_EC_KEY(key, eckey)) goto out; #endif /* OPENSSL_VERSION_MAJOR */ if (tls_generate_cert(&cert, cn)) goto out; if (!X509_set_pubkey(cert, key)) goto out; if (!X509_sign(cert, key, EVP_sha256())) goto out; r = SSL_CTX_use_certificate(tls->ctx, cert); if (r != 1) goto out; r = SSL_CTX_use_PrivateKey(tls->ctx, key); if (r != 1) goto out; if (tls->cert) X509_free(tls->cert); tls->cert = cert; cert = NULL; err = 0; out: #ifndef OPENSSL_VERSION_MAJOR if (eckey) EC_KEY_free(eckey); #endif if (key) EVP_PKEY_free(key); if (cert) X509_free(cert); return err; } /** * Set the certificate and private key on a TLS context * * @param tls TLS Context * @param cert Certificate * @param pkey Private key * @param up_ref If true, increment reference count of the certificate if * successfully set. * If false, the reference count is not incremented and * the ownership of the certificate is passed to the TLS * context. * * @return 0 if success, otherwise errorcode */ int tls_set_certificate_openssl(struct tls *tls, X509* cert, EVP_PKEY* pkey, bool up_ref) { int r, err = ENOMEM; if (!tls || !cert || !pkey) return EINVAL; r = SSL_CTX_use_certificate(tls->ctx, cert); if (r != 1) goto out; r = SSL_CTX_use_PrivateKey(tls->ctx, pkey); if (r != 1) { DEBUG_WARNING("set_certificate_openssl: use_PrivateKey" " failed\n"); goto out; } if (tls->cert) X509_free(tls->cert); tls->cert = cert; if (up_ref) X509_up_ref(tls->cert); err = 0; out: if (err) ERR_clear_error(); return err; } /** * Set the certificate and private key on a TLS context * * @param tls TLS Context * @param cert Certificate in PEM format * @param len_cert Length of certificate PEM string * @param key Private key in PEM format, will be read from cert if NULL * @param len_key Length of private key PEM string * * @return 0 if success, otherwise errorcode */ int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert, const char *key, size_t len_key) { BIO *bio = NULL, *kbio = NULL; X509 *x509 = NULL; EVP_PKEY *pkey = NULL; int r, err = ENOMEM; if (!tls || !cert || !len_cert || (key && !len_key)) return EINVAL; if (!key) { key = cert; len_key = len_cert; } bio = BIO_new_mem_buf((char *)cert, (int)len_cert); kbio = BIO_new_mem_buf((char *)key, (int)len_key); if (!bio || !kbio) goto out; x509 = PEM_read_bio_X509(bio, NULL, 0, NULL); pkey = PEM_read_bio_PrivateKey(kbio, NULL, 0, NULL); if (!x509 || !pkey) goto out; r = SSL_CTX_use_certificate(tls->ctx, x509); if (r != 1) goto out; r = SSL_CTX_use_PrivateKey(tls->ctx, pkey); if (r != 1) { DEBUG_WARNING("set_certificate_pem: use_PrivateKey failed\n"); goto out; } if (tls->cert) X509_free(tls->cert); tls->cert = x509; x509 = NULL; err = 0; out: if (x509) X509_free(x509); if (pkey) EVP_PKEY_free(pkey); if (bio) BIO_free(bio); if (kbio) BIO_free(kbio); if (err) ERR_clear_error(); return err; } /** * Set the certificate and private key on a TLS context * * @param tls TLS Context * @param pem Certificate and private key in PEM format * @param len Length of PEM string * * @return 0 if success, otherwise errorcode */ int tls_set_certificate(struct tls *tls, const char *pem, size_t len) { return tls_set_certificate_pem(tls, pem, len, NULL, 0); } static int verify_trust_all(int ok, X509_STORE_CTX *ctx) { (void)ok; (void)ctx; return 1; /* We trust the certificate from peer */ } /** * Set TLS server context to request certificate from peer * and set trust all certificates of peer. * * @param tls TLS Context */ void tls_set_verify_client_trust_all(struct tls *tls) { if (!tls) return; SSL_CTX_set_verify_depth(tls->ctx, 0); SSL_CTX_set_verify(tls->ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_trust_all); } static int tls_verify_handler_ud(int ok, X509_STORE_CTX *ctx) { int ret = ok; struct tls_conn_d *d; SSL *ssl; int err, depth; err = X509_STORE_CTX_get_error(ctx); #if (DEBUG_LEVEL >= 6) char buf[128]; X509 *err_cert; err_cert = X509_STORE_CTX_get_current_cert(ctx); X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 128); DEBUG_INFO("%s: subject_name = %s\n", __func__, buf); X509_NAME_oneline(X509_get_issuer_name(err_cert), buf, 128); DEBUG_INFO("%s: issuer_name = %s\n", __func__, buf); #endif if (err) { depth = X509_STORE_CTX_get_error_depth(ctx); DEBUG_WARNING("%s: err = %d\n", __func__, err); DEBUG_WARNING("%s: error_string = %s\n", __func__, X509_verify_cert_error_string(err)); DEBUG_WARNING("%s: depth = %d\n", __func__, depth); } #if (DEBUG_LEVEL >= 6) DEBUG_INFO("tls_verify_handler ok = %d\n", ok); #endif ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); if (!ssl) { DEBUG_WARNING("X509_STORE_CTX_get_ex_data (SSL*) failed\n"); return ret; } d = SSL_get_ex_data(ssl, tls_verify_idx); if (!d) { DEBUG_WARNING("SSL_get_app_data (struct tls_conn_d) failed\n"); return ret; } if (d->verifyh) ret = d->verifyh(ok, d->arg); return ret; } /** * Enable request certificate from peer in TLS server connection * Set verify handler. * * @param tc TLS connection * @param depth Max depth certificate chain accepted. * A negative depth uses default depth. * @param verifyh SSL verify handler. If NULL default verify handler is used. * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int tls_set_verify_client_handler(struct tls_conn *tc, int depth, int (*verifyh) (int ok, void *arg), void *arg) { int err = 0; SSL_verify_cb tls_cb = tls_verify_handler_ud; if (!tc) return EINVAL; if (!verifyh) { tls_cb = tls_verify_handler; } else { tc->cd.verifyh = verifyh; tc->cd.arg = arg; SSL_set_ex_data(tc->ssl, tls_verify_idx, &tc->cd); } SSL_set_verify_depth(tc->ssl, depth < 0 ? SSL_get_verify_depth(tc->ssl) : depth); SSL_set_verify(tc->ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, tls_cb); return err; } /** * Set SRTP suites on TLS context * * @param tls TLS Context * @param suites Secure-RTP Profiles * * @return 0 if success, otherwise errorcode */ int tls_set_srtp(struct tls *tls, const char *suites) { if (!tls || !suites) return EINVAL; if (0 != SSL_CTX_set_tlsext_use_srtp(tls->ctx, suites)) { ERR_clear_error(); return ENOSYS; } return 0; } static int cert_fingerprint(X509 *cert, enum tls_fingerprint type, uint8_t *md, size_t size) { unsigned int len = (unsigned int)size; int n; switch (type) { case TLS_FINGERPRINT_SHA256: if (size < 32) return EOVERFLOW; n = X509_digest(cert, EVP_sha256(), md, &len); break; default: return ENOSYS; } if (n != 1) { ERR_clear_error(); return ENOENT; } return 0; } /** * Get fingerprint of local certificate * * @param tls TLS Context * @param type Digest type * @param md Buffer for fingerprint digest * @param size Buffer size * * @return 0 if success, otherwise errorcode */ int tls_fingerprint(const struct tls *tls, enum tls_fingerprint type, uint8_t *md, size_t size) { if (!tls || !tls->cert || !md) return EINVAL; return cert_fingerprint(tls->cert, type, md, size); } /** * Get fingerprint of peer certificate of a TLS connection * * @param tc TLS Connection * @param type Digest type * @param md Buffer for fingerprint digest * @param size Buffer size * * @return 0 if success, otherwise errorcode */ int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type, uint8_t *md, size_t size) { X509 *cert; int err; if (!tc || !md) return EINVAL; #if OPENSSL_VERSION_MAJOR >= 3 cert = SSL_get1_peer_certificate(tc->ssl); #else cert = SSL_get_peer_certificate(tc->ssl); #endif if (!cert) return ENOENT; err = cert_fingerprint(cert, type, md, size); X509_free(cert); return err; } /** * Get common name of peer certificate of a TLS connection * * @param tc TLS Connection * @param cn Returned common name * @param size Size of common name * * @return 0 if success, otherwise errorcode */ int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size) { X509 *cert; int n = -1; if (!tc || !cn || !size) return EINVAL; #if OPENSSL_VERSION_MAJOR >= 3 cert = SSL_get1_peer_certificate(tc->ssl); #else cert = SSL_get_peer_certificate(tc->ssl); #endif if (!cert) return ENOENT; const X509_NAME *name = X509_get_subject_name(cert); int ix = X509_NAME_get_index_by_NID(name, NID_commonName, -1); if (ix != -1) { const X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, ix); const ASN1_STRING *astr = X509_NAME_ENTRY_get_data(entry); if (astr) { str_ncpy(cn, (char *)ASN1_STRING_get0_data(astr), size); n = ASN1_STRING_length(astr); } } X509_free(cert); if (n < 0) { ERR_clear_error(); return ENOENT; } return 0; } /** * Verify peer certificate of a TLS connection * * @param tc TLS Connection * * @return 0 if verified, otherwise errorcode */ int tls_peer_verify(const struct tls_conn *tc) { if (!tc) return EINVAL; if (SSL_get_verify_result(tc->ssl) != X509_V_OK) return EAUTH; return 0; } /** * Get SRTP suite and keying material of a TLS connection * * @param tc TLS Connection * @param suite Returned SRTP suite * @param cli_key Client key * @param cli_key_size Client key size * @param srv_key Server key * @param srv_key_size Server key size * * @return 0 if success, otherwise errorcode */ int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite, uint8_t *cli_key, size_t cli_key_size, uint8_t *srv_key, size_t srv_key_size) { static const char *label = "EXTRACTOR-dtls_srtp"; size_t key_size, salt_size, size; SRTP_PROTECTION_PROFILE *sel; uint8_t keymat[256], *p; if (!tc || !suite || !cli_key || !srv_key) return EINVAL; sel = SSL_get_selected_srtp_profile(tc->ssl); if (!sel) return ENOENT; switch (sel->id) { case SRTP_AES128_CM_SHA1_80: *suite = SRTP_AES_CM_128_HMAC_SHA1_80; key_size = 16; salt_size = 14; break; case SRTP_AES128_CM_SHA1_32: *suite = SRTP_AES_CM_128_HMAC_SHA1_32; key_size = 16; salt_size = 14; break; #ifdef SRTP_AEAD_AES_128_GCM case SRTP_AEAD_AES_128_GCM: *suite = SRTP_AES_128_GCM; key_size = 16; salt_size = 12; break; #endif #ifdef SRTP_AEAD_AES_256_GCM case SRTP_AEAD_AES_256_GCM: *suite = SRTP_AES_256_GCM; key_size = 32; salt_size = 12; break; #endif default: return ENOSYS; } size = key_size + salt_size; if (cli_key_size < size || srv_key_size < size) return EOVERFLOW; if (sizeof(keymat) < 2*size) return EOVERFLOW; if (1 != SSL_export_keying_material(tc->ssl, keymat, 2*size, label, strlen(label), NULL, 0, 0)) { ERR_clear_error(); return ENOENT; } p = keymat; memcpy(cli_key, p, key_size); p += key_size; memcpy(srv_key, p, key_size); p += key_size; memcpy(cli_key + key_size, p, salt_size); p += salt_size; memcpy(srv_key + key_size, p, salt_size); mem_secclean(keymat, sizeof(keymat)); return 0; } /** * Get cipher name of a TLS connection * * @param tc TLS Connection * * @return name of cipher actually used. */ const char *tls_cipher_name(const struct tls_conn *tc) { if (!tc) return NULL; return SSL_get_cipher_name(tc->ssl); } /** * Set the ciphers to use for this TLS context * * @param tls TLS Context * @param cipherv Vector of cipher names, in order of priority * @param count Number of cipher names in the vector * * @return 0 if success, otherwise errorcode */ int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count) { struct mbuf *mb; int r, err; size_t i; if (!tls || !cipherv || !count) return EINVAL; mb = mbuf_alloc(32 * count); if (!mb) return ENOMEM; for (i=0; i0 ? ":" : "", cipherv[i]); if (err) goto out; } err = mbuf_write_u8(mb, '\0'); if (err) goto out; r = SSL_CTX_set_cipher_list(tls->ctx, (char *)mb->buf); if (r <= 0) { ERR_clear_error(); err = EPROTO; goto out; } out: mem_deref(mb); return err; } /** * Enable verification of server certificate and hostname (SNI) * * @param tc TLS Connection * @param host Server hostname * * @return 0 if success, otherwise errorcode */ int tls_set_verify_server(struct tls_conn *tc, const char *host) { struct sa sa; if (!tc || !host) return EINVAL; if (!tc->tls->verify_server) return 0; if (sa_set_str(&sa, host, 0)) { SSL_set_hostflags(tc->ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); #if OPENSSL_VERSION_MAJOR >= 4 if (!SSL_set1_dnsname(tc->ssl, host)) { DEBUG_WARNING("SSL_set1_dnsname error\n"); ERR_clear_error(); return EPROTO; } #else if (!SSL_set1_host(tc->ssl, host)) { DEBUG_WARNING("SSL_set1_host error\n"); ERR_clear_error(); return EPROTO; } #endif if (!SSL_set_tlsext_host_name(tc->ssl, host)) { DEBUG_WARNING("SSL_set_tlsext_host_name error\n"); ERR_clear_error(); return EPROTO; } } SSL_set_verify(tc->ssl, SSL_VERIFY_PEER, tls_verify_handler); return 0; } /** * Enable verification of client certificate * * @param tc TLS Connection * * @return 0 if success, otherwise errorcode */ int tls_verify_client(struct tls_conn *tc) { if (!tc) return EINVAL; if (!tc->tls->verify_client) return 0; SSL_set_verify(tc->ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, tls_verify_handler); return 0; } static int print_error(const char *str, size_t len, void *unused) { (void)unused; DEBUG_WARNING("%b", str, len); return 1; } void tls_flush_error(void) { ERR_print_errors_cb(print_error, NULL); } /** * Convert a X509_NAME object into a human-readable form placed in an mbuf * * @param field X509_NAME of Cert field * @param mb Memorybuffer to store the readable format * @param flags X509_NAME_print_ex flags * * @return 0 if success, otherwise errorcode */ static int convert_X509_NAME_to_mbuf(const X509_NAME *field, struct mbuf *mb, unsigned long flags) { BIO *outbio; char *p; long size; int err = ENOMEM; if (!field || !mb) return EINVAL; outbio = BIO_new(BIO_s_mem()); if (!outbio) return ENOMEM; if (X509_NAME_print_ex(outbio, field, 1, flags) <= 0) goto out; if (BIO_eof(outbio)) goto out; size = BIO_get_mem_data(outbio, &p); err = mbuf_write_mem(mb, (uint8_t *)p, size); if (err) goto out; err = 0; out: if (outbio) BIO_free(outbio); return err; } /** * Extract a X509 certificate issuer/subject and write the result into an mbuf * * @param tls TLS Object * @param mb Memory buffer * @param field_getter Functionpointer to the X509 getter function * @param flags X509_NAME_print_ex flags * * @return 0 if success, otherwise errorcode */ static int tls_get_ca_chain_field(struct tls *tls, struct mbuf *mb, tls_get_certfield_h *field_getter, unsigned long flags) { X509 *crt = NULL; const X509_NAME *field; crt = SSL_CTX_get0_certificate(tls->ctx); if (!crt) return ENOENT; field = field_getter(crt); if (!field) return ENOTSUP; return convert_X509_NAME_to_mbuf(field, mb, flags); } /** * Get the issuers fields of a certificate chain * * @param tls TLS Object * @param mb Memory Buffer * * @return 0 if success, otherwise errorcode */ int tls_get_issuer(struct tls *tls, struct mbuf *mb) { if (!tls || !tls->ctx || !mb) return EINVAL; return tls_get_ca_chain_field(tls, mb, &X509_get_issuer_name, XN_FLAG_RFC2253); } /** * Get the subject fields of a certificate chain * * @param tls TLS Object * @param mb Memory Buffer * * @return 0 if success, otherwise errorcode */ int tls_get_subject(struct tls *tls, struct mbuf *mb) { if (!tls || !tls->ctx || !mb) return EINVAL; return tls_get_ca_chain_field(tls, mb, &X509_get_subject_name, XN_FLAG_RFC2253); } /** * Disables SIP TLS server verifications for following requests * * @param tls TLS Object */ void tls_disable_verify_server(struct tls *tls) { if (!tls) return; tls->verify_server = false; } /** * Enables SIP TLS client verifications for following requests * * @param tls TLS Object * @param enable true to enable client verification, false to disable */ void tls_enable_verify_client(struct tls *tls, bool enable) { if (!tls) return; tls->verify_client = enable; } /** * Set minimum TLS version * * @param tls TLS Object * @param version Minimum version, e.g.: TLS1_2_VERSION * * @return 0 if success, otherwise errorcode */ int tls_set_min_proto_version(struct tls *tls, int version) { if (!tls) return EINVAL; if (SSL_CTX_set_min_proto_version(tls->ctx, version)) return 0; return EACCES; } /** * Set maximum TLS version * * @param tls TLS Object * @param version Maximum version, e.g. TLS1_2_VERSION * * @return 0 if success, otherwise errorcode */ int tls_set_max_proto_version(struct tls *tls, int version) { if (!tls) return EINVAL; if (SSL_CTX_set_max_proto_version(tls->ctx, version)) return 0; return EACCES; } struct session_entry { struct le le; struct sa peer; SSL_SESSION *sess; }; static void session_destructor(void *arg) { struct session_entry *e = arg; hash_unlink(&e->le); if (e->sess) SSL_SESSION_free(e->sess); } static bool session_cmp_handler(struct le *le, void *arg) { const struct session_entry *s = le->data; if (!s) return false; return sa_cmp(&s->peer, arg, SA_ALL); } static int tls_session_update_cache(const struct tls_conn *tc, SSL_SESSION *sess) { struct sa peer; struct session_entry* e = NULL; int err = 0; if (!tc || !tc->tls) { DEBUG_WARNING("%s: no tc or tls.\n", __func__); return EINVAL; } err = tcp_conn_peer_get(tls_get_tcp_conn(tc), &peer); if (err) { DEBUG_WARNING("%s: tcp_conn_peer_get failed: (%m).\n", __func__, err); return ENODATA; } e = list_ledata(hash_lookup(tc->tls->reuse.ht_sessions, sa_hash(&peer, SA_ALL), session_cmp_handler, &peer)); mem_deref(e); #if !defined(LIBRESSL_VERSION_NUMBER) if (!SSL_SESSION_is_resumable(sess)) { return EINVAL; } #endif e = mem_zalloc(sizeof(struct session_entry), session_destructor); if (!e) { DEBUG_WARNING("%s: error allocating session_entry.\n", __func__); return ENOMEM; } sa_cpy(&e->peer, &peer); e->sess = sess; hash_append(tc->tls->reuse.ht_sessions, sa_hash(&e->peer, SA_ALL), &e->le, e); return err; } static int session_new_cb(struct ssl_st *ssl, SSL_SESSION *sess) { BIO *wbio = NULL; const struct tls_conn *tc = NULL; wbio = SSL_get_wbio(ssl); if (!wbio) { DEBUG_WARNING("%s: SSL_get_rbio failed.\n", __func__); return 0; } tc = BIO_get_data(wbio); if (!tc) { DEBUG_WARNING("%s: BIO_get_data tc failed.\n", __func__); return 0; } if (tls_session_update_cache(tc, sess)) return 0; if (!SSL_SESSION_set_ex_data(sess, 0, tc->tls)) { DEBUG_WARNING("%s: SSL_SESSION_set_ex_data failed.\n", __func__); return 0; } /* openssl will increments reference counter of sess on 1 */ return 1; } static bool remove_handler(struct le *le, void *arg) { struct session_entry *e = le->data; if (!e || !arg) return false; if (e->sess == arg) mem_deref(e); return false; } static void session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) { struct tls *tls = SSL_SESSION_get_ex_data(sess, 0); (void) ctx; if (!tls) { DEBUG_WARNING("%s: SSL_SESSION_get_ex_data failed.\n", __func__); return; } /* iterate over all hash table entries and search for session */ (void) hash_apply(tls->reuse.ht_sessions, remove_handler, sess); } /** * Enable/disable TLS session cache. * * @param tls TLS Object * @param enabled enabled or disable session cache. Default: disabled * * Note: session reuse in TLSv1.3 is not yet supported * * @return 0 if success, otherwise errorcode */ int tls_set_session_reuse(struct tls *tls, int enabled) { if (!tls) return EINVAL; tls->reuse.enabled = enabled; SSL_CTX_set_session_cache_mode(tls->ctx, enabled ? SSL_SESS_CACHE_BOTH : SSL_SESS_CACHE_OFF); if (!tls->reuse.enabled) return 0; SSL_CTX_sess_set_new_cb(tls->ctx, session_new_cb); SSL_CTX_sess_set_remove_cb(tls->ctx, session_remove_cb); return 0; } /** * Check if session was reused * * @param tc tlc connection object * * @return 1 reused, 0 otherwise */ bool tls_session_reused(const struct tls_conn *tc) { if (!tc) return false; return SSL_session_reused(tc->ssl); } /** * getter for session reuse enabled * * @param tc tlc connection object * * @return 1 enabled, 0 disabled */ bool tls_get_session_reuse(const struct tls_conn *tc) { if (!tc) return false; return tc->tls->reuse.enabled; } /** * Reuse session if possible * * @param tc tlc connection object * * @return 0 if success, otherwise errorcode */ int tls_reuse_session(const struct tls_conn *tc) { int err = 0; struct sa peer; struct session_entry *sess = NULL; if (!tc || !tc->tls) return EINVAL; err = tcp_conn_peer_get(tls_get_tcp_conn(tc), &peer); if (err) { DEBUG_WARNING("%s: tcp_conn_peer_get failed: (%m).\n", __func__, err); return 0; } sess = list_ledata(hash_lookup(tc->tls->reuse.ht_sessions, sa_hash(&peer, SA_ALL), session_cmp_handler, &peer)); if (sess && !SSL_set_session(tc->ssl, sess->sess)) { err = EFAULT; DEBUG_WARNING("%s: error: %m, ssl_err=%d\n", __func__, err, SSL_get_error(tc->ssl, err)); } return err; } /** * update session cache manually * * @param tc tlc connection object * * @return 0 if success, otherwise errorcode */ int tls_update_sessions(const struct tls_conn *tc) { int err = 0; SSL_SESSION *sess = NULL; if (!tc || !tc->tls) return EINVAL; sess = SSL_get1_session(tc->ssl); if (!sess) return EINVAL; err = tls_session_update_cache(tc, sess); if (err) SSL_SESSION_free(sess); return err; } /** * Reuse session if possible * * @param tls tls connection object * * @return SSL_CTX* if set or NULL otherwise */ SSL_CTX *tls_ssl_ctx(const struct tls *tls) { if (!tls) return NULL; return tls->ctx; } static void tls_cert_destructor(void *arg) { struct tls_cert *uc = arg; mem_deref(uc->host); if (uc->ctx) SSL_CTX_free(uc->ctx); } /** * Adds a certificate for Server Name Indication (SNI) based certificate * selection. An incoming client hello may contain an SNI extension which * is used to select a local server certificate * * @param tls TLS context * @param certf Filename of the certificate * @param host Hostname that should match the SNI from client hello * * @return 0 if success, otherwise errorcode */ int tls_add_certf(struct tls *tls, const char *certf, const char *host) { struct tls_cert *uc; int err = 0; if (!tls || !certf) return EINVAL; uc = mem_zalloc(sizeof(*uc), tls_cert_destructor); if (!uc) return ENOMEM; if (str_isset(host)) { err = str_dup(&uc->host, host); if (err) goto err; } err = tls_ctx_alloc(&uc->ctx, TLS_METHOD_TLS, certf, NULL, NULL); if (err) goto err; X509_STORE *ca = SSL_CTX_get_cert_store(tls->ctx); if (ca) { X509_STORE_up_ref(ca); SSL_CTX_set_cert_store(uc->ctx, ca); } list_append(&tls->certs, &uc->le, uc); if (list_count(&tls->certs) == 1) tls_enable_sni(tls); return 0; err: ERR_clear_error(); mem_deref(uc); return err; } /** * Returns the X509 of the TLS certificate * * @param hc TLS certificate * * @return The OpenSSL X509 */ X509 *tls_cert_x509(struct tls_cert *hc) { return hc ? SSL_CTX_get0_certificate(hc->ctx) : NULL; } SSL_CTX *tls_cert_ctx(struct tls_cert *hc) { return hc ? hc->ctx : NULL; } /** * Returns the host name of the TLS certificate * * @param hc TLS certificate * * @return The host name */ const char *tls_cert_host(struct tls_cert *hc) { return hc ? hc->host : NULL; } /** * Returns the list of TLS certificates * * @param tls TLS context * * @return The list */ const struct list *tls_certs(const struct tls *tls) { return tls ? &tls->certs : NULL; } /** * Enable/disable posthandshake * Only on client side for TLSv1.3 * * @param tls tls object * @param value posthandshake auth value. 1 enabled, Default: 0 * */ void tls_set_posthandshake_auth(struct tls *tls, int value) { if (!tls) return; SSL_CTX_set_post_handshake_auth(tls->ctx, value); } /** * Request client certificate using post handshake * Only on client side for TLSv1.3 * * @param tc tls connection * * @return 0 if success, otherwise errorcode */ int tls_verify_client_post_handshake(struct tls_conn *tc) { int ret; int err = 0; if (!tc || !tc->ssl) return EINVAL; if (!(ret=SSL_verify_client_post_handshake(tc->ssl))) { err = EFAULT; DEBUG_WARNING("SSL_verify_client_post_handshake error: "\ "%m, ssl_err=%d\n", err, SSL_get_error(tc->ssl, ret)); ERR_clear_error(); return err; } if (!(ret = SSL_do_handshake(tc->ssl))) { err = EIO; DEBUG_WARNING("SSL_do_handshake error: "\ "%m, ssl_err=%d\n", err, SSL_get_error(tc->ssl, ret)); ERR_clear_error(); } return err; } /** * Set TLS session resumption mode * * @param tls TLS Object * @param mode TLS session resumption mode * * @return 0 if success, otherwise errorcode */ int tls_set_resumption(struct tls *tls, enum tls_resume_mode mode) { long ok = 1; if (!tls) return EINVAL; if (mode & TLS_RESUMPTION_IDS) { ok = SSL_CTX_set_session_cache_mode(tls->ctx, SSL_SESS_CACHE_SERVER); } else { ok = SSL_CTX_set_session_cache_mode(tls->ctx, SSL_SESS_CACHE_OFF); } if (mode & TLS_RESUMPTION_TICKETS) { ok |= SSL_CTX_clear_options(tls->ctx, SSL_OP_NO_TICKET); ok |= SSL_CTX_set_num_tickets(tls->ctx, 2); } else { ok |= SSL_CTX_set_options(tls->ctx, SSL_OP_NO_TICKET); ok |= SSL_CTX_set_num_tickets(tls->ctx, 0); } if (!ok) { ERR_clear_error(); return EFAULT; } return 0; } /** * Change used certificate+key of an existing SSL object * * @param tls TLS Object * @param chain Cert (chain) + Key in PEM format * @param len_chain Length of certificate + key PEM string * * @return int 0 if success, otherwise errorcode */ int tls_set_certificate_chain_pem(struct tls *tls, const char *chain, size_t len_chain) { STACK_OF(X509) *cert_stack = NULL; BIO *bio_mem = NULL; EVP_PKEY *pkey = NULL; X509 *leaf_cert = NULL; int err = ENOMEM; if (!tls || !chain || !len_chain) return EINVAL; bio_mem = BIO_new_mem_buf(chain, (int)len_chain); cert_stack = sk_X509_new_null(); if (!bio_mem || !cert_stack) goto out; X509 *cert; while ((cert = PEM_read_bio_X509(bio_mem, NULL, NULL, NULL)) != NULL) { int n = sk_X509_push(cert_stack, cert); if (n < 1) { X509_free(cert); goto out; } } err = EINVAL; if (sk_X509_num(cert_stack) == 0) goto out; leaf_cert = sk_X509_shift(cert_stack); long ok = SSL_CTX_use_certificate(tls->ctx, leaf_cert); if (ok <= 0) { X509_free(leaf_cert); goto out; } if (sk_X509_num(cert_stack)) { ok = SSL_CTX_clear_chain_certs(tls->ctx); if (!ok) goto out; while((cert = sk_X509_shift(cert_stack)) != NULL){ ok = SSL_CTX_add0_chain_cert(tls->ctx, cert); if (!ok) { X509_free(cert); goto out; } } } BIO_free(bio_mem); bio_mem = BIO_new_mem_buf(chain, (int)len_chain); if (!bio_mem) { err = ENOMEM; goto out; } pkey = PEM_read_bio_PrivateKey(bio_mem, NULL, NULL, NULL); if (!pkey) goto out; ok = SSL_CTX_use_PrivateKey(tls->ctx, pkey); if (ok <= 0) { err = EKEYREJECTED; goto out; } ok = SSL_CTX_check_private_key(tls->ctx); if (ok <= 0) goto out; if (tls->cert) X509_free(tls->cert); tls->cert = leaf_cert; leaf_cert = NULL; err = 0; out: if (bio_mem) BIO_free(bio_mem); if (leaf_cert) X509_free(leaf_cert); if (cert_stack) sk_X509_pop_free(cert_stack, X509_free); if (pkey) EVP_PKEY_free(pkey); if (err) ERR_clear_error(); return err; } /** * Change used certificate+key of an existing SSL object * * @param tls TLS Object * @param path Path to Cert (chain) + Key file (PEM format) * * @return int 0 if success, otherwise errorcode */ int tls_set_certificate_chain(struct tls *tls, const char *path) { X509 *cert; int ok = 0; if (!tls || !path) return EINVAL; ok = SSL_CTX_use_certificate_chain_file(tls->ctx, path); if (ok <= 0) { ERR_clear_error(); return ENOENT; } ok = SSL_CTX_use_PrivateKey_file(tls->ctx, path, SSL_FILETYPE_PEM); if (ok <= 0) { ERR_clear_error(); return EKEYREJECTED; } cert = SSL_CTX_get0_certificate(tls->ctx); if (!cert) { ERR_clear_error(); return ENOENT; } X509_up_ref(cert); if (tls->cert) X509_free(tls->cert); tls->cert = cert; return 0; } ================================================ FILE: src/tls/openssl/tls.h ================================================ /** * @file openssl/tls.h TLS backend using OpenSSL (Internal API) * * Copyright (C) 2010 Creytiv.com */ /* also defined by wincrypt.h */ #ifdef WIN32 #undef X509_NAME #endif /* * Mapping of feature macros */ #if defined(LIBRESSL_VERSION_NUMBER) typedef int (*SSL_verify_cb)(int preverify_ok, X509_STORE_CTX *x509_ctx); #else #define SSL_state SSL_get_state #define SSL_ST_OK TLS_ST_OK #endif #if OPENSSL_VERSION_MAJOR >= 4 typedef const X509_NAME*(tls_get_certfield_h)(const X509 *); #else typedef X509_NAME*(tls_get_certfield_h)(const X509 *); #endif struct tls; struct tls_cert; void tls_flush_error(void); SSL_CTX *tls_ssl_ctx(const struct tls *tls); X509 *tls_cert_x509(struct tls_cert *hc); SSL_CTX *tls_cert_ctx(struct tls_cert *hc); const char *tls_cert_host(struct tls_cert *hc); const struct list *tls_certs(const struct tls *tls); struct tls_cert *tls_cert_for_sni(const struct tls *tls, const char *sni); void tls_enable_sni(struct tls *tls); ================================================ FILE: src/tls/openssl/tls_tcp.c ================================================ /** * @file openssl/tls_tcp.c TLS/TCP backend using OpenSSL * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include "tls.h" #define DEBUG_MODULE "tls" #define DEBUG_LEVEL 5 #include /* NOTE: shadow struct defined in tls_*.c */ struct tls_conn { SSL *ssl; /* inheritance */ struct tls *tls; /* inheritance */ struct tls_conn_d cd; /* inheritance */ BIO_METHOD *biomet; BIO *sbio_out; BIO *sbio_in; struct tcp_helper *th; struct tcp_conn *tcp; bool active; bool up; }; static void destructor(void *arg) { struct tls_conn *tc = arg; if (tc->ssl) { int r = SSL_shutdown(tc->ssl); if (r <= 0) ERR_clear_error(); SSL_free(tc->ssl); } if (tc->biomet) BIO_meth_free(tc->biomet); mem_deref(tc->th); mem_deref(tc->tcp); } static int bio_create(BIO *b) { BIO_set_init(b, 1); BIO_set_data(b, NULL); BIO_set_flags(b, 0); return 1; } static int bio_destroy(BIO *b) { if (!b) return 0; BIO_set_init(b, 0); BIO_set_data(b, NULL); BIO_set_flags(b, 0); return 1; } static int bio_write(BIO *b, const char *buf, int len) { struct tls_conn *tc = BIO_get_data(b); struct mbuf mb; int err; mb.buf = (void *)buf; mb.pos = 0; mb.end = mb.size = len; err = tcp_send_helper(tc->tcp, &mb, tc->th); if (err) return -1; return len; } static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) { (void)b; (void)num; (void)ptr; if (cmd == BIO_CTRL_FLUSH) { /* The OpenSSL library needs this */ return 1; } return 0; } static BIO_METHOD *bio_method_tcp(void) { BIO_METHOD *method; method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "tcp_send"); if (!method) { DEBUG_WARNING("alloc: BIO_meth_new() failed\n"); ERR_clear_error(); return NULL; } BIO_meth_set_write(method, bio_write); BIO_meth_set_ctrl(method, bio_ctrl); BIO_meth_set_create(method, bio_create); BIO_meth_set_destroy(method, bio_destroy); return method; } static int tls_connect(struct tls_conn *tc) { int err = 0, r; ERR_clear_error(); r = SSL_connect(tc->ssl); if (r <= 0) { const int ssl_err = SSL_get_error(tc->ssl, r); switch (ssl_err) { case SSL_ERROR_WANT_READ: break; default: DEBUG_WARNING("connect: error (r=%d, ssl_err=%d)\n", r, ssl_err); tls_flush_error(); err = EPROTO; break; } ERR_clear_error(); } return err; } static int tls_accept(struct tls_conn *tc) { int err = 0, r; ERR_clear_error(); r = SSL_accept(tc->ssl); if (r <= 0) { const int ssl_err = SSL_get_error(tc->ssl, r); switch (ssl_err) { case SSL_ERROR_WANT_READ: break; default: DEBUG_WARNING("accept error: (r=%d, ssl_err=%d)\n", r, ssl_err); tls_flush_error(); err = EPROTO; break; } ERR_clear_error(); } return err; } static bool estab_handler(int *err, bool active, void *arg) { struct tls_conn *tc = arg; DEBUG_INFO("tcp established (active=%u)\n", active); if (!active) return true; tc->active = true; if (tls_get_session_reuse(tc)) (void) tls_reuse_session(tc); *err = tls_connect(tc); return true; } static bool recv_handler(int *err, struct mbuf *mb, bool *estab, void *arg) { struct tls_conn *tc = arg; int r; /* feed SSL data to the BIO */ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb)); if (r <= 0) { DEBUG_WARNING("recv: BIO_write %d\n", r); ERR_clear_error(); *err = ENOMEM; return true; } if (SSL_state(tc->ssl) != SSL_ST_OK) { if (tc->up && !SSL_get_secure_renegotiation_support(tc->ssl)) { *err = EPROTO; return true; } if (tc->active) { *err = tls_connect(tc); } else { *err = tls_accept(tc); } DEBUG_INFO("state: %s\n", SSL_state_string_long(tc->ssl)); /* TLS connection is established */ if (SSL_state(tc->ssl) != SSL_ST_OK) return true; *estab = true; tc->up = true; } mbuf_set_pos(mb, 0); for (;;) { int n; if (mbuf_get_space(mb) < 4096) { *err = mbuf_resize(mb, mb->size + 8192); if (*err) return true; } ERR_clear_error(); n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb)); if (n <= 0) { const int ssl_err = SSL_get_error(tc->ssl, n); ERR_clear_error(); switch (ssl_err) { case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_WANT_READ: break; default: *err = EPROTO; return true; } break; } mb->pos += n; } mbuf_set_end(mb, mb->pos); mbuf_set_pos(mb, 0); return false; } static bool send_handler(int *err, struct mbuf *mb, void *arg) { struct tls_conn *tc = arg; int r; ERR_clear_error(); r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb)); if (r <= 0) { DEBUG_WARNING("SSL_write: %d\n", SSL_get_error(tc->ssl, r)); ERR_clear_error(); *err = EPROTO; } return true; } /** * Change used certificate+key of an existing SSL object * * @param tc TLS connection object * @param file Cert+Key file * * @return int 0 if success, otherwise errorcode */ int tls_conn_change_cert(struct tls_conn *tc, const char *file) { if (!tc || !file) return EINVAL; #if !defined(LIBRESSL_VERSION_NUMBER) SSL_certs_clear(tc->ssl); int r = SSL_use_certificate_chain_file(tc->ssl, file); if (r <= 0) { ERR_clear_error(); return ENOENT; } r = SSL_use_PrivateKey_file(tc->ssl, file, SSL_FILETYPE_PEM); if (r <= 0) { ERR_clear_error(); return EKEYREJECTED; } return 0; #else return ENOSYS; #endif } /** * Start TLS on a TCP-connection * * @param ptc Pointer to allocated TLS connectioon * @param tls TLS Context * @param tcp TCP Connection * @param layer Protocol stack layer * * @return 0 if success, otherwise errorcode */ int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp, int layer) { struct tls_conn *tc; int err; if (!ptc || !tls || !tcp) return EINVAL; tc = mem_zalloc(sizeof(*tc), destructor); if (!tc) return ENOMEM; err = tcp_register_helper(&tc->th, tcp, layer, estab_handler, send_handler, recv_handler, tc); if (err) goto out; tc->tcp = mem_ref(tcp); tc->tls = tls; tc->biomet = bio_method_tcp(); if (!tc->biomet) { err = ENOMEM; goto out; } err = ENOMEM; /* Connect the SSL socket */ tc->ssl = SSL_new(tls_ssl_ctx(tls)); if (!tc->ssl) { DEBUG_WARNING("alloc: SSL_new() failed (ctx=%p)\n", tls_ssl_ctx(tls)); ERR_clear_error(); goto out; } tc->sbio_in = BIO_new(BIO_s_mem()); if (!tc->sbio_in) { DEBUG_WARNING("alloc: BIO_new() failed\n"); ERR_clear_error(); goto out; } tc->sbio_out = BIO_new(tc->biomet); if (!tc->sbio_out) { DEBUG_WARNING("alloc: BIO_new_socket() failed\n"); ERR_clear_error(); BIO_free(tc->sbio_in); goto out; } BIO_set_data(tc->sbio_out, tc); SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out); err = 0; out: if (err) mem_deref(tc); else *ptc = tc; return err; } /** * Get tcp connection * * @param tc TLS connection * * @return pointer to tcp connection struct */ const struct tcp_conn *tls_get_tcp_conn(const struct tls_conn *tc) { if (!tc) return NULL; return tc->tcp; } ================================================ FILE: src/tls/openssl/tls_udp.c ================================================ /** * @file openssl/tls_udp.c DTLS backend using OpenSSL * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tls.h" #define DEBUG_MODULE "dtls" #define DEBUG_LEVEL 5 #include enum { MTU_DEFAULT = 1400, MTU_FALLBACK = 548, }; /* TLS ContentType defined in RFC 5246 */ enum content_type { TYPE_CHANGE_CIPHER_SPEC = 20, TYPE_ALERT = 21, TYPE_HANDSHAKE = 22, TYPE_APPLICATION_DATA = 23 }; struct dtls_sock { struct sa peer; struct udp_helper *uh; struct udp_sock *us; struct hash *ht; struct mbuf *mb; dtls_conn_h *connh; void *arg; size_t mtu; bool single_conn; /* If enabled, only one DTLS connection */ }; /* NOTE: shadow struct defined in tls_*.c */ struct tls_conn { SSL *ssl; /* inheritance */ struct tls *tls; /* inheritance */ struct tls_conn_d cd; /* inheritance */ BIO_METHOD *biomet; BIO *sbio_out; BIO *sbio_in; struct tmr tmr; struct sa peer; struct le he; struct dtls_sock *sock; dtls_estab_h *estabh; dtls_recv_h *recvh; dtls_close_h *closeh; void *arg; bool active; bool up; }; #if DEBUG_LEVEL >= 6 static const char *content_type_str(enum content_type content_type) { switch (content_type) { case TYPE_CHANGE_CIPHER_SPEC: return "CHANGE_CIPHER_SPEC"; case TYPE_ALERT: return "ALERT"; case TYPE_HANDSHAKE: return "HANDSHAKE"; case TYPE_APPLICATION_DATA: return "APPLICATION_DATA"; default: return "???"; } } #endif static bool first_handler(struct le *le, void *arg) { (void)le; (void)arg; return true; /* stop on the first element */ } static struct le *hash_get_first(const struct hash *ht) { return hash_apply(ht, first_handler, NULL); } static int bio_create(BIO *b) { BIO_set_init(b, 1); BIO_set_data(b, NULL); BIO_set_flags(b, 0); return 1; } static int bio_destroy(BIO *b) { if (!b) return 0; BIO_set_init(b, 0); BIO_set_data(b, NULL); BIO_set_flags(b, 0); return 1; } static int bio_write(BIO *b, const char *buf, int len) { struct tls_conn *tc = BIO_get_data(b); struct mbuf *mb; enum {SPACE = 4}; int err; mb = mbuf_alloc(SPACE + len); if (!mb) return -1; mb->pos = SPACE; (void)mbuf_write_mem(mb, (void *)buf, len); mb->pos = SPACE; err = udp_send_helper(tc->sock->us, &tc->peer, mb, tc->sock->uh); mem_deref(mb); return err ? -1 : len; } static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) { struct tls_conn *tc = BIO_get_data(b); (void)num; (void)ptr; switch (cmd) { case BIO_CTRL_FLUSH: /* The OpenSSL library needs this */ return 1; #if defined (BIO_CTRL_DGRAM_QUERY_MTU) case BIO_CTRL_DGRAM_QUERY_MTU: return tc ? (long)tc->sock->mtu : MTU_DEFAULT; #endif #if defined (BIO_CTRL_DGRAM_GET_FALLBACK_MTU) case BIO_CTRL_DGRAM_GET_FALLBACK_MTU: return MTU_FALLBACK; #endif } return 0; } static BIO_METHOD *bio_method_udp(void) { BIO_METHOD *method; method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "udp_send"); if (!method) { DEBUG_WARNING("alloc: BIO_meth_new() failed\n"); ERR_clear_error(); return NULL; } BIO_meth_set_write(method, bio_write); BIO_meth_set_ctrl(method, bio_ctrl); BIO_meth_set_create(method, bio_create); BIO_meth_set_destroy(method, bio_destroy); return method; } static void tls_close(struct tls_conn *tc) { int r; if (!tc->ssl) return; r = SSL_shutdown(tc->ssl); if (r <= 0) ERR_clear_error(); SSL_free(tc->ssl); tc->ssl = NULL; } static void conn_destructor(void *arg) { struct tls_conn *tc = arg; hash_unlink(&tc->he); tmr_cancel(&tc->tmr); tls_close(tc); if (tc->biomet) BIO_meth_free(tc->biomet); mem_deref(tc->sock); } static void conn_close(struct tls_conn *tc, int err) { tmr_cancel(&tc->tmr); tls_close(tc); tc->up = false; if (tc->closeh) tc->closeh(err, tc->arg); } static void check_timer(struct tls_conn *tc); static void timeout(void *arg) { struct tls_conn *tc = arg; DEBUG_INFO("timeout\n"); if (0 <= DTLSv1_handle_timeout(tc->ssl)) { check_timer(tc); } else { ERR_clear_error(); conn_close(tc, ETIMEDOUT); } } static void check_timer(struct tls_conn *tc) { struct timeval tv = {0, 0}; if (1 == DTLSv1_get_timeout(tc->ssl, &tv)) { tmr_start(&tc->tmr, tv.tv_sec * 1000 + tv.tv_usec / 1000, timeout, tc); } else { tmr_cancel(&tc->tmr); } } static int tls_connect(struct tls_conn *tc) { int r; ERR_clear_error(); r = SSL_connect(tc->ssl); if (r <= 0) { const int ssl_err = SSL_get_error(tc->ssl, r); tls_flush_error(); switch (ssl_err) { case SSL_ERROR_WANT_READ: break; default: DEBUG_WARNING("connect error: %i\n", ssl_err); return EPROTO; } } check_timer(tc); return 0; } static int tls_accept(struct tls_conn *tc) { int r; ERR_clear_error(); r = SSL_accept(tc->ssl); if (r <= 0) { const int ssl_err = SSL_get_error(tc->ssl, r); tls_flush_error(); switch (ssl_err) { case SSL_ERROR_WANT_READ: break; default: DEBUG_WARNING("accept error: %i\n", ssl_err); return EPROTO; } } check_timer(tc); return 0; } static void conn_recv(struct tls_conn *tc, struct mbuf *mb) { int err, r; if (!tc->ssl) return; /* feed SSL data to the BIO */ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb)); if (r <= 0) { DEBUG_WARNING("receive bio write error: %i\n", r); ERR_clear_error(); conn_close(tc, ENOMEM); return; } if (SSL_state(tc->ssl) != SSL_ST_OK) { if (tc->up && !SSL_get_secure_renegotiation_support(tc->ssl)) { conn_close(tc, EPROTO); return; } if (tc->active) { err = tls_connect(tc); } else { err = tls_accept(tc); } if (err) { conn_close(tc, err); return; } DEBUG_INFO("%s: state=0x%04x\n", tc->active ? "client" : "server", SSL_state(tc->ssl)); /* TLS connection is established */ if (SSL_state(tc->ssl) != SSL_ST_OK) return; tc->up = true; if (tc->estabh) { uint32_t nrefs; mem_ref(tc); tc->estabh(tc->arg); nrefs = mem_nrefs(tc); mem_deref(tc); /* check if connection was deref'd from handler */ if (nrefs == 1) return; } } mbuf_set_pos(mb, 0); for (;;) { int n; if (mbuf_get_space(mb) < 4096) { err = mbuf_resize(mb, mb->size + 8192); if (err) { conn_close(tc, err); return; } } ERR_clear_error(); n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb)); if (n <= 0) { const int ssl_err = SSL_get_error(tc->ssl, n); ERR_clear_error(); switch (ssl_err) { case SSL_ERROR_WANT_READ: break; case SSL_ERROR_ZERO_RETURN: conn_close(tc, ECONNRESET); return; default: DEBUG_WARNING("read error: %i\n", ssl_err); conn_close(tc, EPROTO); return; } break; } mb->pos += n; } mbuf_set_posend(mb, 0, mb->pos); if (tc->recvh && mbuf_get_left(mb) > 0) tc->recvh(mb, tc->arg); } static int conn_alloc(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, const struct sa *peer, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { struct tls_conn *tc; int err = 0; if (sock->single_conn) { if (hash_get_first(sock->ht)) { DEBUG_WARNING("single: only one connection allowed\n"); return EMFILE; } } tc = mem_zalloc(sizeof(*tc), conn_destructor); if (!tc) return ENOMEM; hash_append(sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc); tc->sock = mem_ref(sock); tc->peer = *peer; tc->estabh = estabh; tc->recvh = recvh; tc->closeh = closeh; tc->arg = arg; tc->tls = tls; tc->biomet = bio_method_udp(); if (!tc->biomet) { err = ENOMEM; goto out; } /* Connect the SSL socket */ tc->ssl = SSL_new(tls_ssl_ctx(tls)); if (!tc->ssl) { DEBUG_WARNING("ssl new failed: %i\n", ERR_GET_REASON(ERR_get_error())); ERR_clear_error(); err = ENOMEM; goto out; } tc->sbio_in = BIO_new(BIO_s_mem()); if (!tc->sbio_in) { ERR_clear_error(); err = ENOMEM; goto out; } tc->sbio_out = BIO_new(tc->biomet); if (!tc->sbio_out) { ERR_clear_error(); BIO_free(tc->sbio_in); err = ENOMEM; goto out; } BIO_set_data(tc->sbio_out, tc); SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out); SSL_set_read_ahead(tc->ssl, 1); out: if (err) mem_deref(tc); else *ptc = tc; return err; } /** * DTLS Connect * * @param ptc Pointer to allocated DTLS connection * @param tls TLS Context * @param sock DTLS Socket * @param peer Peer address * @param estabh Establish handler * @param recvh Receive handler * @param closeh Close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dtls_connect(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, const struct sa *peer, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { struct tls_conn *tc; int err; if (!ptc || !tls || !sock || !peer) return EINVAL; err = conn_alloc(&tc, tls, sock, peer, estabh, recvh, closeh, arg); if (err) return err; tc->active = true; err = tls_connect(tc); if (err) goto out; out: if (err) mem_deref(tc); else *ptc = tc; return err; } /** * DTLS Accept * * @param ptc Pointer to allocated DTLS connection * @param tls TLS Context * @param sock DTLS Socket * @param estabh Establish handler * @param recvh Receive handler * @param closeh Close handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dtls_accept(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { struct tls_conn *tc; int err, r; if (!ptc || !tls || !sock || !sock->mb) return EINVAL; err = conn_alloc(&tc, tls, sock, &sock->peer, estabh, recvh, closeh, arg); if (err) return err; tc->active = false; r = BIO_write(tc->sbio_in, mbuf_buf(sock->mb), (int)mbuf_get_left(sock->mb)); if (r <= 0) { DEBUG_WARNING("accept bio write error: %i\n", r); ERR_clear_error(); err = ENOMEM; goto out; } err = tls_accept(tc); if (err) goto out; sock->mb = mem_deref(sock->mb); out: if (err) mem_deref(tc); else *ptc = tc; return err; } /** * Send data on a DTLS connection * * @param tc DTLS connection * @param mb Buffer to send * * @return 0 if success, otherwise errorcode */ int dtls_send(struct tls_conn *tc, struct mbuf *mb) { int r; if (!tc || !mb) return EINVAL; if (!tc->up || !tc->ssl) return ENOTCONN; ERR_clear_error(); r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb)); if (r <= 0) { DEBUG_WARNING("write error: %i\n", SSL_get_error(tc->ssl, r)); ERR_clear_error(); return EPROTO; } return 0; } /** * Set handlers on a DTLS Connection * * @param tc DTLS Connection * @param estabh DTLS Connection Established handler * @param recvh DTLS Connection Receive data handler * @param closeh DTLS Connection Close handler * @param arg Handler argument */ void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { if (!tc) return; tc->estabh = estabh; tc->recvh = recvh; tc->closeh = closeh; tc->arg = arg; } /** * Get the remote peer of a DTLS Connection * * @param tc DTLS Connection * * @return Remote peer */ const struct sa *dtls_peer(const struct tls_conn *tc) { return tc ? &tc->peer : NULL; } /** * Set the remote peer of a DTLS Connection * * @param tc DTLS Connection * @param peer Peer address */ void dtls_set_peer(struct tls_conn *tc, const struct sa *peer) { if (!tc || !peer) return; hash_unlink(&tc->he); hash_append(tc->sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc); tc->peer = *peer; } static void sock_destructor(void *arg) { struct dtls_sock *sock = arg; hash_clear(sock->ht); mem_deref(sock->uh); mem_deref(sock->us); mem_deref(sock->ht); mem_deref(sock->mb); } static bool cmp_handler(struct le *le, void *arg) { struct tls_conn *tc = le->data; return sa_cmp(&tc->peer, arg, SA_ALL); } static struct tls_conn *conn_lookup(struct dtls_sock *sock, const struct sa *peer) { if (sock->single_conn) { struct le *le = hash_get_first(sock->ht); return list_ledata(le); } return list_ledata(hash_lookup(sock->ht, sa_hash(peer, SA_ALL), cmp_handler, (void *)peer)); } static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) { struct dtls_sock *sock = arg; struct tls_conn *tc; uint8_t b; if (mbuf_get_left(mb) < 1) return false; b = mb->buf[mb->pos]; if (b < 20 || b > 63) return false; DEBUG_INFO("receive '%s' from %J\n", content_type_str(b), src); tc = conn_lookup(sock, src); if (tc) { conn_recv(tc, mb); return true; } if (sock->connh) { mem_deref(sock->mb); sock->mb = mem_ref(mb); sock->peer = *src; sock->connh(src, sock->arg); } return true; } /** * Create DTLS Socket * * @param sockp Pointer to returned DTLS Socket * @param laddr Local listen address (optional) * @param us External UDP socket (optional) * @param htsize Connection hash table size * @param layer UDP protocol layer * @param connh Connect handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr, struct udp_sock *us, uint32_t htsize, int layer, dtls_conn_h *connh, void *arg) { struct dtls_sock *sock; int err; if (!sockp) return EINVAL; sock = mem_zalloc(sizeof(*sock), sock_destructor); if (!sock) return ENOMEM; if (us) { sock->us = mem_ref(us); } else { err = udp_listen(&sock->us, laddr, NULL, NULL); if (err) goto out; } err = udp_register_helper(&sock->uh, sock->us, layer, NULL, recv_handler, sock); if (err) goto out; err = hash_alloc(&sock->ht, hash_valid_size(htsize)); if (err) goto out; sock->mtu = MTU_DEFAULT; sock->connh = connh; sock->arg = arg; out: if (err) mem_deref(sock); else *sockp = sock; return err; } /** * Get the underlying UDP socket of a DTLS Socket * * @param sock DTLS Socket * * @return UDP Socket */ struct udp_sock *dtls_udp_sock(struct dtls_sock *sock) { return sock ? sock->us : NULL; } /** * Set MTU on a DTLS Socket * * @param sock DTLS Socket * @param mtu MTU value */ void dtls_set_mtu(struct dtls_sock *sock, size_t mtu) { if (!sock) return; sock->mtu = mtu; } /** * Receive a DTLS packet * * @param sock DTLS Socket * @param src Source network address * @param mb Mbuffer with DTLS packet */ void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src, struct mbuf *mb) { struct sa addr; if (!sock || !src || !mb) return; addr = *src; recv_handler(&addr, mb, sock); } /** * Set single connection mode. If enabled, only one DTLS connection is allowed. * * @param sock DTLS Socket * @param single True to enable, False to disable */ void dtls_set_single(struct dtls_sock *sock, bool single) { if (!sock) return; sock->single_conn = single; } ================================================ FILE: src/tls/stub.c ================================================ /** * @file tls/stub.c TLS empty stub * # Copyright (C) 2023 Christian Spielberger */ #include #include #include #include #include #include int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile, const char *pwd) { (void)tlsp; (void)method; (void)keyfile; (void)pwd; return ENOSYS; } int tls_add_ca(struct tls *tls, const char *cafile) { (void)tls; (void)cafile; return ENOSYS; } int tls_add_cafile_path(struct tls *tls, const char *cafile, const char *capath) { (void)tls; (void)cafile; (void)capath; return ENOSYS; } int tls_add_capem(const struct tls *tls, const char *capem) { (void)tls; (void)capem; return ENOSYS; } int tls_add_crlpem(const struct tls *tls, const char *pem) { (void)tls; (void)pem; return ENOSYS; } int tls_set_selfsigned_ec(struct tls *tls, const char *cn, const char *curve_n) { (void)tls; (void)cn; (void)curve_n; return ENOSYS; } int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert, const char *key, size_t len_key) { (void)tls; (void)cert; (void)len_cert; (void)key; (void)len_key; return ENOSYS; } int tls_set_certificate(struct tls *tls, const char *pem, size_t len) { (void)tls; (void)pem; (void)len; return ENOSYS; } void tls_set_verify_client(struct tls *tls) { (void)tls; } void tls_set_verify_client_trust_all(struct tls *tls) { (void)tls; } int tls_set_verify_client_handler(struct tls_conn *tc, int depth, int (*verifyh) (int ok, void *arg), void *arg) { (void)tc; (void)depth; (void)verifyh; (void)arg; return ENOSYS; } int tls_set_srtp(struct tls *tls, const char *suites) { (void)tls; (void)suites; return ENOSYS; } int tls_fingerprint(const struct tls *tls, enum tls_fingerprint type, uint8_t *md, size_t size) { (void)tls; (void)type; (void)md; (void)size; return ENOSYS; } int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type, uint8_t *md, size_t size) { (void)tc; (void)type; (void)md; (void)size; return ENOSYS; } int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size) { (void)tc; (void)cn; (void)size; return ENOSYS; } int tls_set_verify_purpose(struct tls *tls, const char *purpose) { (void)tls; (void)purpose; return ENOSYS; } int tls_peer_verify(const struct tls_conn *tc) { (void)tc; return ENOSYS; } int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite, uint8_t *cli_key, size_t cli_key_size, uint8_t *srv_key, size_t srv_key_size) { (void)tc; (void)suite; (void)cli_key; (void)cli_key_size; (void)srv_key; (void)srv_key_size; return ENOSYS; } const char *tls_cipher_name(const struct tls_conn *tc) { (void)tc; return NULL; } int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count) { (void)tls; (void)cipherv; (void)count; return ENOSYS; } int tls_set_verify_server(struct tls_conn *tc, const char *host) { (void)tc; (void)host; return ENOSYS; } int tls_get_issuer(struct tls *tls, struct mbuf *mb) { (void)tls; (void)mb; return ENOSYS; } int tls_get_subject(struct tls *tls, struct mbuf *mb) { (void)tls; (void)mb; return ENOSYS; } void tls_disable_verify_server(struct tls *tls) { (void)tls; } int tls_set_min_proto_version(struct tls *tls, int version) { (void)tls; (void)version; return ENOSYS; } int tls_set_max_proto_version(struct tls *tls, int version) { (void)tls; (void)version; return ENOSYS; } int tls_set_session_reuse(struct tls *tls, int enabled) { (void)tls; (void)enabled; return ENOSYS; } bool tls_get_session_reuse(const struct tls_conn *tc) { (void)tc; return false; } int tls_reuse_session(const struct tls_conn *tc) { (void)tc; return ENOSYS; } bool tls_session_reused(const struct tls_conn *tc) { (void)tc; return false; } int tls_update_sessions(const struct tls_conn *tc) { (void)tc; return ENOSYS; } void tls_set_posthandshake_auth(struct tls *tls, int value) { (void)tls; (void)value; } int tls_conn_change_cert(struct tls_conn *tc, const char *file) { (void)tc; (void)file; return ENOSYS; } int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp, int layer) { (void)ptc; (void)tls; (void)tcp; (void)layer; return ENOSYS; } int tls_verify_client_post_handshake(struct tls_conn *tc) { (void)tc; return ENOSYS; } const struct tcp_conn *tls_get_tcp_conn(const struct tls_conn *tc) { (void)tc; return NULL; } int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr, struct udp_sock *us, uint32_t htsize, int layer, dtls_conn_h *connh, void *arg) { (void)sockp; (void)laddr; (void)us; (void)htsize; (void)layer; (void)connh; (void)arg; return ENOSYS; } struct udp_sock *dtls_udp_sock(struct dtls_sock *sock) { (void)sock; return NULL; } void dtls_set_mtu(struct dtls_sock *sock, size_t mtu) { (void)sock; (void)mtu; } int dtls_connect(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, const struct sa *peer, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { (void)ptc; (void)tls; (void)sock; (void)peer; (void)estabh; (void)recvh; (void)closeh; (void)arg; return ENOSYS; } int dtls_accept(struct tls_conn **ptc, struct tls *tls, struct dtls_sock *sock, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { (void)ptc; (void)tls; (void)sock; (void)estabh; (void)recvh; (void)closeh; (void)arg; return ENOSYS; } int dtls_send(struct tls_conn *tc, struct mbuf *mb) { (void)tc; (void)mb; return ENOSYS; } void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh, dtls_recv_h *recvh, dtls_close_h *closeh, void *arg) { (void)tc; (void)estabh; (void)recvh; (void)closeh; (void)arg; } const struct sa *dtls_peer(const struct tls_conn *tc) { (void)tc; return NULL; } void dtls_set_peer(struct tls_conn *tc, const struct sa *peer) { (void)tc; (void)peer; } void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src, struct mbuf *mb) { (void)sock; (void)src; (void)mb; } void dtls_set_single(struct dtls_sock *sock, bool single) { (void)sock; (void)single; } int tls_set_certificate_openssl(struct tls *tls, struct x509_st *cert, struct evp_pkey_st *pkey, bool up_ref) { (void)tls; (void)cert; (void)pkey; (void)up_ref; return ENOSYS; } int tls_add_certf(struct tls *tls, const char *certf, const char *host) { (void)tls; (void)certf; (void)host; return ENOSYS; } ================================================ FILE: src/tmr/tmr.c ================================================ /** * @file tmr.c Timer implementation * * Copyright (C) 2010 Creytiv.com */ #include #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #define DEBUG_MODULE "tmr" #define DEBUG_LEVEL 5 #include #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #endif /** Timer values */ enum { MAX_BLOCKING = 500 /**< Maximum time spent in handler [ms] */ }; struct tmrl { struct list list; mtx_t *lock; }; static void tmrl_destructor(void *arg) { struct tmrl *tmrl = arg; struct le *le; mtx_lock(tmrl->lock); LIST_FOREACH(&tmrl->list, le) { struct tmr *tmr = le->data; re_atomic_rls_set(&tmr->llock, (uintptr_t)NULL); } list_clear(&tmrl->list); mtx_unlock(tmrl->lock); mem_deref(tmrl->lock); } int tmrl_alloc(struct tmrl **tmrl) { struct tmrl *l; int err; if (!tmrl) return EINVAL; l = mem_zalloc(sizeof(struct tmrl), NULL); if (!l) return ENOMEM; list_init(&l->list); err = mutex_alloc(&l->lock); if (err) { mem_deref(l); return err; } mem_destructor(l, tmrl_destructor); *tmrl = l; return 0; } static bool inspos_handler(struct le *le, void *arg) { struct tmr *tmr = le->data; const uint64_t now = *(uint64_t *)arg; return tmr->jfs <= now; } static bool inspos_handler_0(struct le *le, void *arg) { struct tmr *tmr = le->data; const uint64_t now = *(uint64_t *)arg; return tmr->jfs > now; } #if TMR_DEBUG static void call_handler(tmr_h *th, void *arg) { const uint64_t tick = tmr_jiffies(); uint32_t diff; /* Call handler */ th(arg); diff = (uint32_t)(tmr_jiffies() - tick); if (diff > MAX_BLOCKING) { DEBUG_WARNING("long async blocking: %u>%u ms (h=%p arg=%p)\n", diff, MAX_BLOCKING, th, arg); } } #endif /** * Poll all timers in the current thread * * @param tmrl Timer list */ void tmr_poll(struct tmrl *tmrl) { const uint64_t jfs = tmr_jiffies(); if (!tmrl) return; for (;;) { struct tmr *tmr; tmr_h *th; void *th_arg; mtx_lock(tmrl->lock); tmr = list_ledata(tmrl->list.head); if (!tmr || (tmr->jfs > jfs)) { mtx_unlock(tmrl->lock); break; } th = tmr->th; th_arg = tmr->arg; tmr->th = NULL; list_unlink(&tmr->le); re_atomic_rls_set(&tmr->llock, (uintptr_t)NULL); mtx_unlock(tmrl->lock); if (!th) continue; #if TMR_DEBUG call_handler(th, th_arg); #else th(th_arg); #endif } } /** * Get the timer jiffies in microseconds * * @return Jiffies in [us] */ uint64_t tmr_jiffies_usec(void) { uint64_t jfs; #if defined(WIN32) LARGE_INTEGER li; static LARGE_INTEGER freq; if (!freq.QuadPart) QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&li); li.QuadPart *= 1000000; li.QuadPart /= freq.QuadPart; jfs = li.QuadPart; #else struct timespec now; clockid_t clock_id; /* Use CLOCK_MONOTONIC_RAW, if available, which is not subject to adjustment by NTP */ #ifdef CLOCK_MONOTONIC_RAW clock_id = CLOCK_MONOTONIC_RAW; #else clock_id = CLOCK_MONOTONIC; #endif if (0 != clock_gettime(clock_id, &now)) { DEBUG_WARNING("jiffies: clock_gettime() failed (%m)\n", errno); return 0; } jfs = (long)now.tv_sec * (uint64_t)1000000; jfs += now.tv_nsec/1000; #endif return jfs; } /** * Get the timer jiffies in milliseconds * * @return Jiffies in [ms] */ uint64_t tmr_jiffies(void) { return tmr_jiffies_usec() / 1000; } /** * Obtain the current realtime wallclock time in microseconds since UNIX epoch * * @return realtime wallclock time in microseconds since UNIX epoch */ uint64_t tmr_jiffies_rt_usec(void) { uint64_t jfs_rt; #if defined(WIN32) FILETIME now; GetSystemTimeAsFileTime(&now); jfs_rt = (((uint64_t)now.dwHighDateTime) << 32u) | (uint64_t)now.dwLowDateTime; jfs_rt -= 116444736000000000ull; jfs_rt /= 10u; #else struct timespec now; if (0 != clock_gettime(CLOCK_REALTIME, &now)) { DEBUG_WARNING("jiffies_rt: clock_gettime() failed (%m)\n", errno); return 0; } jfs_rt = (uint64_t)now.tv_sec * (uint64_t)1000000u; jfs_rt += now.tv_nsec / 1000; #endif return jfs_rt; } /** * Modifies the timespec object to current calendar time (TIME_UTC) * * @param tp Pointer to timespec object * @param offset Offset in [ms] * * @return 0 if success, otherwise errorcode */ int tmr_timespec_get(struct timespec *tp, uint64_t offset) { int err; if (!tp) return EINVAL; #if defined(WIN32) && !defined(__MINGW32__) err = (timespec_get(tp, TIME_UTC) == TIME_UTC) ? 0 : EINVAL; #else err = (clock_gettime(CLOCK_REALTIME, tp) == 0) ? 0 : errno; #endif if (err) return err; if (offset) { tp->tv_sec += (offset / 1000); tp->tv_nsec += ((offset * 1000000) % 1000000000LL); while (tp->tv_nsec > 1000000000LL) { tp->tv_sec += 1; tp->tv_nsec -= 1000000000LL; } } return 0; } /** * Get number of milliseconds until the next timer expires * * @param tmrl Timer-list * * @return Number of [ms], or 0 if no active timers */ uint64_t tmr_next_timeout(struct tmrl *tmrl) { const uint64_t jif = tmr_jiffies(); const struct tmr *tmr; uint64_t ret = 0; if (!tmrl) return 0; mtx_lock(tmrl->lock); tmr = list_ledata(tmrl->list.head); if (!tmr) goto out; if (tmr->jfs <= jif) ret = 1; else ret = tmr->jfs - jif; out: mtx_unlock(tmrl->lock); return ret; } int tmr_status(struct re_printf *pf, void *unused) { struct tmrl *tmrl = re_tmrl_get(); struct le *le; uint32_t n; int err = 0; (void)unused; if (!tmrl) return EINVAL; mtx_lock(tmrl->lock); n = list_count(&tmrl->list); if (!n) goto out; err = re_hprintf(pf, "Timers (%u):\n", n); for (le = tmrl->list.head; le; le = le->next) { const struct tmr *tmr = le->data; err |= re_hprintf(pf, " %p: th=%p expire=%llums file=%s:%d\n", tmr, tmr->th, (unsigned long long)tmr_get_expire(tmr), tmr->file, tmr->line); } if (n > 100) err |= re_hprintf(pf, " (Dumped Timers: %u)\n", n); out: mtx_unlock(tmrl->lock); return err; } /** * Print timer debug info to stderr */ void tmr_debug(void) { (void)re_fprintf(stderr, "%H", tmr_status, NULL); } /** * Initialise a timer object * * @param tmr Timer to initialise */ void tmr_init(struct tmr *tmr) { if (!tmr) return; memset(tmr, 0, sizeof(*tmr)); } static void tmr_startcont_dbg(struct tmr *tmr, uint64_t delay, bool syncnow, tmr_h *th, void *arg, const char *file, int line) { struct tmrl *tmrl = re_tmrl_get(); struct le *le; if (!tmr || !tmrl) return; /* use old list lock for unlinking */ uintptr_t lock_t = (uintptr_t)re_atomic_acq(&tmr->llock); mtx_t *lock = (mtx_t *)lock_t; if (!lock) lock = tmrl->lock; /* use current list lock */ mtx_lock(lock); if (tmr->th) list_unlink(&tmr->le); mtx_unlock(lock); lock = tmrl->lock; if (!th) { re_atomic_rls_set(&tmr->llock, (uintptr_t)NULL); return; } re_atomic_rls_set(&tmr->llock, (uintptr_t)tmrl->lock); mtx_lock(lock); tmr->th = th; tmr->arg = arg; tmr->file = file; tmr->line = line; if (syncnow) tmr->jfs = tmr_jiffies(); tmr->jfs += delay; if (delay == 0) { le = list_apply(&tmrl->list, true, inspos_handler_0, &tmr->jfs); if (le) { list_insert_before(&tmrl->list, le, &tmr->le, tmr); } else { list_append(&tmrl->list, &tmr->le, tmr); } } else { le = list_apply(&tmrl->list, false, inspos_handler, &tmr->jfs); if (le) { list_insert_after(&tmrl->list, le, &tmr->le, tmr); } else { list_prepend(&tmrl->list, &tmr->le, tmr); } } mtx_unlock(lock); } void tmr_start_dbg(struct tmr *tmr, uint64_t delay, tmr_h *th, void *arg, const char *file, int line) { tmr_startcont_dbg(tmr, delay, true, th, arg, file, line); } void tmr_continue_dbg(struct tmr *tmr, uint64_t delay, tmr_h *th, void *arg, const char *file, int line) { tmr_startcont_dbg(tmr, delay, false, th, arg, file, line); } /** * Cancel an active timer * * @param tmr Timer to cancel */ void tmr_cancel(struct tmr *tmr) { tmr_start(tmr, 0, NULL, NULL); } /** * Get the time left until timer expires * * @param tmr Timer object * * @return Time in [ms] until expiration */ uint64_t tmr_get_expire(const struct tmr *tmr) { uint64_t jfs; if (!tmr || !tmr->th) return 0; jfs = tmr_jiffies(); return (tmr->jfs > jfs) ? (tmr->jfs - jfs) : 0; } /** * Get current timer list count * * @param tmrl Timer list object * * @return timer list count */ uint32_t tmrl_count(struct tmrl *tmrl) { uint32_t c; if (!tmrl) return 0; mtx_lock(tmrl->lock); c = list_count(&tmrl->list); mtx_unlock(tmrl->lock); return c; } ================================================ FILE: src/trace/trace.c ================================================ /** * @file trace.c RE_TRACE helpers * JSON traces (chrome://tracing) */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTHREAD #include #endif #if defined(WIN32) #include #endif #ifdef LINUX #include #endif #ifdef HAVE_UNISTD_H #include #endif #define DEBUG_MODULE "trace" #define DEBUG_LEVEL 5 #include #ifndef TRACE_BUFFER_SIZE #define TRACE_BUFFER_SIZE 100000 #endif #ifndef TRACE_FLUSH_THRESHOLD #define TRACE_FLUSH_THRESHOLD 1000 #endif #ifndef TRACE_FLUSH_TMR #define TRACE_FLUSH_TMR 1000 #endif #ifdef RE_TRACE_ENABLED /** Trace configuration */ static struct { RE_ATOMIC bool init; int process_id; FILE *f; int event_count; struct re_trace_event_s *event_buffer; struct re_trace_event_s *event_buffer_flush; re_trace_line_h *trace_h; mtx_t lock; bool new; uint64_t start_time; struct tmr flush_tmr; bool flush_active; } trace = { .init = false }; static inline unsigned long get_thread_id(void) { #if defined(WIN32) return (unsigned long)GetCurrentThreadId(); #elif defined(LINUX) return (unsigned long)syscall(SYS_gettid); #elif defined(HAVE_PTHREAD) #if defined(DARWIN) || defined(FREEBSD) || defined(OPENBSD) || \ defined(NETBSD) || defined(DRAGONFLY) return (unsigned long)(void *)pthread_self(); #else return (unsigned long)pthread_self(); #endif #else return 0; #endif } static inline int get_process_id(void) { #if defined(WIN32) return (int)GetCurrentProcessId(); #else return (int)getpid(); #endif } static int flush_worker(void *arg) { (void)arg; re_trace_flush(); return 0; } static void flush_tmr(void *arg) { (void)arg; mtx_lock(&trace.lock); if (!trace.flush_active && trace.event_count >= TRACE_FLUSH_THRESHOLD) { re_thread_async(flush_worker, NULL, NULL); } mtx_unlock(&trace.lock); tmr_start(&trace.flush_tmr, TRACE_FLUSH_TMR, flush_tmr, NULL); } #endif /** * Init new trace json file * * @param json_file json file for trace events * * @return 0 if success, otherwise errorcode */ int re_trace_init(const char *json_file) { #ifdef RE_TRACE_ENABLED int err = 0; if (!json_file) return EINVAL; if (re_atomic_rlx(&trace.init)) return EALREADY; trace.event_buffer = mem_zalloc( TRACE_BUFFER_SIZE * sizeof(struct re_trace_event_s), NULL); if (!trace.event_buffer) return ENOMEM; trace.event_buffer_flush = mem_zalloc( TRACE_BUFFER_SIZE * sizeof(struct re_trace_event_s), NULL); if (!trace.event_buffer_flush) { trace.event_buffer = mem_deref(trace.event_buffer); return ENOMEM; } err = mtx_init(&trace.lock, mtx_plain) != thrd_success; if (err) { err = ENOMEM; goto out; } err = fs_fopen(&trace.f, json_file, "w+"); if (err) goto out; (void)re_fprintf(trace.f, "{\t\n\t\"traceEvents\": [\n"); (void)fflush(trace.f); trace.start_time = tmr_jiffies_usec(); re_atomic_rlx_set(&trace.init, true); trace.new = true; tmr_init(&trace.flush_tmr); tmr_start(&trace.flush_tmr, TRACE_FLUSH_TMR, flush_tmr, NULL); out: if (err) { re_atomic_rlx_set(&trace.init, false); trace.event_buffer = mem_deref(trace.event_buffer); trace.event_buffer_flush = mem_deref(trace.event_buffer_flush); } return err; #else (void)json_file; return 0; #endif } void re_set_trace_line_h(re_trace_line_h *trace_h) { #ifndef RE_TRACE_ENABLED (void)trace_h; #else trace.trace_h = trace_h; #endif } /** * Close and flush trace file * * @return 0 if success, otherwise errorcode */ int re_trace_close(void) { #ifdef RE_TRACE_ENABLED int err = 0; tmr_cancel(&trace.flush_tmr); re_trace_flush(); re_atomic_rlx_set(&trace.init, false); trace.event_buffer = mem_deref(trace.event_buffer); trace.event_buffer_flush = mem_deref(trace.event_buffer_flush); mtx_destroy(&trace.lock); (void)re_fprintf(trace.f, "\n\t]\n}\n"); if (trace.f) err = fclose(trace.f); if (err) return errno; trace.f = NULL; return 0; #else return 0; #endif } /** * Flush trace buffer (can be called multiple times) * * @return 0 if success, otherwise errorcode */ int re_trace_flush(void) { #ifdef RE_TRACE_ENABLED char *json_arg = NULL; char name[128] = {0}; char id_str[128] = {0}; int err = 0; if (!re_atomic_rlx(&trace.init)) return 0; mtx_lock(&trace.lock); if (trace.flush_active) { mtx_unlock(&trace.lock); return EALREADY; } trace.flush_active = true; struct re_trace_event_s *event_tmp = trace.event_buffer_flush; trace.event_buffer_flush = trace.event_buffer; trace.event_buffer = event_tmp; int flush_count = trace.event_count; trace.event_count = 0; mtx_unlock(&trace.lock); struct mbuf *mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } size_t json_arg_sz = 4096; json_arg = mem_zalloc(json_arg_sz, NULL); if (!json_arg) { err = ENOMEM; goto out; } for (int i = 0; i < flush_count; i++) { struct re_trace_event_s *e = &trace.event_buffer_flush[i]; switch (e->arg_type) { case RE_TRACE_ARG_NONE: json_arg[0] = '\0'; break; case RE_TRACE_ARG_INT: (void)re_snprintf(json_arg, json_arg_sz, ", \"args\":{\"%s\":%i}", e->arg_name, e->arg.a_int); break; case RE_TRACE_ARG_STRING_CONST: (void)re_snprintf(json_arg, json_arg_sz, ", \"args\":{\"%s\":\"%s\"}", e->arg_name, e->arg.a_str); break; case RE_TRACE_ARG_STRING_COPY: (void)re_snprintf(json_arg, json_arg_sz, ", \"args\":{\"%s\":\"%s\"}", e->arg_name, e->arg.a_str); mem_deref((void *)e->arg.a_str); break; } re_snprintf(name, sizeof(name), "\"name\":\"%s\"", e->name); if (e->id) { re_snprintf(id_str, sizeof(id_str), ", \"id\":\"%r\"", e->id); } mbuf_printf( mb, "{\"cat\":\"%s\",\"pid\":%i,\"tid\":%lu,\"ts\":%Lu," "\"ph\":\"%c\",%s%s%s}", e->cat, e->pid, e->tid, e->ts - trace.start_time, e->ph, name, e->id ? id_str : "", str_isset(json_arg) ? json_arg : ""); mbuf_set_pos(mb, 0); if (trace.trace_h) trace.trace_h(e, mb); mbuf_set_pos(mb, 0); (void)re_fprintf(trace.f, "%s%b", trace.new ? "" : ",\n", mbuf_buf(mb), mbuf_get_left(mb)); trace.new = false; mem_deref(e->id); mbuf_rewind(mb); } out: if (err) { for (int i = 0; i < flush_count; i++) { struct re_trace_event_s *e = &trace.event_buffer_flush[i]; if (e->arg_type == RE_TRACE_ARG_STRING_COPY) mem_deref((void *)e->arg.a_str); if (e->id) mem_deref(e->id); } } mem_deref(json_arg); mem_deref(mb); (void)fflush(trace.f); mtx_lock(&trace.lock); trace.flush_active = false; mtx_unlock(&trace.lock); return err; #else return 0; #endif } void re_trace_event(const char *cat, const char *name, char ph, struct pl *id, re_trace_arg_type arg_type, const char *arg_name, void *arg_value) { #ifdef RE_TRACE_ENABLED struct re_trace_event_s *e; if (!re_atomic_rlx(&trace.init)) return; mtx_lock(&trace.lock); if (trace.event_count >= TRACE_BUFFER_SIZE) { DEBUG_WARNING("Increase TRACE_BUFFER_SIZE\n"); mtx_unlock(&trace.lock); return; } e = &trace.event_buffer[trace.event_count]; ++trace.event_count; mtx_unlock(&trace.lock); e->ts = tmr_jiffies_usec(); e->id = mem_ref(id); e->ph = ph; e->cat = cat; e->name = name; e->pid = get_process_id(); e->tid = get_thread_id(); e->arg_type = arg_type; e->arg_name = arg_name; switch (arg_type) { case RE_TRACE_ARG_NONE: break; case RE_TRACE_ARG_INT: e->arg.a_int = (int)(intptr_t)arg_value; break; case RE_TRACE_ARG_STRING_CONST: e->arg.a_str = (const char *)arg_value; break; case RE_TRACE_ARG_STRING_COPY: str_dup((char **)&e->arg.a_str, (const char *)arg_value); break; } #else (void)cat; (void)name; (void)ph; (void)id; (void)arg_type; (void)arg_name; (void)arg_value; #endif } ================================================ FILE: src/trice/README.md ================================================ ICE-NOTES: --------- ---------------------------------- Application layer: - Can handle any modes (Full, Ice, Trickle) - Can implement any nomination-type (regular, aggressive) - Can encode/decode SDP attributes - Must gather all candidates (including STUN/TURN servers) - Application can choose the Default Local Candidate - Application can choose the Default Remote Candidate - Can measure RTT end-to-end - Can apply a STUN consent timer on top of ICE - the application should have the freedom to choose any selected candidate-pair - can install Keep-Alive timer (Binding Indication) for any pairs - can install a consent timer for any pair ---------------------------------- ICE layer: - All modes (Full-ICE and Trickle-ICE) are implemented - ICE-lite is NOT supported - agnostic to modes (Full, Trickle) - agnostic to nomination-type (regular, aggressive) - SDP encoding and decoding of ICE-relevant attributes - can handle between 1 and 2 components per media-stream - gathering: No candidate gathering, must be done in App - agnostic to transport protocol (should handle both UDP and TCP) - rel-addr (related address) is supported, but it is not used in the logic - ICE-stack does not choose the Default Local Candidate - ICE-stack does not choose the Default Remote Candidate - no TURN client - Each local candidate can have its own listen address/port. Yes - Support for UDP-transport - Support for TCP-transport - Support for IPv4 and IPv6 - modular design with building blocks. - check all components before calling estabh ? no. - no support for "default" candidate/candidate-pair - component object: NO - selected pair: NO - must be able to support custom UDP/TCP-transport via helpers ---------------------------------- Interop: OK - Firefox 31.0 OK - Firefox 35.0 OK - Firefox 36.0 OK - Chrome 41 OK - Chrome 58 TODO: done - remove ICE-lite mode done - remove rel-addr from ICE-code done - interop testing with Chrome done - interop testing with Firefox - seems to be working with 31.0 done - Firefox: test with trickling candidates over Websock done - add support for TCP-candidates (test with Chrome) done - make a new test-client (reicec) and test on a public server done - do we need to support LITE at all? No. done - split EOC flag into local_eoc and remote_eoc done - send triggered request from stunsrv done - new module "icesdp" for SDP encoding/decoding done - add a new module "shim" (RFC 4571) - ICE module should be Conncheck-only, no data-transport - test_ice_tcp: S-O not working on Linux done - check when adding PRFLX that EOC-flag is set/unset (not needed) done - move use_cand flag to checklist_start/send_conncheck ? - verify that APP can grab udp/tcp-sock - consider moving pacing-logic to application? Architecture Diagram: -------------------- ``` .-------. .-------. | App | | App | '-------' '-------' | | \ | .--------. | | +--------+ STUN +--------+ | | '--------' | | "ICE-layer" | .-------. | | | SHIM | | | '-------' | | | / .-------. .-------. | UDP | | TCP | '-------' '-------' | | ! ! ``` ================================================ FILE: src/trice/cand.c ================================================ /** * @file cand.c Common ICE Candidates * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" /** * Get the string for the ICE TCP type * * @param tcptype ICE tcp type * * @return String with ICE-TCP type name */ const char *ice_tcptype_name(enum ice_tcptype tcptype) { switch (tcptype) { case ICE_TCP_ACTIVE: return "active"; case ICE_TCP_PASSIVE: return "passive"; case ICE_TCP_SO: return "so"; default: return "???"; } } /** * Get the reverse TCP-type * * @param type ICE tcp type * * @return The reverse ICE-TCP type * * \verbatim Local Remote Candidate Candidate --------------------------- tcp-so tcp-so tcp-active tcp-passive tcp-passive tcp-active * \endverbatim */ enum ice_tcptype ice_tcptype_reverse(enum ice_tcptype type) { switch (type) { case ICE_TCP_SO: return ICE_TCP_SO; case ICE_TCP_ACTIVE: return ICE_TCP_PASSIVE; case ICE_TCP_PASSIVE: return ICE_TCP_ACTIVE; default: return (enum ice_tcptype)-1; } } /** * Get the base type of the candidate type * * @param type Candidate type * * @return Base candidate type */ enum ice_cand_type ice_cand_type_base(enum ice_cand_type type) { switch (type) { case ICE_CAND_TYPE_HOST: return ICE_CAND_TYPE_HOST; case ICE_CAND_TYPE_SRFLX: return ICE_CAND_TYPE_HOST; case ICE_CAND_TYPE_PRFLX: return ICE_CAND_TYPE_HOST; case ICE_CAND_TYPE_RELAY: return ICE_CAND_TYPE_RELAY; default: return type; } } /** * Print debug information for the ICE candidate * * @param pf Print function for debug output * @param cand ICE candidate * * @return 0 if success, otherwise errorcode */ int trice_cand_print(struct re_printf *pf, const struct ice_cand_attr *cand) { int err = 0; if (!cand) return 0; err |= re_hprintf(pf, "%s|%s", ice_cand_type2name(cand->type), net_proto2name(cand->proto)); if (cand->proto == IPPROTO_TCP) { err |= re_hprintf(pf, ".%s", ice_tcptype_name(cand->tcptype)); } err |= re_hprintf(pf, "|%J", &cand->addr); return err; } ================================================ FILE: src/trice/candpair.c ================================================ /** * @file candpair.c ICE Candidate Pairs * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "candpair" #define DEBUG_LEVEL 5 #include /* * generic routines to operate on "struct ice_candpair" * (for both checkl and validl) */ /* * g = controlling agent * d = controlled agent pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) */ static uint64_t ice_calc_pair_prio(uint32_t g, uint32_t d) { const uint64_t m = min(g, d); const uint64_t x = max(g, d); return (m<<32) + 2*x + (g>d?1:0); } static void candpair_destructor(void *arg) { struct ice_candpair *cp = arg; list_unlink(&cp->le); mem_deref(cp->lcand); mem_deref(cp->rcand); mem_deref(cp->tc); mem_deref(cp->conn); } static bool sort_handler(struct le *le1, struct le *le2, void *arg) { const struct ice_candpair *cp1 = le1->data, *cp2 = le2->data; (void)arg; return cp1->pprio >= cp2->pprio; } static void candpair_set_pprio(struct ice_candpair *cp, bool controlling) { uint32_t g, d; if (controlling) { g = cp->lcand->attr.prio; d = cp->rcand->attr.prio; } else { g = cp->rcand->attr.prio; d = cp->lcand->attr.prio; } cp->pprio = ice_calc_pair_prio(g, d); } /** * Add candidate pair to list, sorted by pair priority (highest is first) */ static void list_add_sorted(struct list *list, struct ice_candpair *cp) { struct le *le; /* find our slot */ for (le = list_tail(list); le; le = le->prev) { struct ice_candpair *cp0 = le->data; if (cp->pprio < cp0->pprio) { list_insert_after(list, le, &cp->le, cp); return; } } list_prepend(list, &cp->le, cp); } int trice_candpair_alloc(struct ice_candpair **cpp, struct trice *icem, struct ice_lcand *lcand, struct ice_rcand *rcand) { struct ice_candpair *cp; if (!icem || !lcand || !rcand) return EINVAL; if (icem->lrole == ICE_ROLE_UNKNOWN) { DEBUG_WARNING("trice_candpair_alloc: invalid local role!\n"); return EINVAL; } cp = mem_zalloc(sizeof(*cp), candpair_destructor); if (!cp) return ENOMEM; cp->lcand = mem_ref(lcand); cp->rcand = mem_ref(rcand); cp->state = ICE_CANDPAIR_FROZEN; candpair_set_pprio(cp, icem->lrole == ICE_ROLE_CONTROLLING); list_add_sorted(&icem->checkl, cp); if (cpp) *cpp = cp; return 0; } /* Computing Pair Priority and Ordering Pairs */ void trice_candpair_prio_order(struct list *lst, bool controlling) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; candpair_set_pprio(cp, controlling); } list_sort(lst, sort_handler, NULL); } void trice_candpair_make_valid(struct trice *icem, struct ice_candpair *pair) { if (!icem || !pair) return; if (pair->state == ICE_CANDPAIR_FAILED) { DEBUG_WARNING("make_valid: pair already FAILED [%H]\n", trice_candpair_debug, pair); } pair->err = 0; pair->scode = 0; pair->valid = true; trice_candpair_set_state(pair, ICE_CANDPAIR_SUCCEEDED); list_unlink(&pair->le); list_add_sorted(&icem->validl, pair); } void trice_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode) { if (!cp) return; if (cp->state == ICE_CANDPAIR_SUCCEEDED) { DEBUG_WARNING("set_failed(%m): pair already SUCCEEDED [%H]\n", err, trice_candpair_debug, cp); } cp->err = err; cp->scode = scode; cp->valid = false; cp->conn = mem_deref(cp->conn); trice_candpair_set_state(cp, ICE_CANDPAIR_FAILED); } void trice_candpair_set_state(struct ice_candpair *pair, enum ice_candpair_state state) { if (!pair) return; if (pair->state == state) return; if (trice_candpair_iscompleted(pair)) { DEBUG_WARNING("set_state(%s): pair is already completed" " [%H]\n", trice_candpair_state2name(state), trice_candpair_debug, pair); } #if 0 trice_printf(pair->lcand->icem, "%H new state \"%s\"\n", trice_candpair_debug, pair, trice_candpair_state2name(state)); #endif pair->state = state; } bool trice_candpair_iscompleted(const struct ice_candpair *cp) { if (!cp) return false; return cp->state == ICE_CANDPAIR_FAILED || cp->state == ICE_CANDPAIR_SUCCEEDED; } /** * Find the highest-priority candidate-pair in a given list, with * optional match parameters * * @param lst List of candidate pairs * @param lcand Local candidate (optional) * @param rcand Remote candidate (optional) * * @return Matching candidate pair if found, otherwise NULL * * note: assume list is sorted by priority */ struct ice_candpair *trice_candpair_find(const struct list *lst, const struct ice_lcand *lcand, const struct ice_rcand *rcand) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; if (!cp->lcand || !cp->rcand) { DEBUG_WARNING("corrupt candpair %p\n", cp); continue; } if (lcand && cp->lcand != lcand) continue; if (rcand && cp->rcand != rcand) continue; return cp; } return NULL; } /* find the first pair with a given state */ struct ice_candpair *trice_candpair_find_state(const struct list *lst, enum ice_candpair_state state) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; if (cp->state != state) continue; return cp; } return NULL; } bool trice_candpair_cmp_fnd(const struct ice_candpair *cp1, const struct ice_candpair *cp2) { if (!cp1 || !cp2) return false; return 0 == strcmp(cp1->lcand->attr.foundation, cp2->lcand->attr.foundation) && 0 == strcmp(cp1->rcand->attr.foundation, cp2->rcand->attr.foundation); } /* RFC 6544 -- 6.2. Forming the Check Lists Local Remote Candidate Candidate --------------------------- tcp-so tcp-so tcp-active tcp-passive tcp-passive tcp-active */ static bool tcptype_match(enum ice_tcptype loc, enum ice_tcptype rem) { if (loc == ICE_TCP_SO && rem == ICE_TCP_SO) return true; if (loc == ICE_TCP_ACTIVE && rem == ICE_TCP_PASSIVE) return true; if (loc == ICE_TCP_PASSIVE && rem == ICE_TCP_ACTIVE) return true; return false; } /* Replace server reflexive candidates by its base */ static const struct sa *cand_srflx_addr(const struct ice_lcand *cand) { if (ICE_CAND_TYPE_SRFLX == cand->attr.type) return &cand->base_addr; else return &cand->attr.addr; } static struct ice_candpair *find_same_base_list(const struct list *lst, const struct ice_lcand *lcand, const struct ice_rcand *rcand) { struct le *le; for (le = list_head(lst); le; le = le->next) { struct ice_candpair *cp = le->data; if (cp->lcand->attr.compid == lcand->attr.compid && cp->lcand->attr.proto == lcand->attr.proto && sa_cmp(cand_srflx_addr(cp->lcand), cand_srflx_addr(lcand), SA_ALL) && sa_cmp(&cp->rcand->attr.addr, &rcand->attr.addr, SA_ALL)) { return cp; } } return NULL; } /* look in both check-list and valid-list */ static struct ice_candpair *find_same_base(struct trice *icem, const struct ice_lcand *lcand, const struct ice_rcand *rcand) { struct ice_candpair *cp; cp = find_same_base_list(&icem->checkl, lcand, rcand); if (cp) return cp; cp = find_same_base_list(&icem->validl, lcand, rcand); if (cp) return cp; return NULL; } /* Pair a local candidate with a remote candidate */ static int create_pair(struct trice *icem, struct ice_lcand *lcand, struct ice_rcand *rcand) { struct ice_candpair *cpx; if (lcand->attr.compid != rcand->attr.compid || lcand->attr.proto != rcand->attr.proto || sa_af(&lcand->attr.addr) != sa_af(&rcand->attr.addr)) { return 0; } /* * IPv6 link-local: only pair with IPv6 link-local addresses * see: RFC5245bis, section 6.1.2.2 */ if (sa_af(&lcand->attr.addr) == AF_INET6 && sa_is_linklocal(&lcand->attr.addr) != sa_is_linklocal(&rcand->attr.addr)) { return 0; } /* loopback pairing optimization: only pair with loopback addresses */ if (icem->conf.optimize_loopback_pairing && sa_is_loopback(&lcand->attr.addr) != sa_is_loopback(&rcand->attr.addr)) { return 0; } cpx = find_same_base(icem, lcand, rcand); if (cpx) { trice_printf(icem, "with: pair with same" " base already exist" " (%H)\n", trice_candpair_debug, cpx); return 0; } if (lcand->attr.proto == IPPROTO_TCP) { if (!tcptype_match(lcand->attr.tcptype, rcand->attr.tcptype)) return 0; } /* add sorted */ return trice_candpair_alloc(NULL, icem, lcand, rcand); } /* Pair a candidate with all other candidates of the opposite kind */ int trice_candpair_with_local(struct trice *icem, struct ice_lcand *lcand) { struct list *lst = &icem->rcandl; struct le *le; int err = 0; if (icem->lrole == ICE_ROLE_UNKNOWN) { DEBUG_WARNING("trice_candpair_with_local: invalid local role!" "\n"); return EINVAL; } for (le = list_head(lst); le; le = le->next) { struct ice_rcand *rcand = le->data; err = create_pair(icem, lcand, rcand); if (err) goto out; } out: return err; } /* Pair a candidate with all other candidates of the opposite kind */ int trice_candpair_with_remote(struct trice *icem, struct ice_rcand *rcand) { struct list *lst = &icem->lcandl; struct le *le; int err = 0; if (icem->lrole == ICE_ROLE_UNKNOWN) { DEBUG_WARNING("trice_candpair_with_remote: invalid local role!" "\n"); return EINVAL; } for (le = list_head(lst); le; le = le->next) { struct ice_lcand *lcand = le->data; err = create_pair(icem, lcand, rcand); if (err) goto out; } out: return err; } int trice_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp) { int err; if (!cp) return 0; err = re_hprintf(pf, "{comp=%u} %10s {%c%c%c%c} %28H <---> %28H", cp->lcand->attr.compid, trice_candpair_state2name(cp->state), cp->valid ? 'V' : ' ', cp->nominated ? 'N' : ' ', cp->estab ? 'E' : ' ', cp->trigged ? 'T' : ' ', trice_cand_print, cp->lcand, trice_cand_print, cp->rcand); if (cp->err) err |= re_hprintf(pf, " (%m)", cp->err); if (cp->scode) err |= re_hprintf(pf, " [%u]", cp->scode); return err; } int trice_candpairs_debug(struct re_printf *pf, bool ansi_output, const struct list *list) { struct le *le; int err; if (!list) return 0; err = re_hprintf(pf, " (%u)\n", list_count(list)); for (le = list->head; le && !err; le = le->next) { const struct ice_candpair *cp = le->data; bool ansi = false; if (ansi_output) { if (cp->state == ICE_CANDPAIR_SUCCEEDED) { err |= re_hprintf(pf, "\x1b[32m"); ansi = true; } else if (cp->err || cp->scode) { err |= re_hprintf(pf, "\x1b[31m"); ansi = true; } } err |= re_hprintf(pf, " %H\n", trice_candpair_debug, cp); if (ansi) err |= re_hprintf(pf, "\x1b[;m"); } return err; } const char *trice_candpair_state2name(enum ice_candpair_state st) { switch (st) { case ICE_CANDPAIR_FROZEN: return "Frozen"; case ICE_CANDPAIR_WAITING: return "Waiting"; case ICE_CANDPAIR_INPROGRESS: return "InProgress"; case ICE_CANDPAIR_SUCCEEDED: return "Succeeded"; case ICE_CANDPAIR_FAILED: return "Failed"; default: return "???"; } } ================================================ FILE: src/trice/chklist.c ================================================ /** * @file chklist.c ICE Checklist * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "checklist" #define DEBUG_LEVEL 5 #include static void destructor(void *arg) { struct ice_checklist *ic = arg; tmr_cancel(&ic->tmr_pace); list_flush(&ic->conncheckl); /* flush before stun deref */ mem_deref(ic->stun); } static void pace_timeout(void *arg) { struct ice_checklist *ic = arg; struct trice *icem = (struct trice *)ic->icem; tmr_start(&ic->tmr_pace, ic->interval, pace_timeout, ic); trice_conncheck_schedule_check(icem); trice_checklist_update(icem); } int trice_checklist_start(struct trice *icem, struct stun *stun, uint32_t interval, trice_estab_h *estabh, trice_failed_h *failh, void *arg) { struct ice_checklist *ic; int err = 0; if (!icem) return EINVAL; if (icem->lrole == ICE_ROLE_UNKNOWN) { DEBUG_WARNING("trice_checklist_start: missing local role!\n"); return EINVAL; } if (icem->checklist) { ic = icem->checklist; if (!tmr_isrunning(&ic->tmr_pace)) { tmr_start(&ic->tmr_pace, 1, pace_timeout, ic); } return 0; } /* The password is equal to the password provided by the peer */ if (!str_isset(icem->rpwd)) { DEBUG_WARNING("start: remote password not set\n"); return EINVAL; } ic = mem_zalloc(sizeof(*ic), destructor); if (!ic) return ENOMEM; if (stun) { ic->stun = mem_ref(stun); } else { err = stun_alloc(&ic->stun, NULL, NULL, NULL); if (err) goto out; /* Update STUN Transport */ stun_conf(ic->stun)->rto = 100; stun_conf(ic->stun)->rc = 4; } tmr_init(&ic->tmr_pace); ic->interval = interval; ic->icem = icem; ic->estabh = estabh; ic->failh = failh; ic->arg = arg; ic->is_running = true; tmr_start(&ic->tmr_pace, 0, pace_timeout, ic); icem->checklist = ic; out: if (err) mem_deref(ic); return err; } void trice_checklist_stop(struct trice *icem) { struct ice_checklist *ic; if (!icem || !icem->checklist) return; ic = icem->checklist; ic->is_running = false; tmr_cancel(&ic->tmr_pace); } /* If all of the pairs in the check list are now either in the Failed or Succeeded state: */ bool trice_checklist_iscompleted(const struct trice *icem) { struct le *le; if (!icem) return false; for (le = icem->checkl.head; le; le = le->next) { const struct ice_candpair *cp = le->data; if (!trice_candpair_iscompleted(cp)) return false; } return true; } /* * Scheduling Checks */ void trice_conncheck_schedule_check(struct trice *icem) { struct ice_candpair *pair; bool use_cand; int err = 0; if (!icem) return; use_cand = false; /* Find the highest priority pair in that check list that is in the Waiting state. */ pair = trice_candpair_find_state(&icem->checkl, ICE_CANDPAIR_WAITING); if (pair) { err = trice_conncheck_send(icem, pair, use_cand); if (err) trice_candpair_failed(pair, err, 0); return; } /* If there is no such pair: */ /* Find the highest priority pair in that check list that is in the Frozen state. */ pair = trice_candpair_find_state(&icem->checkl, ICE_CANDPAIR_FROZEN); if (pair) { /* If there is such a pair: */ /* Unfreeze the pair. Perform a check for that pair, causing its state to transition to In-Progress. */ err = trice_conncheck_send(icem, pair, use_cand); if (err) trice_candpair_failed(pair, err, 0); return; } /* If there is no such pair: */ /* Terminate the timer for that check list. */ } /* * Computing States */ void trice_checklist_set_waiting(struct trice *icem) { struct le *le, *le2; if (!icem) return; if (icem->lrole == ICE_ROLE_UNKNOWN) { DEBUG_WARNING("trice_checklist_set_waiting: invalid local" "role!\n"); return; } /* For all pairs with the same foundation, it 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. */ for (le = icem->checkl.head; le; le = le->next) { struct ice_candpair *cp = le->data; for (le2 = icem->checkl.head; le2; le2 = le2->next) { struct ice_candpair *cp2 = le2->data; if (!trice_candpair_cmp_fnd(cp, cp2)) continue; if (cp2->lcand->attr.compid < cp->lcand->attr.compid && cp2->pprio > cp->pprio) cp = cp2; } if (cp->state == ICE_CANDPAIR_FROZEN) trice_candpair_set_state(cp, ICE_CANDPAIR_WAITING); } } int trice_checklist_update(struct trice *icem) { struct ice_checklist *ic; if (!icem) return EINVAL; ic = icem->checklist; if (!ic) return ENOSYS; if (trice_checklist_iscompleted(icem)) { tmr_cancel(&ic->tmr_pace); trice_printf(icem, "ICE checklist is complete" " (checkl=%u, valid=%u)\n", list_count(&icem->checkl), list_count(&icem->validl)); } return 0; } void trice_checklist_refresh(struct trice *icem) { struct ice_checklist *ic; if (!icem || !icem->checklist) return; ic = icem->checklist; tmr_start(&ic->tmr_pace, ic->interval, pace_timeout, ic); } bool trice_checklist_isrunning(const struct trice *icem) { struct ice_checklist *ic; if (!icem || !icem->checklist) return false; ic = icem->checklist; return ic->is_running; } int trice_checklist_debug(struct re_printf *pf, const struct ice_checklist *ic) { struct le *le; int err = 0; if (!ic) return 0; err |= re_hprintf(pf, " Checklist: %s, interval=%ums\n", tmr_isrunning(&ic->tmr_pace) ? "Running" : "Not-Running", ic->interval); err |= re_hprintf(pf, " Pending connchecks: %u\n", list_count(&ic->conncheckl)); for (le = ic->conncheckl.head; le; le = le->next) { struct ice_conncheck *cc = le->data; err |= re_hprintf(pf, " ...%H\n", trice_conncheck_debug, cc); } err |= stun_debug(pf, ic->stun); return err; } ================================================ FILE: src/trice/connchk.c ================================================ /** * @file connchk.c ICE Connectivity Checks * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "conncheck" #define DEBUG_LEVEL 5 #include enum {PRESZ_RELAY = 36}; static void conncheck_destructor(void *arg) { struct ice_conncheck *cc = arg; cc->term = true; list_unlink(&cc->le); mem_deref(cc->ct_conn); } static void pair_established(struct trice *icem, struct ice_candpair *pair, const struct stun_msg *msg) { struct ice_tcpconn *conn; if (!icem || !pair) return; if (pair->lcand->attr.proto == IPPROTO_TCP) { conn = pair->conn; if (!conn) { /* todo: Hack to grab TCPCONN */ conn = trice_conn_find(&icem->connl, pair->lcand->attr.compid, &pair->lcand->attr.addr, &pair->rcand->attr.addr); } if (conn) { pair->tc = mem_deref(pair->tc); pair->tc = mem_ref(conn->tc); } else { DEBUG_WARNING("pair_established: TCP-connection " " from %H to %H not found!\n", trice_cand_print, pair->lcand, trice_cand_print, pair->rcand); } } if (!pair->estab) { pair->estab = true; if (icem->checklist->estabh) { icem->checklist->estabh(pair, msg, icem->checklist->arg); } } } /* * NOTE for TCP-candidates: * * Note also that STUN responses received on an active TCP candidate * will typically produce a peer reflexive candidate. * * * NOTE for Trickle ICE and Peer Reflexive Candidates: * * With Trickle ICE, it is possible that * server reflexive candidates be discovered as peer reflexive in cases * where incoming connectivity checks are received from these candidates * before the trickle updates that carry them. * */ static void handle_success(struct trice *icem, struct ice_candpair *pair, const struct sa *mapped_addr, const struct stun_msg *msg, struct ice_conncheck *cc) { unsigned compid; int err; if (!icem || !pair || !pair->lcand) { DEBUG_WARNING("handle_success: invalid params\n"); return; } compid = pair->lcand->attr.compid; if (icem && !trice_lcand_find(icem, -1, compid, pair->lcand->attr.proto, mapped_addr)) { struct ice_lcand *lcand; struct ice_candpair *pair_prflx; uint32_t prio; prio = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, compid); err = trice_add_lcandidate(&lcand, icem, &icem->lcandl, compid, "FND", pair->lcand->attr.proto, prio, mapped_addr, &pair->lcand->attr.addr, ICE_CAND_TYPE_PRFLX, &pair->lcand->attr.addr, pair->lcand->attr.tcptype); if (err) { DEBUG_WARNING("failed to add PRFLX: %m\n", err); return; } if (str_isset(pair->lcand->ifname)) { str_ncpy(lcand->ifname, pair->lcand->ifname, sizeof(lcand->ifname)); } trice_printf(icem, "added PRFLX local candidate (%H)" " from base (%H)\n", trice_cand_print, lcand, trice_cand_print, pair->lcand); /* newly created Candidate-PAir */ err = trice_candpair_alloc(&pair_prflx, icem, lcand, pair->rcand); if (err) { DEBUG_WARNING("prflx alloc: %m\n", err); return; } lcand->us = mem_ref(pair->lcand->us); pair_prflx->conn = mem_ref(pair->conn); /* mark the original HOST-one as failed */ trice_candpair_failed(pair, 0, 0); trice_candpair_make_valid(icem, pair_prflx); pair_established(icem, pair_prflx, msg); return; } pair->state = ICE_CANDPAIR_FROZEN; trice_candpair_make_valid(icem, pair); /* Updating the Nominated Flag */ if (icem && ICE_ROLE_CONTROLLING == icem->lrole) { if (cc->use_cand) pair->nominated = true; } pair_established(icem, pair, msg); } static int print_err(struct re_printf *pf, const int *err) { if (err && *err) return re_hprintf(pf, " (%m)", *err); return 0; } static void stunc_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct ice_conncheck *cc = arg; struct ice_candpair *pair = cc->pair; struct trice *icem = cc->icem; struct stun_attr *attr; bool success = (err == 0) && (scode == 0); (void)reason; if (!icem) { DEBUG_WARNING("stun response: no icem\n"); } if (cc->term) return; trice_tracef(icem, success ? 32 : 31, "[%u] Rx %H <--- %H '%u %s'%H\n", pair->lcand->attr.compid, trice_cand_print, pair->lcand, trice_cand_print, pair->rcand, scode, reason, print_err, &err); if (err) { DEBUG_NOTICE("stun response: [%H --> %H] %m\n", trice_cand_print, pair->lcand, trice_cand_print, pair->rcand, err); trice_candpair_failed(pair, err, scode); goto out; } switch (scode) { case 0: /* Success case */ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr) { DEBUG_WARNING("no XOR-MAPPED-ADDR in response\n"); trice_candpair_failed(pair, EPROTO, 0); break; } handle_success(icem, pair, &attr->v.sa, msg, cc); break; case 487: /* Role Conflict */ trice_switch_local_role(icem); (void)trice_conncheck_send(icem, pair, cc->use_cand); break; default: trice_candpair_failed(pair, err, scode); break; } out: if (err || scode) { if (icem && icem->checklist) { icem->checklist->failh(err, scode, pair, icem->checklist->arg); } } mem_deref(cc); return; } int trice_conncheck_stun_request(struct ice_checklist *ic, struct ice_conncheck *cc, struct ice_candpair *cp, void *sock, bool cc_use_cand) { struct ice_lcand *lcand; struct trice *icem; char username_buf[256]; uint32_t prio_prflx; uint16_t ctrl_attr; bool use_cand = false; size_t presz = 0; int err = 0; if (!cp) return EINVAL; if (!ic) return ENOSYS; lcand = cp->lcand; icem = ic->icem; if (!sock) { DEBUG_NOTICE("conncheck: no SOCK\n"); return EINVAL; } /* The password is equal to the password provided by the peer */ if (!str_isset(icem->rpwd)) { DEBUG_WARNING("conncheck: remote password missing for" " raddr=%J\n", &cp->rcand->attr.addr); err = EINVAL; goto out; } if (lcand->attr.proto == IPPROTO_UDP && lcand->attr.type == ICE_CAND_TYPE_RELAY) presz = PRESZ_RELAY; else if (lcand->attr.proto == IPPROTO_TCP) presz = 2; if (re_snprintf(username_buf, sizeof(username_buf), "%s:%s", icem->rufrag, icem->lufrag) < 0) { DEBUG_WARNING("conncheck: username buffer too small\n"); err = ENOMEM; goto out; } /* PRIORITY and USE-CANDIDATE */ prio_prflx = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, lcand->attr.compid); switch (icem->lrole) { case ICE_ROLE_CONTROLLING: ctrl_attr = STUN_ATTR_CONTROLLING; use_cand = cc_use_cand; break; case ICE_ROLE_CONTROLLED: ctrl_attr = STUN_ATTR_CONTROLLED; break; default: DEBUG_WARNING("conncheck: invalid local role\n"); return EINVAL; } trice_tracef(icem, 36, "[%u] Tx [presz=%zu] %H ---> %H (%s) %s\n", lcand->attr.compid, presz, trice_cand_print, cp->lcand, trice_cand_print, cp->rcand, trice_candpair_state2name(cp->state), use_cand ? "[USE]" : ""); /* A connectivity check MUST utilize the STUN short term credential mechanism. */ err = stun_request(&cc->ct_conn, ic->stun, lcand->attr.proto, sock, &cp->rcand->attr.addr, presz, STUN_METHOD_BINDING, (uint8_t *)icem->rpwd, str_len(icem->rpwd), true, stunc_resp_handler, cc, 4, STUN_ATTR_USERNAME, username_buf, STUN_ATTR_PRIORITY, &prio_prflx, ctrl_attr, &icem->tiebrk, STUN_ATTR_USE_CAND, use_cand ? &use_cand : 0); if (err) { DEBUG_NOTICE("stun_request from %H to %H failed (%m)\n", trice_cand_print, lcand, trice_cand_print, cp->rcand, err); goto out; } out: if (err) { trice_candpair_failed(cp, err, 0); } return err; } static bool tcpconn_frame_handler(struct trice *icem, struct tcp_conn *tc, struct sa *src, struct mbuf *mb, void *arg) { struct ice_lcand *lcand = arg; return trice_stun_process(icem, lcand, IPPROTO_TCP, tc, src, mb); } int trice_conncheck_send(struct trice *icem, struct ice_candpair *pair, bool use_cand) { struct ice_checklist *ic; struct ice_lcand *lcand; struct ice_tcpconn *conn; struct ice_conncheck *cc = NULL; void *sock; int err = 0; if (!icem || !pair) return EINVAL; lcand = pair->lcand; ic = icem->checklist; if (!ic) { DEBUG_WARNING("conncheck_send: no checklist\n"); return EINVAL; } cc = mem_zalloc(sizeof(*cc), conncheck_destructor); if (!cc) return ENOMEM; cc->icem = icem; cc->pair = pair; cc->use_cand = use_cand; if (pair->state < ICE_CANDPAIR_INPROGRESS) trice_candpair_set_state(pair, ICE_CANDPAIR_INPROGRESS); switch (pair->lcand->attr.proto) { case IPPROTO_UDP: sock = trice_lcand_sock(icem, lcand); err = trice_conncheck_stun_request(ic, cc, pair, sock, use_cand); if (err) goto out; break; case IPPROTO_TCP: conn = trice_conn_find(&icem->connl, lcand->attr.compid, &pair->lcand->attr.addr, &pair->rcand->attr.addr); if (conn) { trice_printf(icem, "TCP-connection" " already exist [%H]\n", trice_conn_debug, conn); pair->conn = mem_ref(conn); /* todo: */ err = trice_conncheck_stun_request(ic, cc, pair, conn->tc, use_cand); if (err) goto out; break; } switch (pair->lcand->attr.tcptype) { case ICE_TCP_ACTIVE: case ICE_TCP_SO: err = trice_conn_alloc(&icem->connl, icem, lcand->attr.compid, true, &lcand->attr.addr, &pair->rcand->attr.addr, lcand->ts, lcand->layer, tcpconn_frame_handler, lcand); if (err) { DEBUG_NOTICE("trice_conn_alloc to" " %J failed (%m)\n", &pair->rcand->attr.addr, err); goto out; } break; case ICE_TCP_PASSIVE: /* do nothing now. */ /* we must wait for the other side to create a TCP-connection to us. when this TCP-connection is established, we can then send our Connectivity-check */ trice_candpair_set_state(pair, ICE_CANDPAIR_INPROGRESS); break; } break; default: err = EPROTONOSUPPORT; goto out; } list_append(&ic->conncheckl, &cc->le, cc); out: if (err) { mem_deref(cc); trice_candpair_failed(pair, err, 0); } return err; } int trice_conncheck_trigged(struct trice *icem, struct ice_candpair *pair, void *sock, bool use_cand) { struct ice_checklist *ic; struct ice_conncheck *cc = NULL; int err = 0; if (!icem || !pair) return EINVAL; ic = icem->checklist; if (!ic) { DEBUG_WARNING("conncheck_send: no checklist\n"); return EINVAL; } cc = mem_zalloc(sizeof(*cc), conncheck_destructor); if (!cc) return ENOMEM; cc->icem = icem; cc->pair = pair; cc->use_cand = use_cand; if (pair->state < ICE_CANDPAIR_INPROGRESS) trice_candpair_set_state(pair, ICE_CANDPAIR_INPROGRESS); err = trice_conncheck_stun_request(icem->checklist, cc, pair, sock, use_cand); if (err) goto out; list_append(&ic->conncheckl, &cc->le, cc); out: if (err) { mem_deref(cc); trice_candpair_failed(pair, err, 0); } return err; } int trice_conncheck_debug(struct re_printf *pf, const struct ice_conncheck *cc) { if (!cc) return 0; return re_hprintf(pf, "proto=%s stun=%p use_cand=%d" " state=%s" , net_proto2name(cc->pair->lcand->attr.proto), cc->ct_conn, cc->use_cand, trice_candpair_state2name(cc->pair->state)); } ================================================ FILE: src/trice/lcand.c ================================================ /** * @file lcand.c Local ICE Candidates * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "icelcand" #define DEBUG_LEVEL 5 #include static bool tcpconn_frame_handler(struct trice *icem, struct tcp_conn *tc, struct sa *src, struct mbuf *mb, void *arg) { struct ice_lcand *lcand = arg; (void)icem; return lcand->recvh(lcand, IPPROTO_TCP, tc, src, mb, lcand->arg); } static void tcp_conn_handler(const struct sa *peer, void *arg) { struct ice_lcand *lcand = arg; int err; #if 0 trice_printf(lcand->icem, "[local=%H] incoming TCP-connect from %J\n", trice_cand_print, lcand, peer); #endif err = trice_conn_alloc(&lcand->icem->connl, lcand->icem, lcand->attr.compid, false, &lcand->attr.addr, peer, lcand->ts, lcand->layer, tcpconn_frame_handler, lcand); if (err) { DEBUG_WARNING("ice_conn_alloc error (%m)\n", err); } } static void lcand_destructor(void *arg) { struct ice_lcand *cand = arg; list_unlink(&cand->le); mem_deref(cand->ts); mem_deref(cand->uh); mem_deref(cand->us); } /** Foundation is a hash of IP address and candidate type */ static int compute_foundation(struct ice_lcand *cand, const struct sa *addr, enum ice_cand_type type) { uint32_t v; v = sa_hash(addr, SA_ADDR); v ^= type; if (re_snprintf(cand->attr.foundation, sizeof(cand->attr.foundation), "%08x", v) < 0) return ENOMEM; return 0; } static bool trice_lcand_recv_handler(struct ice_lcand *lcand, int proto, void *sock, const struct sa *src, struct mbuf *mb, void *arg) { struct trice *icem = arg; return trice_stun_process(icem, lcand, proto, sock, src, mb); } int trice_add_lcandidate(struct ice_lcand **candp, struct trice *icem, struct list *lst, unsigned compid, char *foundation, int proto, uint32_t prio, const struct sa *addr, const struct sa *base_addr, enum ice_cand_type type, const struct sa *rel_addr, enum ice_tcptype tcptype) { struct ice_lcand *cand; int err = 0; if (!lst || !compid || !proto || !addr) return EINVAL; cand = mem_zalloc(sizeof(*cand), lcand_destructor); if (!cand) return ENOMEM; cand->attr.compid = compid; if (foundation) str_ncpy(cand->attr.foundation, foundation, sizeof(cand->attr.foundation)); else err = compute_foundation(cand, addr, type); cand->attr.proto = proto; cand->attr.prio = prio; cand->attr.addr = *addr; cand->attr.type = type; cand->attr.tcptype = tcptype; if (rel_addr) cand->attr.rel_addr = *rel_addr; if (err) goto out; cand->icem = icem; cand->recvh = trice_lcand_recv_handler; cand->arg = icem; if (base_addr) cand->base_addr = *base_addr; list_append(lst, &cand->le, cand); out: if (err) mem_deref(cand); else if (candp) *candp = cand; return err; } /* this one is only for Send statistics on Local Candidate */ static bool udp_helper_send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) { struct ice_lcand *lcand = arg; (void)err; (void)dst; (void)mb; lcand->stats.n_tx += 1; return false; /* NOT handled */ } /* * lcand: on which Local Candidate to receive the packet * * return TRUE if handled */ static bool udp_helper_recv_handler(struct sa *src, struct mbuf *mb, void *arg) { struct ice_lcand *lcand = arg; lcand->stats.n_rx += 1; return lcand->recvh(lcand, IPPROTO_UDP, lcand->us, src, mb, lcand->arg); } /* The incoming data should not get here */ static void dummy_udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct ice_lcand *lcand = arg; DEBUG_NOTICE("@@@@ NO-ONE cared about this UDP packet? @@@@@" " (%zu bytes from %J to %s.%J)\n", mbuf_get_left(mb), src, ice_cand_type2name(lcand->attr.type), &lcand->attr.addr); } static int udp_listen_range(struct udp_sock **usp, const struct sa *ip, uint16_t min_port, uint16_t max_port, udp_recv_h *rh, void *arg) { struct sa laddr; int tries = 64; int err = 0; sa_cpy(&laddr, ip); /* try hard */ while (tries--) { struct udp_sock *us; uint16_t port; port = (min_port + (rand_u16() % (max_port - min_port))); sa_set_port(&laddr, port); err = udp_listen(&us, &laddr, rh, arg); if (err) continue; /* OK */ if (usp) *usp = us; break; } return err; } /* * you can call this at any time * * @param addr HOST: SA_ADDR portion is used * non-HOST: SA_ADDR + SA_PORT portion is used * * @param base_addr Optional * @param rel_addr Optional * * @param layer mandatory for HOST and RELAY candidates */ int trice_lcand_add(struct ice_lcand **lcandp, struct trice *icem, unsigned compid, int proto, uint32_t prio, const struct sa *addr, const struct sa *base_addr, enum ice_cand_type type, const struct sa *rel_addr, enum ice_tcptype tcptype, void *sock, int layer) { struct ice_lcand *lcand; int err = 0; if (!icem || !compid || !proto || !addr) return EINVAL; if (!sa_isset(addr, SA_ADDR)) { DEBUG_WARNING("lcand_add: SA_ADDR is not set\n"); return EINVAL; } if (type != ICE_CAND_TYPE_HOST) { if (!sa_isset(addr, SA_PORT)) { DEBUG_WARNING("lcand_add: %s: SA_PORT" " must be set (%J)\n", ice_cand_type2name(type), addr); return EINVAL; } } /* lookup candidate, replace if PRIO is higher */ /* TODO: dont look up TCP-ACTIVE types for now (port is zero) */ if (proto == IPPROTO_UDP) { lcand = trice_lcand_find(icem, -1, compid, proto, addr); if (lcand) { trice_printf(icem, "add_local[%s.%J] --" " candidate already exists" " (%H)\n", ice_cand_type2name(type), addr, trice_cand_print, lcand); if (prio > lcand->attr.prio) lcand = mem_deref(lcand); else { goto out; } } } err = trice_add_lcandidate(&lcand, icem, &icem->lcandl, compid, NULL, proto, prio, addr, base_addr, type, rel_addr, tcptype); if (err) return err; if (type == ICE_CAND_TYPE_HOST) { switch (proto) { case IPPROTO_UDP: if (sock) { struct sa laddr; lcand->us = mem_ref(sock); err = udp_local_get(lcand->us, &laddr); if (err) goto out; lcand->attr.addr = *addr; sa_set_port(&lcand->attr.addr, sa_port(&laddr)); } else { if (icem->ports.min && icem->ports.max) { err = udp_listen_range( &lcand->us, addr, icem->ports.min, icem->ports.max, dummy_udp_recv, lcand); } else { err = udp_listen(&lcand->us, addr, dummy_udp_recv, lcand); } if (err) goto out; err = udp_local_get(lcand->us, &lcand->attr.addr); if (err) goto out; } err = udp_register_helper(&lcand->uh, lcand->us, layer, udp_helper_send_handler, udp_helper_recv_handler, lcand); if (err) goto out; break; case IPPROTO_TCP: /* TCP-transport has 3 variants: active, passive, so */ if (lcand->attr.tcptype == ICE_TCP_ACTIVE) { /* the port MUST be set to 9 (i.e., Discard) */ /*sa_set_port(&lcand->attr.addr, 9); */ } else if (lcand->attr.tcptype == ICE_TCP_PASSIVE || lcand->attr.tcptype == ICE_TCP_SO) { err = tcp_listen(&lcand->ts, addr, tcp_conn_handler, lcand); if (err) goto out; err = tcp_local_get(lcand->ts, &lcand->attr.addr); if (err) goto out; } else { err = EPROTONOSUPPORT; goto out; } break; default: err = EPROTONOSUPPORT; goto out; } } else if (type == ICE_CAND_TYPE_RELAY) { switch (proto) { case IPPROTO_UDP: if (sock) { lcand->us = mem_ref(sock); } else { err = udp_listen(&lcand->us, NULL, dummy_udp_recv, lcand); if (err) goto out; } err = udp_register_helper(&lcand->uh, lcand->us, layer, udp_helper_send_handler, udp_helper_recv_handler, lcand); if (err) goto out; break; default: err = EPROTONOSUPPORT; goto out; } } else if (type == ICE_CAND_TYPE_SRFLX || type == ICE_CAND_TYPE_PRFLX) { /* Special case for SRFLX UDP candidates, if he has * its own UDP-socket that can be used. */ if (proto == IPPROTO_UDP && sock) { lcand->us = mem_ref(sock); err = udp_register_helper(&lcand->uh, lcand->us, layer, udp_helper_send_handler, udp_helper_recv_handler, lcand); if (err) goto out; } } lcand->layer = layer; if (icem->lrole != ICE_ROLE_UNKNOWN) { /* pair this local-candidate with all existing * remote-candidates */ err = trice_candpair_with_local(icem, lcand); if (err) goto out; /* new pair -- refresh the checklist timer */ trice_checklist_refresh(icem); } out: if (err) mem_deref(lcand); else if (lcandp) *lcandp = lcand; return err; } struct ice_lcand *trice_lcand_find(struct trice *icem, enum ice_cand_type type, unsigned compid, int proto, const struct sa *addr) { struct list *lst; struct le *le; if (!icem) return NULL; if (!proto) { DEBUG_WARNING("find_candidate: invalid args\n"); return NULL; } lst = &icem->lcandl; for (le = list_head(lst); le; le = le->next) { struct ice_cand_attr *cand = le->data; if (type != (enum ice_cand_type)-1 && type != cand->type) continue; if (compid && cand->compid != compid) continue; if (cand->proto != proto) continue; if (addr && !sa_cmp(&cand->addr, addr, SA_ALL)) continue; return (void *)cand; } return NULL; } struct ice_lcand *trice_lcand_find2(const struct trice *icem, enum ice_cand_type type, int af) { struct le *le; if (!icem) return NULL; for (le = list_head(&icem->lcandl); le; le = le->next) { struct ice_cand_attr *cand = le->data; if (cand->type != type) continue; if (af != sa_af(&cand->addr)) continue; return (void *)cand; } return NULL; } int trice_lcands_debug(struct re_printf *pf, const struct list *lst) { struct le *le; int err; err = re_hprintf(pf, " (%u)\n", list_count(lst)); for (le = list_head(lst); le && !err; le = le->next) { const struct ice_lcand *cand = le->data; err |= re_hprintf(pf, " {%u} [tx=%3zu, rx=%3zu] " "fnd=%-8s prio=%08x ", cand->attr.compid, cand->stats.n_tx, cand->stats.n_rx, cand->attr.foundation, cand->attr.prio); if (str_isset(cand->ifname)) err |= re_hprintf(pf, "%s:", cand->ifname); err |= re_hprintf(pf, "%24H", trice_cand_print, cand); if (sa_isset(&cand->base_addr, SA_ADDR)) { err |= re_hprintf(pf, " (base-addr = %J)", &cand->base_addr); } if (sa_isset(&cand->attr.rel_addr, SA_ADDR)) { err |= re_hprintf(pf, " (rel-addr = %J)", &cand->attr.rel_addr); } err |= re_hprintf(pf, "\n"); } return err; } void *trice_lcand_sock(struct trice *icem, const struct ice_lcand *lcand) { struct ice_lcand *base = NULL; if (!icem || !lcand) return NULL; if (sa_isset(&lcand->base_addr, SA_ALL)) { enum ice_cand_type base_type; base_type = ice_cand_type_base(lcand->attr.type); base = trice_lcand_find(icem, base_type, lcand->attr.compid, lcand->attr.proto, &lcand->base_addr); } /* note: original lcand has precedence, fallback to base-candidate */ switch (lcand->attr.type) { case ICE_CAND_TYPE_HOST: return lcand->us; case ICE_CAND_TYPE_SRFLX: case ICE_CAND_TYPE_PRFLX: if (lcand->us) return lcand->us; else if (base && base->us) return base->us; else { DEBUG_NOTICE("lcand_sock: no SOCK or BASE for " " type '%s'\n", ice_cand_type2name(lcand->attr.type)); return NULL; } case ICE_CAND_TYPE_RELAY: return lcand->us; default: return NULL; } return NULL; } void trice_lcand_recv_packet(struct ice_lcand *lcand, const struct sa *src, struct mbuf *mb) { struct sa addr; if (!lcand || !src || !mb) return; addr = *src; udp_helper_recv_handler(&addr, mb, lcand); } ================================================ FILE: src/trice/rcand.c ================================================ /** * @file rcand.c Remote ICE Candidates * * Copyright (C) 2010 - 2015 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "rcand" #define DEBUG_LEVEL 5 #include static void rcand_destructor(void *data) { struct ice_rcand *cand = data; list_unlink(&cand->le); } static int trice_add_rcandidate(struct ice_rcand **candp, struct list *lst, unsigned compid, const char *foundation, int proto, uint32_t prio, const struct sa *addr, enum ice_cand_type type, enum ice_tcptype tcptype) { struct ice_rcand *cand; if (!lst || !compid || !foundation || !proto || !addr) return EINVAL; cand = mem_zalloc(sizeof(*cand), rcand_destructor); if (!cand) return ENOMEM; cand->attr.compid = compid; cand->attr.proto = proto; cand->attr.prio = prio; cand->attr.addr = *addr; cand->attr.type = type; cand->attr.tcptype = tcptype; str_ncpy(cand->attr.foundation, foundation, sizeof(cand->attr.foundation)); list_append(lst, &cand->le, cand); *candp = cand; return 0; } /* you can call this at any time */ int trice_rcand_add(struct ice_rcand **rcandp, struct trice *icem, unsigned compid, const char *foundation, int proto, uint32_t prio, const struct sa *addr, enum ice_cand_type type, enum ice_tcptype tcptype) { struct ice_rcand *rcand; int sa_flags = SA_ADDR; int err = 0; if (!icem || !foundation) return EINVAL; if (proto == IPPROTO_UDP) sa_flags |= SA_PORT; if (proto == IPPROTO_TCP && (tcptype == ICE_TCP_PASSIVE || tcptype == ICE_TCP_SO)) sa_flags |= SA_PORT; if (!sa_isset(addr, sa_flags)) { DEBUG_WARNING("add_remote_candidate: invalid address" " (%J) for %s.%s\n", addr, net_proto2name(proto), ice_tcptype_name(tcptype)); return EINVAL; } /* avoid duplicates */ rcand = trice_rcand_find(icem, compid, proto, addr); if (rcand) { if (rcand->attr.type == ICE_CAND_TYPE_PRFLX && prio > rcand->attr.prio) { rcand->attr.type = type; rcand->attr.prio = prio; } goto out; } err = trice_add_rcandidate(&rcand, &icem->rcandl, compid, foundation, proto, prio, addr, type, tcptype); if (err) goto out; if (icem->lrole != ICE_ROLE_UNKNOWN) { /* pair this remote-candidate with all existing * local-candidates */ err = trice_candpair_with_remote(icem, rcand); if (err) goto out; /* new pair -- refresh the checklist timer */ trice_checklist_refresh(icem); } out: if (err) mem_deref(rcand); else if (rcandp) *rcandp = rcand; return err; } struct ice_rcand *trice_rcand_find(struct trice *icem, unsigned compid, int proto, const struct sa *addr) { struct list *lst; struct le *le; if (!icem) return NULL; if (!proto) { DEBUG_WARNING("find_candidate: invalid args\n"); return NULL; } lst = &icem->rcandl; for (le = list_head(lst); le; le = le->next) { struct ice_cand_attr *cand = le->data; if (compid && cand->compid != compid) continue; if (cand->proto != proto) continue; if (addr && !sa_cmp(&cand->addr, addr, SA_ALL)) continue; return (void *)cand; } return NULL; } int trice_rcands_debug(struct re_printf *pf, const struct list *lst) { struct le *le; int err; err = re_hprintf(pf, " (%u)\n", list_count(lst)); for (le = list_head(lst); le && !err; le = le->next) { const struct ice_rcand *rcand = le->data; err |= re_hprintf(pf, " {%u} " "fnd=%-8s prio=%08x %24H", rcand->attr.compid, rcand->attr.foundation, rcand->attr.prio, trice_cand_print, rcand); err |= re_hprintf(pf, "\n"); } return err; } ================================================ FILE: src/trice/stunsrv.c ================================================ /** * @file stunsrv.c Basic STUN Server for Connectivity checks * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "stunsrv" #define DEBUG_LEVEL 5 #include static const char *sw = "ice stunsrv v" RE_VERSION " (" ARCH "/" OS ")"; /* * NOTE about TCP-candidates: * * Note that STUN requests received on a passive TCP candidate * will typically produce a remote peer reflexive candidate. */ static int handle_stun_full(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, uint32_t prio, bool use_cand) { struct ice_candpair *pair = NULL; struct ice_rcand *rcand; enum ice_tcptype tcptype_rev; int err = 0; trice_tracef(icem, 36, "[%u] STUNSRV: Rx Binding Request [%H <--- %J] %s\n", lcand->attr.compid, trice_cand_print, lcand, src, use_cand ? "[USE]" : ""); tcptype_rev = ice_tcptype_reverse(lcand->attr.tcptype); rcand = trice_rcand_find(icem, lcand->attr.compid, lcand->attr.proto, src); if (!rcand) { err = trice_rcand_add(&rcand, icem, lcand->attr.compid, "444", lcand->attr.proto, prio, src, ICE_CAND_TYPE_PRFLX, tcptype_rev); if (err) return err; trice_printf(icem, "{%u} added PRFLX " "remote candidate (%H)\n", lcand->attr.compid, trice_cand_print, rcand); } /* already valid, skip */ pair = trice_candpair_find(&icem->validl, lcand, rcand); if (pair) goto out; /* note: the candidate-pair can exist in either list */ pair = trice_candpair_find(&icem->checkl, lcand, rcand); if (!pair) { DEBUG_WARNING("{%u} candidate pair not found:" " source=%J\n", lcand->attr.compid, src); goto out; } /* 7.2.1.5. Updating the Nominated Flag */ if (use_cand) { if (icem->lrole == ICE_ROLE_CONTROLLED) { pair->nominated = true; } } out: /* send a triggered request */ if (pair && use_cand) { if (icem->checklist && !pair->trigged) { err = trice_conncheck_trigged(icem, pair, sock, use_cand); if (err) { DEBUG_WARNING("ice_checklist_stun_request" " failed (%m)\n", err); } pair->trigged = true; } } return 0; } static int stunsrv_ereply(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, size_t presz, const struct stun_msg *req, uint16_t scode, const char *reason) { DEBUG_WARNING("[%H] replying error to %J (%u %s)\n", trice_cand_print, lcand, src, scode, reason); trice_tracef(icem, 31, "[%u] STUNSRV: Tx error [%J <--- %H] (%u %s)\n", lcand->attr.compid, src, trice_cand_print, lcand, scode, reason); return stun_ereply(lcand->attr.proto, sock, src, presz, req, scode, reason, (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 1, STUN_ATTR_SOFTWARE, sw); } int trice_stund_recv(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, struct stun_msg *req, size_t presz) { struct stun_attr *attr; struct pl lu, ru; int err; /* RFC 5389: Fingerprint errors are silently discarded */ err = stun_msg_chk_fingerprint(req); if (err) return err; err = stun_msg_chk_mi(req, (uint8_t *)icem->lpwd, strlen(icem->lpwd)); if (err) { DEBUG_WARNING("message-integrity failed (src=%J)\n", src); if (err == EBADMSG) goto unauth; else goto badmsg; } attr = stun_msg_attr(req, STUN_ATTR_USERNAME); if (!attr) goto badmsg; err = re_regex(attr->v.username, strlen(attr->v.username), "[^:]+:[^]+", &lu, &ru); if (err) { DEBUG_WARNING("could not parse USERNAME attribute (%s)\n", attr->v.username); goto unauth; } if (pl_strcmp(&lu, icem->lufrag)) { DEBUG_WARNING("local ufrag err (expected %s, actual %r)\n", icem->lufrag, &lu); goto unauth; } if (str_isset(icem->rufrag) && pl_strcmp(&ru, icem->rufrag)) { DEBUG_WARNING("remote ufrag err (expected %s, actual %r)\n", icem->rufrag, &ru); goto unauth; } if (icem->lrole == ICE_ROLE_UNKNOWN) { err = trice_reqbuf_append(icem, lcand, sock, src, req, presz); if (err) { DEBUG_WARNING("unable to buffer STUN request: %m\n", err); } } return trice_stund_recv_role_set(icem, lcand, sock, src, req, presz); badmsg: return stunsrv_ereply(icem, lcand, sock, src, presz, req, 400, "Bad Request"); unauth: return stunsrv_ereply(icem, lcand, sock, src, presz, req, 401, "Unauthorized"); } int trice_stund_recv_role_set(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, struct stun_msg *req, size_t presz) { struct stun_attr *attr; enum ice_role remote_role = ICE_ROLE_UNKNOWN; uint64_t tiebrk = 0; uint32_t prio_prflx; int err; bool use_cand = false; attr = stun_msg_attr(req, STUN_ATTR_CONTROLLED); if (attr) { remote_role = ICE_ROLE_CONTROLLED; tiebrk = attr->v.uint64; } attr = stun_msg_attr(req, STUN_ATTR_CONTROLLING); if (attr) { remote_role = ICE_ROLE_CONTROLLING; tiebrk = attr->v.uint64; } if (remote_role == ICE_ROLE_UNKNOWN) goto badmsg; if (remote_role == icem->lrole) { DEBUG_NOTICE("role conflict detected (both %s)\n", ice_role2name(remote_role)); if (icem->tiebrk >= tiebrk) trice_switch_local_role(icem); else goto conflict; } attr = stun_msg_attr(req, STUN_ATTR_PRIORITY); if (attr) prio_prflx = attr->v.uint32; else goto badmsg; attr = stun_msg_attr(req, STUN_ATTR_USE_CAND); if (attr) use_cand = true; err = handle_stun_full(icem, lcand, sock, src, prio_prflx, use_cand); if (err) goto badmsg; trice_tracef(icem, 32, "[%u] STUNSRV: Tx success response [%H ---> %J]\n", lcand->attr.compid, trice_cand_print, lcand, src); return stun_reply(lcand->attr.proto, sock, src, presz, req, (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 2, STUN_ATTR_XOR_MAPPED_ADDR, src, STUN_ATTR_SOFTWARE, sw); badmsg: return stunsrv_ereply(icem, lcand, sock, src, presz, req, 400, "Bad Request"); conflict: return stunsrv_ereply(icem, lcand, sock, src, presz, req, 487, "Role Conflict"); } ================================================ FILE: src/trice/tcpconn.c ================================================ /** * @file tcpconn.c ICE handling of TCP-connections * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "tcpconn" #define DEBUG_LEVEL 5 #include /* `mb' contains a complete frame */ static bool shim_frame_handler(struct mbuf *mb, void *arg) { struct ice_tcpconn *conn = arg; return conn->frameh(conn->icem, conn->tc, &conn->paddr, mb, conn->arg); } static void tcp_estab_handler(void *arg) { struct ice_tcpconn *conn = arg; struct trice *icem = conn->icem; struct le *le; int err; conn->estab = true; trice_printf(icem, "TCP established (local=%J <---> peer=%J)\n", &conn->laddr, &conn->paddr); err = shim_insert(&conn->shim, conn->tc, conn->layer, shim_frame_handler, conn); if (err) goto out; if (!icem->checklist) goto out; /* check all pending CONNCHECKs for TCP */ le = icem->checklist->conncheckl.head; while (le) { struct ice_conncheck *cc = le->data; struct ice_candpair *pair = cc->pair; le = le->next; if (pair->state == ICE_CANDPAIR_INPROGRESS && pair->lcand->attr.compid == conn->compid && pair->lcand->attr.proto == IPPROTO_TCP && sa_cmp(&pair->lcand->attr.addr, &conn->laddr, SA_ADDR) && sa_cmp(&pair->rcand->attr.addr, &conn->paddr, SA_ALL)) { trice_printf(icem, " estab: sending pending check" " from %j to %J\n", &pair->lcand->attr.addr, &pair->rcand->attr.addr); /* todo: */ pair->conn = mem_ref(conn); err = trice_conncheck_stun_request(icem->checklist, cc, pair, conn->tc, cc->use_cand); if (err) { DEBUG_WARNING("stun_request error (%m)\n", err); } } } out: if (err) { DEBUG_WARNING("estab: errors (%m)\n", err); } } /* todo: re-connect if estab and active (with a timer) */ static void tcp_close_handler(int err, void *arg) { struct ice_tcpconn *conn = arg; struct trice *icem = conn->icem; struct le *le; trice_printf(conn->icem, "TCP-connection [%J -> %J] closed (%m)\n", &conn->laddr, &conn->paddr, err); err = err ? err : ECONNRESET; /* note: helper must be closed before tc */ conn->shim = mem_deref(conn->shim); conn->tc = mem_deref(conn->tc); /* todo: iterate through conncheckl and cancel all checks * that are using this conn */ le = conn->icem->checkl.head; while (le) { struct ice_candpair *pair = le->data; le = le->next; if (pair->lcand->attr.compid == conn->compid && pair->lcand->attr.proto == IPPROTO_TCP && sa_cmp(&pair->rcand->attr.addr, &conn->paddr, SA_ALL)) { trice_candpair_failed(pair, err, 0); if (icem->checklist) { icem->checklist->failh(err, 0, pair, icem->checklist->arg); } } } mem_deref(conn); } static void conn_destructor(void *arg) { struct ice_tcpconn *conn = arg; list_unlink(&conn->le); mem_deref(conn->shim); mem_deref(conn->tc); } /* ts: only for accept */ int trice_conn_alloc(struct list *connl, struct trice *icem, unsigned compid, bool active, const struct sa *laddr, const struct sa *peer, struct tcp_sock *ts, int layer, tcpconn_frame_h *frameh, void *arg) { struct ice_tcpconn *conn; int err = 0; if (!connl || !icem || !laddr || !peer || !frameh) return EINVAL; conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return ENOMEM; conn->icem = icem; conn->active = active; conn->paddr = *peer; conn->compid = compid; conn->layer = layer; conn->frameh = frameh; conn->arg = arg; if (active) { trice_printf(conn->icem, "<%p> TCP connecting" " [laddr=%J paddr=%J] ..\n", icem, laddr, peer); /* This connection is opened from the local candidate of the pair to the remote candidate of the pair. */ err = tcp_conn_alloc(&conn->tc, peer, tcp_estab_handler, NULL, tcp_close_handler, conn); if (err) { DEBUG_WARNING("tcp_conn_alloc [peer=%J] (%m)\n", peer, err); goto out; } err = tcp_conn_bind(conn->tc, laddr); if (err) { DEBUG_WARNING("tcp_conn_bind [laddr=%J paddr=%J]" " (%m)\n", laddr, peer, err); goto out; } err = tcp_conn_connect(conn->tc, peer); if (err) { /* NOTE: this happens sometimes on OSX when * setting up two S-O connections */ if (err == EADDRINUSE) { re_printf("EADDRINUSE\n"); } else { DEBUG_NOTICE("tcp_conn_connect [peer=%J]" " (%d/%m)\n", peer, err, err); goto out; } } } else { err = tcp_accept(&conn->tc, ts, tcp_estab_handler, NULL, tcp_close_handler, conn); if (err) { tcp_reject(ts); goto out; } } err = tcp_conn_local_get(conn->tc, &conn->laddr); if (err) goto out; list_append(connl, &conn->le, conn); out: if (err) mem_deref(conn); return err; } /* NOTE: laddr matching is SA_ADDR only */ struct ice_tcpconn *trice_conn_find(struct list *connl, unsigned compid, const struct sa *laddr, const struct sa *peer) { struct le *le; for (le = list_head(connl); le; le = le->next) { struct ice_tcpconn *conn = le->data; if (compid != conn->compid) continue; /* NOTE: only for established */ if (!conn->estab) continue; if (sa_cmp(laddr, &conn->laddr, SA_ADDR) && sa_cmp(peer, &conn->paddr, SA_ALL)) return conn; } return NULL; } int trice_conn_debug(struct re_printf *pf, const struct ice_tcpconn *conn) { int err; if (!conn) return 0; err = re_hprintf(pf, "... {%u} [%s|%5s] %J - %J " " (usage = %u) ", conn->compid, conn->active ? "Active" : "Passive", conn->estab ? "ESTAB" : " ", &conn->laddr, &conn->paddr, mem_nrefs(conn)-1); if (conn->shim) err |= shim_debug(pf, conn->shim); return err; } ================================================ FILE: src/trice/trice.c ================================================ /** * @file trice.c ICE Media stream * * Copyright (C) 2010 Alfred E. Heggestad */ #include #include #include #include #include #include #include #include #include #include #include #include "trice.h" #define DEBUG_MODULE "icem" #define DEBUG_LEVEL 5 #include static const struct trice_conf conf_default = { false, false, false, false }; static void trice_destructor(void *data) { struct trice *icem = data; mem_deref(icem->checklist); list_flush(&icem->validl); list_flush(&icem->checkl); list_flush(&icem->lcandl); list_flush(&icem->rcandl); list_flush(&icem->reqbufl); list_flush(&icem->connl); mem_deref(icem->rufrag); mem_deref(icem->rpwd); mem_deref(icem->lufrag); mem_deref(icem->lpwd); } /** * Allocate a new ICE Media object * * @param icemp Pointer to allocated ICE Media object * @param conf ICE configuration * @param role Local role * @param lufrag Local username fragment * @param lpwd Local password * * @return 0 if success, otherwise errorcode */ int trice_alloc(struct trice **icemp, const struct trice_conf *conf, enum ice_role role, const char *lufrag, const char *lpwd) { struct trice *icem; int err = 0; if (!icemp || !lufrag || !lpwd) return EINVAL; if (str_len(lufrag) < 4 || str_len(lpwd) < 22) { DEBUG_WARNING("alloc: lufrag/lpwd is too short\n"); return EINVAL; } icem = mem_zalloc(sizeof(*icem), trice_destructor); if (!icem) return ENOMEM; icem->conf = conf ? *conf : conf_default; list_init(&icem->reqbufl); list_init(&icem->lcandl); list_init(&icem->rcandl); list_init(&icem->checkl); list_init(&icem->validl); icem->lrole = role; icem->tiebrk = rand_u64(); err |= str_dup(&icem->lufrag, lufrag); err |= str_dup(&icem->lpwd, lpwd); if (err) goto out; out: if (err) mem_deref(icem); else *icemp = icem; return err; } /** * Set the remote username fragment * * @param icem ICE Media object * @param rufrag Remote username fragment * * @return 0 if success, otherwise errorcode */ int trice_set_remote_ufrag(struct trice *icem, const char *rufrag) { if (!icem || !rufrag) return EINVAL; icem->rufrag = mem_deref(icem->rufrag); return str_dup(&icem->rufrag, rufrag); } /** * Set the remote password * * @param icem ICE Media object * @param rpwd Remote password * * @return 0 if success, otherwise errorcode */ int trice_set_remote_pwd(struct trice *icem, const char *rpwd) { if (!icem || !rpwd) return EINVAL; icem->rpwd = mem_deref(icem->rpwd); return str_dup(&icem->rpwd, rpwd); } /** * Get the ICE Configuration * * @param icem ICE Media object * * @return ICE Configuration */ struct trice_conf *trice_conf(struct trice *icem) { return icem ? &icem->conf : NULL; } /* note: call this ONCE AFTER role has been set */ static void trice_create_candpairs(struct trice *icem) { struct list *lst; struct le *le; bool refresh_checklist = false; int err; lst = &icem->lcandl; for (le = list_head(lst); le; le = le->next) { struct ice_lcand *lcand = le->data; /* pair this local-candidate with all existing * remote-candidates */ err = trice_candpair_with_local(icem, lcand); if (err) { DEBUG_WARNING("trice_candpair_with_local: %m\n", err); } else { refresh_checklist = true; } } lst = &icem->rcandl; for (le = list_head(lst); le; le = le->next) { struct ice_rcand *rcand = le->data; /* pair this remote-candidate with all existing * local-candidates */ err = trice_candpair_with_remote(icem, rcand); if (err) { DEBUG_WARNING("trice_candpair_with_remote: %m\n", err); } else { refresh_checklist = true; } } /* new pair -- refresh the checklist timer */ if (refresh_checklist) trice_checklist_refresh(icem); } /* note: call this AFTER role has been set AND candidate pairs * have been created */ static void trice_reqbuf_process(struct trice *icem) { struct le *le; le = list_head(&icem->reqbufl); while (le) { struct trice_reqbuf *reqbuf = le->data; le = le->next; DEBUG_PRINTF("trice_reqbuf_process: Processing buffered " "request\n"); (void)trice_stund_recv_role_set(icem, reqbuf->lcand, reqbuf->sock, &reqbuf->src, reqbuf->req, reqbuf->presz); mem_deref(reqbuf); } } /** * Set the local role to either CONTROLLING or CONTROLLED. * Note: The role can be set multiple times. * * @param trice ICE Media object * @param role New local role * * @return 0 if success, otherwise errorcode */ int trice_set_role(struct trice *trice, enum ice_role role) { bool refresh; if (!trice) return EINVAL; /* Cannot change the role to unknown */ if (role == ICE_ROLE_UNKNOWN) return EINVAL; if (trice->lrole == role) return 0; /* Cannot switch role manually once it has been set */ if (trice->lrole == ICE_ROLE_UNKNOWN) refresh = false; else refresh = true; trice->lrole = role; /* Create candidate pairs and process pending requests */ if (refresh) { trice_candpair_prio_order(&trice->checkl, role == ICE_ROLE_CONTROLLING); } else { trice_create_candpairs(trice); } trice_reqbuf_process(trice); return 0; } /** * Get the local role * * @param icem ICE Media object * * @return Local role */ enum ice_role trice_local_role(const struct trice *icem) { if (!icem) return ICE_ROLE_UNKNOWN; return icem->lrole; } /** * Print debug information for the ICE Media * * @param pf Print function for debug output * @param icem ICE Media object * * @return 0 if success, otherwise errorcode */ int trice_debug(struct re_printf *pf, const struct trice *icem) { struct le *le; int err = 0; if (!icem) return 0; err |= re_hprintf(pf, "----- ICE Media <%p> -----\n", icem); err |= re_hprintf(pf, " local_role=%s\n", ice_role2name(icem->lrole)); err |= re_hprintf(pf, " local_ufrag=\"%s\" local_pwd=\"%s\"\n", icem->lufrag, icem->lpwd); err |= re_hprintf(pf, " Local Candidates: %H", trice_lcands_debug, &icem->lcandl); err |= re_hprintf(pf, " Remote Candidates: %H", trice_rcands_debug, &icem->rcandl); err |= re_hprintf(pf, " Check list: "); err |= trice_candpairs_debug(pf, icem->conf.ansi, &icem->checkl); err |= re_hprintf(pf, " Valid list: "); err |= trice_candpairs_debug(pf, icem->conf.ansi, &icem->validl); err |= re_hprintf(pf, " Buffered STUN Requests: (%u)\n", list_count(&icem->reqbufl)); if (icem->checklist) err |= trice_checklist_debug(pf, icem->checklist); err |= re_hprintf(pf, " TCP Connections: (%u)\n", list_count(&icem->connl)); for (le = list_head(&icem->connl); le; le = le->next) { struct ice_tcpconn *conn = le->data; err |= re_hprintf(pf, " %H\n", trice_conn_debug, conn); } return err; } /** * Get the list of Local Candidates (struct cand) * * @param icem ICE Media object * * @return List of Local Candidates */ struct list *trice_lcandl(const struct trice *icem) { return icem ? (struct list *)&icem->lcandl : NULL; } /** * Get the list of Remote Candidates (struct cand) * * @param icem ICE Media object * * @return List of Remote Candidates */ struct list *trice_rcandl(const struct trice *icem) { return icem ? (struct list *)&icem->rcandl : NULL; } /** * Get the checklist of Candidate Pairs * * @param icem ICE Media object * * @return Checklist (struct ice_candpair) */ struct list *trice_checkl(const struct trice *icem) { return icem ? (struct list *)&icem->checkl : NULL; } /** * Get the list of valid Candidate Pairs * * @param icem ICE Media object * * @return Validlist (struct ice_candpair) */ struct list *trice_validl(const struct trice *icem) { return icem ? (struct list *)&icem->validl : NULL; } void trice_printf(struct trice *icem, const char *fmt, ...) { va_list ap; if (!icem || !icem->conf.debug) return; va_start(ap, fmt); (void)re_printf("%v", fmt, &ap); va_end(ap); } void trice_tracef(struct trice *icem, int color, const char *fmt, ...) { va_list ap; if (!icem || !icem->conf.trace) return; if (icem->conf.ansi && color) { re_printf("\x1b[%dm", color); } va_start(ap, fmt); (void)re_printf("%v", fmt, &ap); va_end(ap); if (icem->conf.ansi && color) { re_printf("\x1b[;m"); } } void trice_switch_local_role(struct trice *ice) { enum ice_role new_role; if (!ice) return; switch (ice->lrole) { case ICE_ROLE_CONTROLLING: new_role = ICE_ROLE_CONTROLLED; break; case ICE_ROLE_CONTROLLED: new_role = ICE_ROLE_CONTROLLING; break; default: DEBUG_WARNING("trice_switch_local_role: local role unknown\n"); return; } DEBUG_NOTICE("Switch local role from %s to %s\n", ice_role2name(ice->lrole), ice_role2name(new_role)); ice->lrole = new_role; /* recompute pair priorities for all media streams */ trice_candpair_prio_order(&ice->checkl, ice->lrole == ICE_ROLE_CONTROLLING); } /* sock = [ struct udp_sock | struct tcp_conn ] */ bool trice_stun_process(struct trice *icem, struct ice_lcand *lcand, int proto, void *sock, const struct sa *src, struct mbuf *mb) { struct stun_msg *msg = NULL; struct stun_unknown_attr ua; size_t start = mb->pos; (void)proto; if (stun_msg_decode(&msg, mb, &ua)) { return false; /* continue recv-processing */ } if (STUN_METHOD_BINDING == stun_msg_method(msg)) { switch (stun_msg_class(msg)) { case STUN_CLASS_REQUEST: (void)trice_stund_recv(icem, lcand, sock, src, msg, start); break; default: if (icem->checklist) { (void)stun_ctrans_recv(icem->checklist->stun, msg, &ua); } else { DEBUG_NOTICE("STUN resp from %J dropped" " (no checklist)\n", src); } break; } } mem_deref(msg); return true; } static void trice_reqbuf_destructor(void *data) { struct trice_reqbuf *reqbuf = data; list_unlink(&reqbuf->le); mem_deref(reqbuf->req); mem_deref(reqbuf->sock); mem_deref(reqbuf->lcand); } int trice_reqbuf_append(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, struct stun_msg *req, size_t presz) { struct trice_reqbuf *reqbuf; if (!icem || !src ||!req) return EINVAL; reqbuf = mem_zalloc(sizeof(*reqbuf), trice_reqbuf_destructor); if (!reqbuf) return ENOMEM; DEBUG_PRINTF("trice_reqbuf_append: Buffering request\n"); reqbuf->lcand = mem_ref(lcand); reqbuf->sock = mem_ref(sock); reqbuf->src = *src; reqbuf->req = mem_ref(req); reqbuf->presz = presz; list_append(&icem->reqbufl, &reqbuf->le, reqbuf); return 0; } /** * Set the port range for local sockets * * @param trice ICE Media object * @param min_port Minimum port * @param max_port Maximum port * * @return 0 if success, otherwise errorcode */ int trice_set_port_range(struct trice *trice, uint16_t min_port, uint16_t max_port) { if (!trice) return EINVAL; if (max_port < min_port) return ERANGE; trice->ports.min = min_port; trice->ports.max = max_port; return 0; } ================================================ FILE: src/trice/trice.h ================================================ /** * @file trice.h Internal Interface to ICE * * Copyright (C) 2010 Alfred E. Heggestad */ struct ice_tcpconn; struct ice_conncheck; /** * Active Checklist. Only used by Full-ICE and Trickle-ICE */ struct ice_checklist { struct trice *icem; /* parent */ struct tmr tmr_pace; /**< Timer for pacing STUN requests */ uint32_t interval; /**< Interval in [ms] */ struct stun *stun; /**< STUN Transport */ struct list conncheckl; bool is_running; /**< Checklist is running */ /* callback handlers */ trice_estab_h *estabh; trice_failed_h *failh; void *arg; }; /** * Defines an ICE media-stream * * NOTE: We try to follow the Resource Acquisition Is Initialization (RAII) * programming idiom, which means: * * - at any time is the number of local/remote candidates correct * - at any time is the checklist up to date (matching local/remote candidates) * */ struct trice { struct trice_conf conf; enum ice_role lrole; /**< Local role */ uint64_t tiebrk; /**< Tie-break value for roleconflict */ /* stun/authentication */ char *lufrag; /**< Local Username fragment */ char *lpwd; /**< Local Password */ char *rufrag; /**< Remote Username fragment */ char *rpwd; /**< Remote Password */ struct list lcandl; /**< local candidates (add order) */ struct list rcandl; /**< remote candidates (add order) */ struct list checkl; /**< Check List of cand pairs (sorted) */ struct list validl; /**< Valid List of cand pairs (sorted) */ struct list reqbufl; /**< buffered incoming requests */ struct ice_checklist *checklist; struct list connl; /**< TCP-connections for all components */ /* Port range */ struct { uint16_t min; uint16_t max; } ports; }; /** * Holds an unhandled STUN request message that will be handled once * the role has been determined. */ struct trice_reqbuf { struct le le; /**< list element */ struct ice_lcand *lcand; /**< corresponding local candidate */ void *sock; /**< request's socket */ struct sa src; /**< source address */ struct stun_msg *req; /**< buffered STUN request */ size_t presz; /**< number of bytes in preamble */ }; /* return TRUE if handled */ typedef bool (tcpconn_frame_h)(struct trice *icem, struct tcp_conn *tc, struct sa *src, struct mbuf *mb, void *arg); /** * Defines a TCP-connection from local-address to remote-address * * - one TCP-connection can be shared by multiple candidate pairs * * - one TCP-connection is always created by the Local Candidate */ struct ice_tcpconn { struct trice *icem; /* parent */ struct le le; struct tcp_conn *tc; struct shim *shim; struct sa laddr; struct sa paddr; unsigned compid; int layer; bool active; bool estab; tcpconn_frame_h *frameh; void *arg; }; struct ice_conncheck { struct le le; struct ice_candpair *pair; /* pointer */ struct stun_ctrans *ct_conn; struct trice *icem; /* owner */ bool use_cand; bool term; }; /* cand */ int trice_add_lcandidate(struct ice_lcand **candp, struct trice *icem, struct list *lst, unsigned compid, char *foundation, int proto, uint32_t prio, const struct sa *addr, const struct sa *base_addr, enum ice_cand_type type, const struct sa *rel_addr, enum ice_tcptype tcptype); int trice_lcands_debug(struct re_printf *pf, const struct list *lst); int trice_rcands_debug(struct re_printf *pf, const struct list *lst); /* candpair */ int trice_candpair_alloc(struct ice_candpair **cpp, struct trice *icem, struct ice_lcand *lcand, struct ice_rcand *rcand); void trice_candpair_prio_order(struct list *lst, bool controlling); void trice_candpair_make_valid(struct trice *icem, struct ice_candpair *pair); void trice_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode); void trice_candpair_set_state(struct ice_candpair *cp, enum ice_candpair_state state); bool trice_candpair_iscompleted(const struct ice_candpair *cp); bool trice_candpair_cmp_fnd(const struct ice_candpair *cp1, const struct ice_candpair *cp2); struct ice_candpair *trice_candpair_find(const struct list *lst, const struct ice_lcand *lcand, const struct ice_rcand *rcand); int trice_candpair_with_local(struct trice *icem, struct ice_lcand *lcand); int trice_candpair_with_remote(struct trice *icem, struct ice_rcand *rcand); const char *trice_candpair_state2name(enum ice_candpair_state st); /* STUN server */ int trice_stund_recv(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, struct stun_msg *req, size_t presz); int trice_stund_recv_role_set(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, struct stun_msg *req, size_t presz); /* ICE media */ void trice_switch_local_role(struct trice *ice); void trice_printf(struct trice *icem, const char *fmt, ...); void trice_tracef(struct trice *icem, int color, const char *fmt, ...); /* ICE checklist */ int trice_checklist_debug(struct re_printf *pf, const struct ice_checklist *ic); void trice_conncheck_schedule_check(struct trice *icem); int trice_checklist_update(struct trice *icem); void trice_checklist_refresh(struct trice *icem); /* ICE conncheck */ int trice_conncheck_stun_request(struct ice_checklist *ic, struct ice_conncheck *cc, struct ice_candpair *cp, void *sock, bool cc_use_cand); int trice_conncheck_trigged(struct trice *icem, struct ice_candpair *pair, void *sock, bool use_cand); int trice_conncheck_debug(struct re_printf *pf, const struct ice_conncheck *cc); /* TCP connections */ int trice_conn_alloc(struct list *connl, struct trice *icem, unsigned compid, bool active, const struct sa *laddr, const struct sa *peer, struct tcp_sock *ts, int layer, tcpconn_frame_h *frameh, void *arg); struct ice_tcpconn *trice_conn_find(struct list *connl, unsigned compid, const struct sa *laddr, const struct sa *peer); int trice_conn_debug(struct re_printf *pf, const struct ice_tcpconn *conn); bool trice_stun_process(struct trice *icem, struct ice_lcand *lcand, int proto, void *sock, const struct sa *src, struct mbuf *mb); int trice_reqbuf_append(struct trice *icem, struct ice_lcand *lcand, void *sock, const struct sa *src, struct stun_msg *req, size_t presz); ================================================ FILE: src/turn/chan.c ================================================ /** * @file chan.c TURN Channels handling * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include "turnc.h" enum { CHAN_LIFETIME = 600, CHAN_REFRESH = 250, CHAN_NUMB_MIN = 0x4000, CHAN_NUMB_MAX = 0x7fff }; struct channels { struct hash *ht_numb; struct hash *ht_peer; uint16_t nr; }; struct chan { struct le he_numb; struct le he_peer; struct loop_state ls; uint16_t nr; struct sa peer; struct tmr tmr; struct turnc *turnc; struct stun_ctrans *ct; turnc_chan_h *ch; void *arg; }; static int chanbind_request(struct chan *chan, bool reset_ls); static void channels_destructor(void *data) { struct channels *c = data; /* flush from primary hash */ hash_flush(c->ht_numb); mem_deref(c->ht_numb); mem_deref(c->ht_peer); } static void chan_destructor(void *data) { struct chan *chan = data; tmr_cancel(&chan->tmr); mem_deref(chan->ct); mtx_lock(chan->turnc->lock); hash_unlink(&chan->he_numb); hash_unlink(&chan->he_peer); mtx_unlock(chan->turnc->lock); } static bool numb_hash_cmp_handler(struct le *le, void *arg) { const struct chan *chan = le->data; const uint16_t *nr = arg; return chan->nr == *nr; } static bool peer_hash_cmp_handler(struct le *le, void *arg) { const struct chan *chan = le->data; return sa_cmp(&chan->peer, arg, SA_ALL); } static void timeout(void *arg) { struct chan *chan = arg; int err; err = chanbind_request(chan, true); if (err) chan->turnc->th(err, 0, NULL, NULL, NULL, NULL, chan->turnc->arg); } static void chanbind_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct chan *chan = arg; if (err || turnc_request_loops(&chan->ls, scode)) goto out; switch (scode) { case 0: tmr_start(&chan->tmr, CHAN_REFRESH * 1000, timeout, chan); if (chan->ch) { chan->ch(chan->arg); chan->ch = NULL; chan->arg = NULL; } return; case 401: case 438: err = turnc_keygen(chan->turnc, msg); if (err) break; err = chanbind_request(chan, false); if (err) break; return; default: break; } out: chan->turnc->th(err, scode, reason, NULL, NULL, msg, chan->turnc->arg); } static int chanbind_request(struct chan *chan, bool reset_ls) { struct turnc *t = chan->turnc; if (reset_ls) turnc_loopstate_reset(&chan->ls); return stun_request(&chan->ct, t->stun, t->proto, t->sock, &t->srv, 0, STUN_METHOD_CHANBIND, t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash), false, chanbind_resp_handler, chan, 6, STUN_ATTR_CHANNEL_NUMBER, &chan->nr, STUN_ATTR_XOR_PEER_ADDR, &chan->peer, STUN_ATTR_USERNAME, t->realm ? t->username : NULL, STUN_ATTR_REALM, t->realm, STUN_ATTR_NONCE, t->nonce, STUN_ATTR_SOFTWARE, stun_software); } /** * Add a TURN Channel for a peer * * @param turnc TURN Client * @param peer Peer IP-address * @param ch Channel handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int turnc_add_chan(struct turnc *turnc, const struct sa *peer, turnc_chan_h *ch, void *arg) { struct chan *chan; int err; if (!turnc || !peer) return EINVAL; if (turnc->chans->nr >= CHAN_NUMB_MAX) return ERANGE; if (turnc_chan_find_peer(turnc, peer)) return 0; chan = mem_zalloc(sizeof(*chan), chan_destructor); if (!chan) return ENOMEM; chan->nr = turnc->chans->nr++; chan->peer = *peer; mtx_lock(turnc->lock); hash_append(turnc->chans->ht_numb, chan->nr, &chan->he_numb, chan); hash_append(turnc->chans->ht_peer, sa_hash(peer, SA_ALL), &chan->he_peer, chan); mtx_unlock(turnc->lock); tmr_init(&chan->tmr); chan->turnc = turnc; chan->ch = ch; chan->arg = arg; err = chanbind_request(chan, true); if (err) mem_deref(chan); return err; } int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize) { struct channels *c; int err; if (!cp) return EINVAL; c = mem_zalloc(sizeof(*c), channels_destructor); if (!c) return ENOMEM; err = hash_alloc(&c->ht_numb, bsize); if (err) goto out; err = hash_alloc(&c->ht_peer, bsize); if (err) goto out; c->nr = CHAN_NUMB_MIN; out: if (err) mem_deref(c); else *cp = c; return err; } struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr) { if (!turnc) return NULL; return list_ledata(hash_lookup(turnc->chans->ht_numb, nr, numb_hash_cmp_handler, &nr)); } struct chan *turnc_chan_find_peer(const struct turnc *turnc, const struct sa *peer) { if (!turnc) return NULL; mtx_lock(turnc->lock); struct chan *c = list_ledata( hash_lookup(turnc->chans->ht_peer, sa_hash(peer, SA_ALL), peer_hash_cmp_handler, (void *)peer)); mtx_unlock(turnc->lock); return c; } uint16_t turnc_chan_numb(const struct chan *chan) { return chan ? chan->nr : 0; } const struct sa *turnc_chan_peer(const struct chan *chan) { return chan ? &chan->peer : NULL; } int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb) { int err; if (!hdr || !mb) return EINVAL; err = mbuf_write_u16(mb, htons(hdr->nr)); err |= mbuf_write_u16(mb, htons(hdr->len)); return err; } int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb) { if (!hdr || !mb) return EINVAL; if (mbuf_get_left(mb) < sizeof(*hdr)) return ENOENT; hdr->nr = ntohs(mbuf_read_u16(mb)); hdr->len = ntohs(mbuf_read_u16(mb)); return 0; } ================================================ FILE: src/turn/perm.c ================================================ /** * @file perm.c TURN permission handling * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include "turnc.h" enum { PERM_LIFETIME = 300, PERM_REFRESH = 250, }; struct perm { struct le he; struct loop_state ls; struct sa peer; struct tmr tmr; struct turnc *turnc; struct stun_ctrans *ct; turnc_perm_h *ph; void *arg; }; static int createperm_request(struct perm *perm, bool reset_ls); static void destructor(void *arg) { struct perm *perm = arg; tmr_cancel(&perm->tmr); mem_deref(perm->ct); hash_unlink(&perm->he); } static bool hash_cmp_handler(struct le *le, void *arg) { const struct perm *perm = le->data; return sa_cmp(&perm->peer, arg, SA_ADDR); } static struct perm *perm_find(const struct turnc *turnc, const struct sa *peer) { return list_ledata(hash_lookup(turnc->perms, sa_hash(peer, SA_ADDR), hash_cmp_handler, (void *)peer)); } static void timeout(void *arg) { struct perm *perm = arg; int err; err = createperm_request(perm, true); if (err) perm->turnc->th(err, 0, NULL, NULL, NULL, NULL, perm->turnc->arg); } static void createperm_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct perm *perm = arg; if (err || turnc_request_loops(&perm->ls, scode)) goto out; switch (scode) { case 0: tmr_start(&perm->tmr, PERM_REFRESH * 1000, timeout, perm); if (perm->ph) { perm->ph(perm->arg); perm->ph = NULL; perm->arg = NULL; } return; case 401: case 438: err = turnc_keygen(perm->turnc, msg); if (err) break; err = createperm_request(perm, false); if (err) break; return; default: break; } out: perm->turnc->th(err, scode, reason, NULL, NULL, msg, perm->turnc->arg); } static int createperm_request(struct perm *perm, bool reset_ls) { struct turnc *t = perm->turnc; if (reset_ls) turnc_loopstate_reset(&perm->ls); return stun_request(&perm->ct, t->stun, t->proto, t->sock, &t->srv, 0, STUN_METHOD_CREATEPERM, t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash), false, createperm_resp_handler, perm, 5, STUN_ATTR_XOR_PEER_ADDR, &perm->peer, STUN_ATTR_USERNAME, t->realm ? t->username : NULL, STUN_ATTR_REALM, t->realm, STUN_ATTR_NONCE, t->nonce, STUN_ATTR_SOFTWARE, stun_software); } /** * Add TURN Permission for a peer * * @param turnc TURN Client * @param peer Peer IP-address * @param ph Permission handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int turnc_add_perm(struct turnc *turnc, const struct sa *peer, turnc_perm_h *ph, void *arg) { struct perm *perm; int err; if (!turnc || !peer) return EINVAL; if (perm_find(turnc, peer)) return 0; perm = mem_zalloc(sizeof(*perm), destructor); if (!perm) return ENOMEM; hash_append(turnc->perms, sa_hash(peer, SA_ADDR), &perm->he, perm); tmr_init(&perm->tmr); perm->peer = *peer; perm->turnc = turnc; perm->ph = ph; perm->arg = arg; err = createperm_request(perm, true); if (err) mem_deref(perm); return err; } int turnc_perm_hash_alloc(struct hash **ht, uint32_t bsize) { return hash_alloc(ht, bsize); } ================================================ FILE: src/turn/turnc.c ================================================ /** * @file turnc.c TURN Client implementation * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "turnc.h" #define DEBUG_MODULE "turnc" #define DEBUG_LEVEL 5 #include /** TURN Client protocol values */ enum { PERM_HASH_SIZE = 16, CHAN_HASH_SIZE = 16, FAILC_MAX = 16, /**< Maximum number of request errors for loopcheck. */ STUN_ATTR_ADDR4_SIZE = 8, STUN_ATTR_ADDR6_SIZE = 20, }; static const uint8_t sendind_tid[STUN_TID_SIZE]; static int allocate_request(struct turnc *t); static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls, stun_resp_h *resph, void *arg); static void refresh_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg); static void destructor(void *arg) { struct turnc *turnc = arg; if (turnc->allocated) (void)refresh_request(turnc, 0, true, NULL, NULL); tmr_cancel(&turnc->tmr); mem_deref(turnc->ct); hash_flush(turnc->perms); mem_deref(turnc->perms); mem_deref(turnc->chans); mem_deref(turnc->username); mem_deref(turnc->password); mem_deref(turnc->nonce); mem_deref(turnc->realm); mem_deref(turnc->stun); mem_deref(turnc->uh); mem_deref(turnc->sock); mem_deref(turnc->lock); } static void timeout(void *arg) { struct turnc *turnc = arg; int err; err = refresh_request(turnc, turnc->lifetime, true, refresh_resp_handler, turnc); if (err) turnc->th(err, 0, NULL, NULL, NULL, NULL, turnc->arg); } static void refresh_timer(struct turnc *turnc) { const uint32_t t = turnc->lifetime*1000*3/4; DEBUG_INFO("Start refresh timer.. %u seconds\n", t/1000); tmr_start(&turnc->tmr, t, timeout, turnc); } static void allocate_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct stun_attr *map = NULL, *rel = NULL, *ltm, *alt; struct turnc *turnc = arg; if (err || turnc_request_loops(&turnc->ls, scode)) goto out; switch (scode) { case 0: map = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); rel = stun_msg_attr(msg, STUN_ATTR_XOR_RELAY_ADDR); ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME); if (!rel || !map) { DEBUG_WARNING("xor_mapped/relay addr attr missing\n"); err = EPROTO; break; } if (ltm) turnc->lifetime = ltm->v.lifetime; turnc->allocated = true; refresh_timer(turnc); break; case 300: if (turnc->proto == IPPROTO_TCP || turnc->proto == STUN_TRANSP_DTLS) break; alt = stun_msg_attr(msg, STUN_ATTR_ALT_SERVER); if (!alt) break; turnc->psrv = turnc->srv; turnc->srv = alt->v.alt_server; err = allocate_request(turnc); if (err) break; return; case 401: case 438: err = turnc_keygen(turnc, msg); if (err) break; err = allocate_request(turnc); if (err) break; return; default: break; } out: turnc->th(err, scode, reason, rel ? &rel->v.xor_relay_addr : NULL, map ? &map->v.xor_mapped_addr : NULL, msg, turnc->arg); } static int allocate_request(struct turnc *t) { const uint8_t proto = IPPROTO_UDP; return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0, STUN_METHOD_ALLOCATE, t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash), false, allocate_resp_handler, t, 6, STUN_ATTR_LIFETIME, &t->lifetime, STUN_ATTR_REQ_TRANSPORT, &proto, STUN_ATTR_USERNAME, t->realm ? t->username : NULL, STUN_ATTR_REALM, t->realm, STUN_ATTR_NONCE, t->nonce, STUN_ATTR_SOFTWARE, stun_software); } static void refresh_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct turnc *turnc = arg; struct stun_attr *ltm; if (err || turnc_request_loops(&turnc->ls, scode)) goto out; switch (scode) { case 0: ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME); if (ltm) turnc->lifetime = ltm->v.lifetime; refresh_timer(turnc); return; case 401: case 438: err = turnc_keygen(turnc, msg); if (err) break; err = refresh_request(turnc, turnc->lifetime, false, refresh_resp_handler, turnc); if (err) break; return; default: break; } out: turnc->th(err, scode, reason, NULL, NULL, msg, turnc->arg); } static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls, stun_resp_h *resph, void *arg) { if (!t) return EINVAL; if (reset_ls) turnc_loopstate_reset(&t->ls); if (t->ct) t->ct = mem_deref(t->ct); return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0, STUN_METHOD_REFRESH, t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash), false, resph, arg, 5, STUN_ATTR_LIFETIME, &lifetime, STUN_ATTR_USERNAME, t->realm ? t->username : NULL, STUN_ATTR_REALM, t->realm, STUN_ATTR_NONCE, t->nonce, STUN_ATTR_SOFTWARE, stun_software); } static inline size_t stun_indlen(const struct sa *sa) { size_t len = STUN_HEADER_SIZE + STUN_ATTR_HEADER_SIZE * 2; switch (sa_af(sa)) { case AF_INET: len += STUN_ATTR_ADDR4_SIZE; break; case AF_INET6: len += STUN_ATTR_ADDR6_SIZE; break; } return len; } static bool udp_send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) { struct turnc *turnc = arg; size_t pos, indlen; struct chan *chan; if (mb->pos < CHAN_HDR_SIZE) return false; chan = turnc_chan_find_peer(turnc, dst); if (chan) { struct chan_hdr hdr; hdr.nr = turnc_chan_numb(chan); hdr.len = (uint16_t)mbuf_get_left(mb); mb->pos -= CHAN_HDR_SIZE; *err = turnc_chan_hdr_encode(&hdr, mb); mb->pos -= CHAN_HDR_SIZE; *dst = turnc->srv; return false; } indlen = stun_indlen(dst); if (mb->pos < indlen) return false; mb->pos -= indlen; pos = mb->pos; *err = stun_msg_encode(mb, STUN_METHOD_SEND, STUN_CLASS_INDICATION, sendind_tid, NULL, NULL, 0, false, 0x00, 2, STUN_ATTR_XOR_PEER_ADDR, dst, STUN_ATTR_DATA, mb); mb->pos = pos; *dst = turnc->srv; return false; } static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg) { struct stun_attr *peer, *data; struct stun_unknown_attr ua; struct turnc *turnc = arg; struct stun_msg *msg; bool hdld = true; if (!sa_cmp(&turnc->srv, src, SA_ALL) && !sa_cmp(&turnc->psrv, src, SA_ALL)) return false; if (stun_msg_decode(&msg, mb, &ua)) { struct chan_hdr hdr; struct chan *chan; if (turnc_chan_hdr_decode(&hdr, mb)) return true; if (mbuf_get_left(mb) < hdr.len) return true; chan = turnc_chan_find_numb(turnc, hdr.nr); if (!chan) return true; *src = *turnc_chan_peer(chan); return false; } switch (stun_msg_class(msg)) { case STUN_CLASS_INDICATION: if (ua.typec > 0) break; if (stun_msg_method(msg) != STUN_METHOD_DATA) break; peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR); data = stun_msg_attr(msg, STUN_ATTR_DATA); if (!peer || !data) break; *src = peer->v.xor_peer_addr; mb->pos = data->v.data.pos; mb->end = data->v.data.end; hdld = false; break; case STUN_CLASS_ERROR_RESP: case STUN_CLASS_SUCCESS_RESP: (void)stun_ctrans_recv(turnc->stun, msg, &ua); break; default: break; } mem_deref(msg); return hdld; } /** * Allocate a TURN Client * * @param turncp Pointer to allocated TURN Client * @param conf Optional STUN Configuration * @param proto Transport Protocol * @param sock Transport socket * @param layer Transport layer * @param srv TURN Server IP-address * @param username Authentication username * @param password Authentication password * @param lifetime Allocate lifetime in [seconds] * @param th TURN handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int turnc_alloc(struct turnc **turncp, const struct stun_conf *conf, int proto, void *sock, int layer, const struct sa *srv, const char *username, const char *password, uint32_t lifetime, turnc_h *th, void *arg) { struct turnc *turnc; int err; if (!turncp || !sock || !srv || !username || !password || !th) return EINVAL; turnc = mem_zalloc(sizeof(*turnc), destructor); if (!turnc) return ENOMEM; err = mutex_alloc(&turnc->lock); if (err) goto out; err = stun_alloc(&turnc->stun, conf, NULL, NULL); if (err) goto out; err = str_dup(&turnc->username, username); if (err) goto out; err = str_dup(&turnc->password, password); if (err) goto out; err = turnc_perm_hash_alloc(&turnc->perms, PERM_HASH_SIZE); if (err) goto out; err = turnc_chan_hash_alloc(&turnc->chans, CHAN_HASH_SIZE); if (err) goto out; tmr_init(&turnc->tmr); turnc->proto = proto; turnc->sock = mem_ref(sock); turnc->psrv = *srv; turnc->srv = *srv; turnc->lifetime = lifetime; turnc->th = th; turnc->arg = arg; switch (proto) { case IPPROTO_UDP: err = udp_register_helper(&turnc->uh, sock, layer, udp_send_handler, udp_recv_handler, turnc); break; default: err = 0; break; } if (err) goto out; err = allocate_request(turnc); if (err) goto out; out: if (err) mem_deref(turnc); else *turncp = turnc; return err; } int turnc_send(struct turnc *turnc, const struct sa *dst, struct mbuf *mb) { size_t pos, indlen; struct chan *chan; int err; if (!turnc || !dst || !mb) return EINVAL; chan = turnc_chan_find_peer(turnc, dst); if (chan) { struct chan_hdr hdr; if (mb->pos < CHAN_HDR_SIZE) return EINVAL; hdr.nr = turnc_chan_numb(chan); hdr.len = (uint16_t)mbuf_get_left(mb); mb->pos -= CHAN_HDR_SIZE; pos = mb->pos; err = turnc_chan_hdr_encode(&hdr, mb); if (err) return err; if (turnc->proto == IPPROTO_TCP) { mb->pos = mb->end; /* padding */ while (hdr.len++ & 0x03) { err = mbuf_write_u8(mb, 0x00); if (err) return err; } } mb->pos = pos; } else { indlen = stun_indlen(dst); if (mb->pos < indlen) return EINVAL; mb->pos -= indlen; pos = mb->pos; err = stun_msg_encode(mb, STUN_METHOD_SEND, STUN_CLASS_INDICATION, sendind_tid, NULL, NULL, 0, false, 0x00, 2, STUN_ATTR_XOR_PEER_ADDR, dst, STUN_ATTR_DATA, mb); if (err) return err; mb->pos = pos; } switch (turnc->proto) { case IPPROTO_UDP: err = udp_send(turnc->sock, &turnc->srv, mb); break; case IPPROTO_TCP: err = tcp_send(turnc->sock, mb); break; #ifdef USE_DTLS case STUN_TRANSP_DTLS: err = dtls_send(turnc->sock, mb); break; #endif default: err = EPROTONOSUPPORT; break; } return err; } int turnc_recv(struct turnc *turnc, struct sa *src, struct mbuf *mb) { struct stun_attr *peer, *data; struct stun_unknown_attr ua; struct stun_msg *msg; int err = 0; if (!turnc || !src || !mb) return EINVAL; if (stun_msg_decode(&msg, mb, &ua)) { struct chan_hdr hdr; struct chan *chan; if (turnc_chan_hdr_decode(&hdr, mb)) return EBADMSG; if (mbuf_get_left(mb) < hdr.len) return EBADMSG; chan = turnc_chan_find_numb(turnc, hdr.nr); if (!chan) return EBADMSG; *src = *turnc_chan_peer(chan); return 0; } switch (stun_msg_class(msg)) { case STUN_CLASS_INDICATION: if (ua.typec > 0) { err = ENOSYS; break; } if (stun_msg_method(msg) != STUN_METHOD_DATA) { err = ENOSYS; break; } peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR); data = stun_msg_attr(msg, STUN_ATTR_DATA); if (!peer || !data) { err = EPROTO; break; } *src = peer->v.xor_peer_addr; mb->pos = data->v.data.pos; mb->end = data->v.data.end; break; case STUN_CLASS_ERROR_RESP: case STUN_CLASS_SUCCESS_RESP: (void)stun_ctrans_recv(turnc->stun, msg, &ua); mb->pos = mb->end; break; default: err = ENOSYS; break; } mem_deref(msg); return err; } bool turnc_request_loops(struct loop_state *ls, uint16_t scode) { bool loop = false; switch (scode) { case 0: ls->failc = 0; break; default: if (ls->last_scode == scode) loop = true; /*@fallthrough@*/ case 300: if (++ls->failc >= FAILC_MAX) loop = true; break; } ls->last_scode = scode; return loop; } void turnc_loopstate_reset(struct loop_state *ls) { if (!ls) return; ls->last_scode = 0; ls->failc = 0; } int turnc_keygen(struct turnc *turnc, const struct stun_msg *msg) { struct stun_attr *realm, *nonce; realm = stun_msg_attr(msg, STUN_ATTR_REALM); nonce = stun_msg_attr(msg, STUN_ATTR_NONCE); if (!realm || !nonce) return EPROTO; mem_deref(turnc->realm); mem_deref(turnc->nonce); turnc->realm = mem_ref(realm->v.realm); turnc->nonce = mem_ref(nonce->v.nonce); return md5_printf(turnc->md5_hash, "%s:%s:%s", turnc->username, turnc->realm, turnc->password); } ================================================ FILE: src/turn/turnc.h ================================================ /** * @file turnc.h Internal TURN interface * * Copyright (C) 2010 Creytiv.com */ #include struct loop_state { uint32_t failc; uint16_t last_scode; }; struct channels; /** Defines a TURN Client */ struct turnc { struct loop_state ls; /**< Loop state */ struct udp_helper *uh; /**< UDP Helper for the TURN Socket */ struct stun_ctrans *ct; /**< Pending STUN Client Transaction */ char *username; /**< Authentication username */ char *password; /**< Authentication password */ struct sa psrv; /**< Previous TURN Server address */ struct sa srv; /**< TURN Server address */ void *sock; /**< Transport socket */ int proto; /**< Transport protocol */ struct stun *stun; /**< STUN Instance */ uint32_t lifetime; /**< Allocation lifetime in [seconds]*/ struct tmr tmr; /**< Allocation refresh timer */ turnc_h *th; /**< Turn client handler */ void *arg; /**< Handler argument */ uint8_t md5_hash[MD5_SIZE]; /**< Cached MD5-sum of credentials */ char *nonce; /**< Saved NONCE value from server */ char *realm; /**< Saved REALM value from server */ struct hash *perms; /**< Hash-table of permissions */ struct channels *chans; /**< TURN Channels */ bool allocated; /**< Allocation was done flag */ mtx_t *lock; /**< Lock turnc */ }; /* Util */ bool turnc_request_loops(struct loop_state *ls, uint16_t scode); void turnc_loopstate_reset(struct loop_state *ls); int turnc_keygen(struct turnc *turnc, const struct stun_msg *msg); /* Permission */ int turnc_perm_hash_alloc(struct hash **ht, uint32_t bsize); /* Channels */ enum { CHAN_HDR_SIZE = 4, }; struct chan_hdr { uint16_t nr; uint16_t len; }; struct chan; int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize); struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr); struct chan *turnc_chan_find_peer(const struct turnc *turnc, const struct sa *peer); uint16_t turnc_chan_numb(const struct chan *chan); const struct sa *turnc_chan_peer(const struct chan *chan); int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb); int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb); ================================================ FILE: src/udp/mcast.c ================================================ /** * @file mcast.c UDP Multicast * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include static int multicast_update(struct udp_sock *us, const struct sa *group, bool join) { struct ip_mreq mreq; struct ipv6_mreq mreq6; int err; if (!us || !group) return EINVAL; switch (sa_af(group)) { case AF_INET: mreq.imr_multiaddr = group->u.in.sin_addr; mreq.imr_interface.s_addr = 0; err = udp_setsockopt(us, IPPROTO_IP, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); break; case AF_INET6: mreq6.ipv6mr_multiaddr = group->u.in6.sin6_addr; mreq6.ipv6mr_interface = sa_scopeid(group); err = udp_setsockopt(us, IPPROTO_IPV6, join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); break; default: return EAFNOSUPPORT; } return err; } int udp_multicast_join(struct udp_sock *us, const struct sa *group) { return multicast_update(us, group, true); } int udp_multicast_leave(struct udp_sock *us, const struct sa *group) { return multicast_update(us, group, false); } ================================================ FILE: src/udp/udp.c ================================================ /** * @file udp.c User Datagram Protocol * * Copyright (C) 2010 Creytiv.com */ #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_IO_H #include #endif #if !defined(WIN32) #include #endif #include #ifdef HAVE_STRINGS_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 #ifndef HAVE_QOS_FLOWID typedef UINT32 QOS_FLOWID; #endif #ifndef HAVE_PQOS_FLOWID typedef UINT32 *PQOS_FLOWID; #endif #include #ifndef QOS_NON_ADAPTIVE_FLOW #define QOS_NON_ADAPTIVE_FLOW 0x00000002 #endif #endif /*WIN32*/ #define DEBUG_MODULE "udp" #define DEBUG_LEVEL 5 #include /** Platform independent buffer type cast */ #ifdef WIN32 #define BUF_CAST (char *) #define SIZ_CAST (int) #define close closesocket #else #define BUF_CAST #define SIZ_CAST #endif enum { UDP_RXSZ_DEFAULT = 8192 }; /** Defines a UDP socket */ struct udp_sock { struct list helpers; /**< List of UDP Helpers */ udp_send_h *sendh; udp_recv_h *rh; /**< Receive handler */ udp_error_h *eh; /**< Error handler */ void *arg; /**< Handler argument */ struct re_fhs *fhs; re_sock_t fd; /**< Socket file descriptor */ bool conn; /**< Connected socket flag */ size_t rxsz; /**< Maximum receive chunk size */ size_t rx_presz; /**< Preallocated rx buffer size */ #ifdef WIN32 HANDLE qos; /**< QOS subsystem handle */ QOS_FLOWID qos_id; /**< QOS flow id */ #endif mtx_t *lock; /**< A lock for helpers list */ }; /** Defines a UDP helper */ struct udp_helper { struct le le; int layer; udp_helper_send_h *sendh; udp_helper_recv_h *recvh; mtx_t *lock; /**< A lock for the helpers list */ void *arg; }; static void dummy_udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { (void)src; (void)mb; (void)arg; } static bool helper_send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) { (void)err; (void)dst; (void)mb; (void)arg; return false; } static bool helper_recv_handler(struct sa *src, struct mbuf *mb, void *arg) { (void)src; (void)mb; (void)arg; return false; } static void udp_destructor(void *data) { struct udp_sock *us = data; list_flush(&us->helpers); mem_deref(us->lock); #ifdef WIN32 if (us->qos && us->qos_id) (void)QOSRemoveSocketFromFlow(us->qos, 0, us->qos_id, 0); if (us->qos) (void)QOSCloseHandle(us->qos); #endif if (RE_BAD_SOCK != us->fd) { us->fhs = fd_close(us->fhs); (void)close(us->fd); } } static void udp_read(struct udp_sock *us, re_sock_t fd) { struct mbuf *mb = mbuf_alloc(us->rxsz); struct sa src; struct le *le; int err = 0; ssize_t n; if (!mb) return; src.len = sizeof(src.u); n = recvfrom(fd, BUF_CAST mb->buf + us->rx_presz, SIZ_CAST (mb->size - us->rx_presz), 0, &src.u.sa, &src.len); if (n < 0) { err = RE_ERRNO_SOCK; if (EAGAIN == err) goto out; #ifdef WIN32 if (WSAEWOULDBLOCK == err) goto out; #endif #if defined (EWOULDBLOCK) && EWOULDBLOCK != EAGAIN if (EWOULDBLOCK == err) goto out; #endif if (us->eh) us->eh(err, us->arg); goto out; } mb->pos = us->rx_presz; mb->end = n + us->rx_presz; (void)mbuf_resize(mb, mb->end); /* call helpers */ mtx_lock(us->lock); le = us->helpers.head; mtx_unlock(us->lock); while (le) { struct udp_helper *uh = le->data; bool hdld; mtx_lock(us->lock); le = le->next; mtx_unlock(us->lock); hdld = uh->recvh(&src, mb, uh->arg); if (hdld) goto out; } us->rh(&src, mb, us->arg); out: mem_deref(mb); } static void udp_read_handler(int flags, void *arg) { struct udp_sock *us = arg; (void)flags; udp_read(us, us->fd); } static int udp_alloc(struct udp_sock **usp) { int err; struct udp_sock *us; if (!usp) return EINVAL; us = mem_zalloc(sizeof(*us), NULL); if (!us) return ENOMEM; list_init(&us->helpers); us->fhs = NULL; us->fd = RE_BAD_SOCK; err = mutex_alloc(&us->lock); if (err) { mem_deref(us); return err; } mem_destructor(us, udp_destructor); *usp = us; return 0; } /** * Create and listen on a UDP Socket * * @param usp Pointer to returned UDP Socket * @param local Local network address * @param rh Receive handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int udp_listen(struct udp_sock **usp, const struct sa *local, udp_recv_h *rh, void *arg) { struct addrinfo hints, *res = NULL, *r; struct udp_sock *us; char addr[64] = {0}; char serv[6] = "0"; int af, error, err = 0; if (!usp) return EINVAL; err = udp_alloc(&us); if (err) return err; if (local) { af = sa_af(local); (void)re_snprintf(addr, sizeof(addr), "%H", sa_print_addr, local); (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local)); } else { af = AF_UNSPEC; } memset(&hints, 0, sizeof(hints)); /* set-up hints structure */ hints.ai_family = af; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; error = getaddrinfo(local ? addr : NULL, serv, &hints, &res); if (error) { #ifdef WIN32 DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n", WSAGetLastError()); #endif DEBUG_WARNING("listen: getaddrinfo: %s:%s (%s)\n", addr, serv, gai_strerror(error)); err = EADDRNOTAVAIL; goto out; } for (r = res; r; r = r->ai_next) { re_sock_t fd; if (us->fd != RE_BAD_SOCK) continue; DEBUG_INFO("listen: for: af=%d addr=%j\n", r->ai_family, r->ai_addr); fd = socket(r->ai_family, SOCK_DGRAM, IPPROTO_UDP); if (fd == RE_BAD_SOCK) { err = RE_ERRNO_SOCK; continue; } err = net_sockopt_blocking_set(fd, false); if (err) { DEBUG_WARNING("udp listen: nonblock set: %m\n", err); (void)close(fd); continue; } /* use dual socket */ if (r->ai_family == AF_INET6) (void)net_sockopt_v6only(fd, false); if (bind(fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) { err = RE_ERRNO_SOCK; DEBUG_INFO("listen: bind(): %m (%J)\n", err, local); (void)close(fd); continue; } /* OK */ us->fd = fd; break; } freeaddrinfo(res); /* We must have at least one socket */ if (RE_BAD_SOCK == us->fd) { if (0 == err) err = EADDRNOTAVAIL; goto out; } err = udp_thread_attach(us); if (err) goto out; us->rh = rh ? rh : dummy_udp_recv_handler; us->arg = arg; us->rxsz = UDP_RXSZ_DEFAULT; out: if (err) mem_deref(us); else *usp = us; return err; } int udp_alloc_sockless(struct udp_sock **usp, udp_send_h *sendh, udp_recv_h *recvh, void *arg) { struct udp_sock *us; int err; if (!usp || !sendh) return EINVAL; err = udp_alloc(&us); if (err) return err; us->sendh = sendh; us->rh = recvh ? recvh : dummy_udp_recv_handler; us->arg = arg; us->rxsz = UDP_RXSZ_DEFAULT; *usp = us; return 0; } int udp_alloc_fd(struct udp_sock **usp, re_sock_t fd, udp_recv_h *recvh, void *arg) { struct udp_sock *us; int err; if (!usp || fd == RE_BAD_SOCK) return EINVAL; err = udp_alloc(&us); if (err) return err; us->fd = fd; us->rh = recvh ? recvh : dummy_udp_recv_handler; us->arg = arg; us->rxsz = UDP_RXSZ_DEFAULT; *usp = us; return 0; } /** * Create an UDP socket with specified address family. * * @param usp Pointer to returned UDP Socket * @param af Address family AF_INET or AF_INET6 * * @return 0 if success, otherwise errorcode */ int udp_open(struct udp_sock **usp, int af) { struct udp_sock *us; int err = 0; re_sock_t fd; if (!usp) return EINVAL; err = udp_alloc(&us); if (err) return err; fd = socket(af, SOCK_DGRAM, IPPROTO_UDP); if (fd == RE_BAD_SOCK) { err = RE_ERRNO_SOCK; goto out; } us->fd = fd; out: if (err) mem_deref(us); else *usp = us; return err; } /** * Connect a UDP Socket to a specific peer. * When connected, this UDP Socket will only receive data from that peer. * * @param us UDP Socket * @param peer Peer network address * * @return 0 if success, otherwise errorcode */ int udp_connect(struct udp_sock *us, const struct sa *peer) { if (!us || !peer) return EINVAL; if (0 != connect(us->fd, &peer->u.sa, peer->len)) return RE_ERRNO_SOCK; us->conn = true; return 0; } static int udp_send_internal(struct udp_sock *us, const struct sa *dst, struct mbuf *mb, struct le *le) { struct sa hdst; int err = 0; re_sock_t fd = us->fd; /* call helpers in reverse order */ while (le) { struct udp_helper *uh = le->data; mtx_lock(us->lock); le = le->prev; mtx_unlock(us->lock); if (dst != &hdst) { sa_cpy(&hdst, dst); dst = &hdst; } if (uh->sendh(&err, &hdst, mb, uh->arg) || err) return err; } /* external send handler */ if (us->sendh) return us->sendh(dst, mb, us->arg); /* Connected socket? */ if (us->conn) { if (send(fd, BUF_CAST mb->buf + mb->pos, SIZ_CAST (mb->end - mb->pos), 0) < 0) return RE_ERRNO_SOCK; } else { if (sendto(fd, BUF_CAST mb->buf + mb->pos, SIZ_CAST (mb->end - mb->pos), 0, &dst->u.sa, dst->len) < 0) return RE_ERRNO_SOCK; } return 0; } /** * Send a UDP Datagram to a peer * * @param us UDP Socket * @param dst Destination network address * @param mb Buffer to send * * @return 0 if success, otherwise errorcode */ int udp_send(struct udp_sock *us, const struct sa *dst, struct mbuf *mb) { struct le *le; if (!us || !dst || !mb) return EINVAL; mtx_lock(us->lock); le = us->helpers.tail; mtx_unlock(us->lock); return udp_send_internal(us, dst, mb, le); } /** * Get the local network address on the UDP Socket * * @param us UDP Socket * @param local The returned local network address * * @return 0 if success, otherwise errorcode */ int udp_local_get(const struct udp_sock *us, struct sa *local) { if (!us || !local) return EINVAL; local->len = sizeof(local->u); if (0 == getsockname(us->fd, &local->u.sa, &local->len)) return 0; return RE_ERRNO_SOCK; } /** * Set socket options on the UDP Socket * * @param us UDP Socket * @param level Socket level * @param optname Option name * @param optval Option value * @param optlen Option length * * @return 0 if success, otherwise errorcode */ int udp_setsockopt(struct udp_sock *us, int level, int optname, const void *optval, uint32_t optlen) { int err = 0; if (!us) return EINVAL; if (RE_BAD_SOCK != us->fd) { if (0 != setsockopt(us->fd, level, optname, BUF_CAST optval, optlen)) err |= RE_ERRNO_SOCK; } return err; } /** * Set the send/receive buffer size on a UDP Socket * * @param us UDP Socket * @param size Buffer size in bytes * * @return 0 if success, otherwise errorcode */ int udp_sockbuf_set(struct udp_sock *us, int size) { int err = 0; if (!us) return EINVAL; err |= udp_setsockopt(us, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); err |= udp_setsockopt(us, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); return err; } int udp_settos(struct udp_sock *us, uint8_t tos) { int err = 0; int v = tos; struct sa sa; #ifdef WIN32 QOS_VERSION qos_version = { 1 , 0 }; QOS_TRAFFIC_TYPE qos_type = QOSTrafficTypeBestEffort; if (tos >= 32) /* >= DSCP_CS1 */ qos_type = QOSTrafficTypeBackground; if (tos >= 40) /* >= DSCP_AF11 */ qos_type = QOSTrafficTypeExcellentEffort; if (tos >= 136) /* >= DSCP_AF41 */ qos_type = QOSTrafficTypeAudioVideo; if (tos >= 184) /* >= DSCP_EF */ qos_type = QOSTrafficTypeVoice; if (tos >= 224) /* >= DSCP_CS7 */ qos_type = QOSTrafficTypeControl; #endif if (!us) return EINVAL; #ifdef WIN32 err = QOSCreateHandle(&qos_version, &us->qos); if (!err) return GetLastError(); us->qos_id = 0; if (RE_BAD_SOCK != us->fd) { err = QOSAddSocketToFlow(us->qos, us->fd, NULL, qos_type, QOS_NON_ADAPTIVE_FLOW, &us->qos_id); if (!err) return WSAGetLastError(); } #endif err = udp_local_get(us, &sa); if (err) return err; if (sa_af(&sa) == AF_INET) { err = udp_setsockopt(us, IPPROTO_IP, IP_TOS, &v, sizeof(v)); } #if defined(IPV6_TCLASS) && !defined(WIN32) else if (sa_af(&sa) == AF_INET6) { err = udp_setsockopt(us, IPPROTO_IPV6, IPV6_TCLASS, &v, sizeof(v)); } #endif return err; } /** * Set the maximum receive chunk size on a UDP Socket * * @param us UDP Socket * @param rxsz Maximum receive chunk size */ void udp_rxsz_set(struct udp_sock *us, size_t rxsz) { if (!us) return; us->rxsz = rxsz; } /** * Set preallocated space on receive buffer. * * @param us UDP Socket * @param rx_presz Size of preallocate space. */ void udp_rxbuf_presz_set(struct udp_sock *us, size_t rx_presz) { if (!us) return; us->rx_presz = rx_presz; } /** * Set receive handler on a UDP Socket * * @param us UDP Socket * @param rh Receive handler * @param arg Handler argument */ void udp_handler_set(struct udp_sock *us, udp_recv_h *rh, void *arg) { if (!us) return; us->rh = rh ? rh : dummy_udp_recv_handler; us->arg = arg; } /** * Set error handler on a UDP Socket * * @param us UDP Socket * @param eh Error handler */ void udp_error_handler_set(struct udp_sock *us, udp_error_h *eh) { if (!us) return; us->eh = eh; } /** * Get the File Descriptor from a UDP Socket * * @param us UDP Socket * * @return File Descriptor, or RE_BAD_SOCK for errors */ re_sock_t udp_sock_fd(const struct udp_sock *us) { return us ? us->fd : RE_BAD_SOCK; } /** * Attach the current thread to the UDP Socket * * @param us UDP Socket * * @return 0 if success, otherwise errorcode */ int udp_thread_attach(struct udp_sock *us) { int err = 0; if (!us) return EINVAL; if (RE_BAD_SOCK != us->fd) { err = fd_listen(&us->fhs, us->fd, FD_READ, udp_read_handler, us); if (err) goto out; } out: if (err) udp_thread_detach(us); return err; } /** * Detach the current thread from the UDP Socket * * @param us UDP Socket */ void udp_thread_detach(struct udp_sock *us) { if (!us) return; if (RE_BAD_SOCK != us->fd) us->fhs = fd_close(us->fhs); } static void helper_destructor(void *data) { struct udp_helper *uh = data; mtx_lock(uh->lock); list_unlink(&uh->le); mtx_unlock(uh->lock); } static bool sort_handler(struct le *le1, struct le *le2, void *arg) { struct udp_helper *uh1 = le1->data, *uh2 = le2->data; (void)arg; return uh1->layer <= uh2->layer; } /** * Register a UDP protocol stack helper * * @param uhp Pointer to allocated UDP helper object * @param us UDP socket * @param layer Layer number; higher number means higher up in stack * @param sh Send handler * @param rh Receive handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode */ int udp_register_helper(struct udp_helper **uhp, struct udp_sock *us, int layer, udp_helper_send_h *sh, udp_helper_recv_h *rh, void *arg) { struct udp_helper *uh; if (!us) return EINVAL; uh = mem_zalloc(sizeof(*uh), helper_destructor); if (!uh) return ENOMEM; mtx_lock(us->lock); list_append(&us->helpers, &uh->le, uh); uh->lock = us->lock; uh->layer = layer; uh->sendh = sh ? sh : helper_send_handler; uh->recvh = rh ? rh : helper_recv_handler; uh->arg = arg; list_sort(&us->helpers, sort_handler, NULL); if (uhp) *uhp = uh; mtx_unlock(us->lock); return 0; } /** * Send a UDP Datagram to a remote peer bypassing this helper and * the helpers above it. * * @param us UDP Socket * @param dst Destination network address * @param mb Buffer to send * @param uh UDP Helper * * @return 0 if success, otherwise errorcode */ int udp_send_helper(struct udp_sock *us, const struct sa *dst, struct mbuf *mb, struct udp_helper *uh) { struct le *le; if (!us || !dst || !mb || !uh) return EINVAL; mtx_lock(us->lock); le = uh->le.prev; mtx_unlock(us->lock); return udp_send_internal(us, dst, mb, le); } /** * Receive a UDP Datagram on this UDP helper layer. * * @param us UDP Socket * @param src Source network address * @param mb Buffer to receive * @param uhx UDP Helper */ void udp_recv_helper(struct udp_sock *us, const struct sa *src, struct mbuf *mb, struct udp_helper *uhx) { struct sa hsrc; struct le *le; if (!us || !src || !mb) return; mtx_lock(us->lock); le = uhx ? uhx->le.next : us->helpers.head; mtx_unlock(us->lock); while (le) { struct udp_helper *uh = le->data; bool hdld; mtx_lock(us->lock); le = le->next; mtx_unlock(us->lock); if (src != &hsrc) { sa_cpy(&hsrc, src); src = &hsrc; } hdld = uh->recvh(&hsrc, mb, uh->arg); if (hdld) return; } us->rh(src, mb, us->arg); } /** * Find a UDP-helper on a UDP socket * * @param us UDP socket * @param layer Layer number * * @return UDP-helper if found, NULL if not found */ struct udp_helper *udp_helper_find(const struct udp_sock *us, int layer) { struct le *le; if (!us) return NULL; mtx_lock(us->lock); le = us->helpers.head; mtx_unlock(us->lock); while (le) { struct udp_helper *uh = le->data; mtx_lock(us->lock); le = le->next; mtx_unlock(us->lock); if (layer == uh->layer) return uh; } return NULL; } /** * Flush a given UDP socket * * @param us UDP socket */ void udp_flush(const struct udp_sock *us) { if (!us) return; if (RE_BAD_SOCK != us->fd) { uint8_t buf[4096]; while (recvfrom(us->fd, BUF_CAST buf, sizeof(buf), 0, NULL, 0) > 0) ; } } /** * Receive a UDP Datagram on this UDP socket. All helpers are processed. * * @param us UDP Socket * @param src Source network address * @param mb Buffer to receive */ void udp_recv_packet(struct udp_sock *us, const struct sa *src, struct mbuf *mb) { udp_recv_helper(us, src, mb, NULL); } ================================================ FILE: src/unixsock/unixsock.c ================================================ /** * @file unixsock/unixsock.c Unix domain sockets * * Copyright (C) 2022 Sebastian Reimers */ #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #define DEBUG_MODULE "unixsock" #define DEBUG_LEVEL 5 #include #ifdef WIN32 #define close closesocket #define unlink _unlink #endif /** * Listen for incoming connections on a Unix domain socket. * * @param fdp Pointer to a re_sock_t variable where the listening socket * file descriptor will be stored. * @param sock Pointer to a struct sa containing the address of the * Unix domain socket. * * @return 0 if success, otherwise errorcode */ int unixsock_listen_fd(re_sock_t *fdp, const struct sa *sock) { #if HAVE_UNIXSOCK int err = 0; re_sock_t fd; if (!fdp || !sock) return EINVAL; if (sa_af(sock) != AF_UNIX || !sa_isset(sock, SA_ADDR)) return EINVAL; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == RE_BAD_SOCK) { err = RE_ERRNO_SOCK; goto err; } err = net_sockopt_blocking_set(fd, false); if (err) { DEBUG_WARNING("unix listen: nonblock set: %m\n", err); goto err; } (void)unlink(sock->u.un.sun_path); if (bind(fd, &sock->u.sa, sock->len) < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("bind(): %m (%J)\n", err, sock); goto err; } if (listen(fd, SOMAXCONN) < 0) { err = RE_ERRNO_SOCK; DEBUG_WARNING("listen(): %m (%J)\n", err, sock); goto err; } *fdp = fd; return 0; err: if (fd != RE_BAD_SOCK) { (void)close(fd); } return err; #else (void)fdp; (void)sock; return ENOTSUP; #endif } ================================================ FILE: src/uri/uri.c ================================================ /** * @file uri.c Uniform Resource Identifier (URI) module * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include /** * Encode a URI object * * @param pf Print function to encode into * @param uri URI object * * @return 0 if success, otherwise errorcode */ int uri_encode(struct re_printf *pf, const struct uri *uri) { int err; if (!uri) return 0; if (!pl_isset(&uri->scheme) || !pl_isset(&uri->host)) return EINVAL; err = re_hprintf(pf, "%r:", &uri->scheme); if (err) return err; if (pl_isset(&uri->user)) { err = re_hprintf(pf, "%r", &uri->user); err |= pf->vph("@", 1, pf->arg); if (err) return err; } /* The IPv6 address is delimited by '[' and ']' */ switch (uri->af) { case AF_INET6: err = re_hprintf(pf, "[%r]", &uri->host); break; default: err = re_hprintf(pf, "%r", &uri->host); break; } if (err) return err; if (uri->port) err = re_hprintf(pf, ":%u", uri->port); err |= re_hprintf(pf, "%r%r%r", &uri->path, &uri->params, &uri->headers); return err; } /** * Decode host-port portion of a URI (if present) * * @param hostport Host and port input string * @param host Decoded host portion * @param port Decoded port portion * * @return 0 if success, otherwise errorcode */ int uri_decode_hostport(const struct pl *hostport, struct pl *host, struct pl *port) { if (!hostport || !host || !port) return EINVAL; /* Try IPv6 first */ if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*", host, NULL, port)) return 0; /* Then non-IPv6 host */ return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*", host, NULL, port); } /** * Decode a pointer-length object into a URI object * * @param uri URI object * @param pl Pointer-length object to decode from * * @return 0 if success, otherwise errorcode */ int uri_decode(struct uri *uri, const struct pl *pl) { struct sa addr; struct pl port = PL_INIT; struct pl hostport; int err; if (!uri || !pl) return EINVAL; memset(uri, 0, sizeof(*uri)); if (0 == re_regex(pl->p, pl->l, "[^:]+:[^@:]*[:]*[^@]*@[^/;? ]+[^;? ]*[^?]*[^]*", &uri->scheme, &uri->user, NULL, NULL, &hostport, &uri->path, &uri->params, &uri->headers)) { if (0 == uri_decode_hostport(&hostport, &uri->host, &port)) goto out; } memset(uri, 0, sizeof(*uri)); err = re_regex(pl->p, pl->l, "[^:]+:[^/;? ]+[^;? ]*[^?]*[^]*", &uri->scheme, &hostport, &uri->path, &uri->params, &uri->headers); if (0 == err) { err = uri_decode_hostport(&hostport, &uri->host, &port); if (0 == err) goto out; } return err; out: /* Cache host address family */ if (0 == sa_set(&addr, &uri->host, 0)) uri->af = sa_af(&addr); else uri->af = AF_UNSPEC; if (pl_isset(&port)) uri->port = (uint16_t)pl_u32(&port); return 0; } /** * Get a URI parameter and possibly the value of it * * @param pl Pointer-length string containing parameters * @param pname URI Parameter name * @param pvalue Returned URI Parameter value * * @return 0 if success, otherwise errorcode */ int uri_param_get(const struct pl *pl, const struct pl *pname, struct pl *pvalue) { char expr[128]; if (!pl || !pname || !pvalue) return EINVAL; (void)re_snprintf(expr, sizeof(expr), ";%r[=]*[^;]*", pname); return re_regex(pl->p, pl->l, expr, NULL, pvalue); } /** * Call the apply handler for each URI Parameter * * @param pl Pointer-length string containing parameters * @param ah Apply handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode (returned from handler) */ int uri_params_apply(const struct pl *pl, uri_apply_h *ah, void *arg) { struct pl plr, pname, eq, pvalue; int err = 0; if (!pl || !ah) return EINVAL; plr = *pl; while (plr.l > 0) { err = re_regex(plr.p, plr.l, ";[^;=]+[=]*[^;]*", &pname, &eq, &pvalue); if (err) break; pl_advance(&plr, 1 + pname.l + eq.l + pvalue.l); err = ah(&pname, &pvalue, arg); if (err) break; } return err; } /** * Get a URI header and possibly the value of it * * @param pl Pointer-length string containing URI Headers * @param hname URI Header name * @param hvalue Returned URI Header value * * @return 0 if success, otherwise errorcode */ int uri_header_get(const struct pl *pl, const struct pl *hname, struct pl *hvalue) { char expr[128]; if (!pl || !hname || !hvalue) return EINVAL; (void)re_snprintf(expr, sizeof(expr), "[?&]1%r=[^&]+", hname); return re_regex(pl->p, pl->l, expr, NULL, hvalue); } /** * Call the apply handler for each URI Header * * @param pl Pointer-length string containing URI Headers * @param ah Apply handler * @param arg Handler argument * * @return 0 if success, otherwise errorcode (returned from handler) */ int uri_headers_apply(const struct pl *pl, uri_apply_h *ah, void *arg) { struct pl plr, sep, hname, hvalue; int err = 0; if (!pl || !ah) return EINVAL; plr = *pl; while (plr.l > 0) { err = re_regex(plr.p, plr.l, "[?&]1[^=]+=[^&]+", &sep, &hname, &hvalue); if (err) break; pl_advance(&plr, sep.l + hname.l + 1 + hvalue.l); err = ah(&hname, &hvalue, arg); if (err) break; } return err; } ================================================ FILE: src/uri/uric.c ================================================ /** * @file uric.c URI component escaping/unescaping * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #define DEBUG_MODULE "uric" #define DEBUG_LEVEL 5 #include /** Defines the URI escape handler */ typedef bool (esc_h)(char c); static bool is_mark(int c) { switch (c) { case '-': case '_': case '.': case '!': case '~': case '*': case '\'': case '(': case ')': return true; default: return false; } } static bool is_unreserved(char c) { return isalnum((unsigned char)c) || is_mark(c); } static bool is_user_unreserved(int c) { switch (c) { case '&': case '=': case '+': case '$': case ',': case ';': case '?': case '/': return true; default: return false; } } static bool is_hnv_unreserved(char c) { switch (c) { case '[': case ']': case '/': case '?': case ':': case '+': case '$': return true; default: return false; } } static bool is_user(char c) { return is_unreserved(c) || is_user_unreserved(c); } static bool is_param_unreserved(char c) { switch (c) { case '[': case ']': case '/': case ':': case '&': case '+': case '$': return true; default: return false; } } static bool is_paramchar(char c) { return is_param_unreserved(c) || is_unreserved(c); } static bool is_hvalue(char c) { return is_hnv_unreserved(c) || is_unreserved(c); } static int comp_escape(struct re_printf *pf, const struct pl *pl, esc_h *eh) { size_t i; int err = 0; if (!pf || !pl || !eh) return EINVAL; for (i=0; il && !err; i++) { const char c = pl->p[i]; if (eh(c)) { err = pf->vph(&c, 1, pf->arg); } else { err = re_hprintf(pf, "%%%02X", c); } } return err; } static int comp_unescape(struct re_printf *pf, const struct pl *pl, esc_h *eh) { size_t i; int err = 0; if (!pf || !pl || !eh) return EINVAL; for (i=0; il && !err; i++) { const char c = pl->p[i]; if (eh(c)) { err = pf->vph(&c, 1, pf->arg); continue; } if ('%' == c) { if (i+2 < pl->l) { const uint8_t hi = ch_hex(pl->p[++i]); const uint8_t lo = ch_hex(pl->p[++i]); const char b = hi<<4 | lo; err = pf->vph(&b, 1, pf->arg); } else { DEBUG_WARNING("unescape: short uri (%u)\n", i); return EBADMSG; } } else { DEBUG_WARNING("unescape: illegal '%c' in %r\n", c, pl); return EINVAL; } } return err; } /** * Escape a URI user component * * @param pf Print function * @param pl String to escape * * @return 0 if success, otherwise errorcode */ int uri_user_escape(struct re_printf *pf, const struct pl *pl) { return comp_escape(pf, pl, is_user); } /** * Unescape a URI user component * * @param pf Print function * @param pl String to unescape * * @return 0 if success, otherwise errorcode */ int uri_user_unescape(struct re_printf *pf, const struct pl *pl) { return comp_unescape(pf, pl, is_user); } /** * Escape one URI Parameter value * * @param pf Print function * @param pl String to escape * * @return 0 if success, otherwise errorcode */ int uri_param_escape(struct re_printf *pf, const struct pl *pl) { return comp_escape(pf, pl, is_paramchar); } /** * Unescape one URI Parameter value * * @param pf Print function * @param pl String to unescape * * @return 0 if success, otherwise errorcode */ int uri_param_unescape(struct re_printf *pf, const struct pl *pl) { return comp_unescape(pf, pl, is_paramchar); } /** * Escape one URI Header name/value * * @param pf Print function * @param pl String to escape * * @return 0 if success, otherwise errorcode */ int uri_header_escape(struct re_printf *pf, const struct pl *pl) { return comp_escape(pf, pl, is_hvalue); } /** * Unescape one URI Header name/value * * @param pf Print function * @param pl String to unescape * * @return 0 if success, otherwise errorcode */ int uri_header_unescape(struct re_printf *pf, const struct pl *pl) { return comp_unescape(pf, pl, is_hvalue); } ================================================ FILE: src/websock/websock.c ================================================ /** * @file websock.c Implementation of The WebSocket Protocol * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { TIMEOUT_CLOSE = 10000, BUFSIZE_MAX = 131072, }; enum websock_state { ACCEPTING = 0, CONNECTING, OPEN, CLOSING, CLOSED, }; struct websock { websock_shutdown_h *shuth; void *arg; bool shutdown; }; struct websock_conn { struct tmr tmr; char nonce[24]; struct websock *sock; struct tcp_conn *tc; struct tls_conn *sc; struct mbuf *mb; struct http_req *req; websock_estab_h *estabh; websock_recv_h *recvh; websock_close_h *closeh; void *arg; enum websock_state state; unsigned kaint; bool active; }; static const char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; static void timeout_handler(void *arg); static void dummy_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb, void *arg) { (void)hdr; (void)mb; (void)arg; } static void internal_close_handler(int err, void *arg) { struct websock_conn *conn = arg; (void)err; mem_deref(conn); } static void sock_destructor(void *arg) { struct websock *sock = arg; if (sock->shutdown) { sock->shutdown = false; mem_ref(sock); if (sock->shuth) sock->shuth(sock->arg); return; } } static void conn_destructor(void *arg) { struct websock_conn *conn = arg; if (conn->state == OPEN) (void)websock_close(conn, WEBSOCK_GOING_AWAY, "Going Away"); if (conn->state == CLOSING) { conn->recvh = dummy_recv_handler; conn->closeh = internal_close_handler; conn->arg = conn; tmr_start(&conn->tmr, TIMEOUT_CLOSE, timeout_handler, conn); /* important: the hack below depends on this */ mem_ref(conn); return; } tmr_cancel(&conn->tmr); mem_deref(conn->sc); mem_deref(conn->tc); mem_deref(conn->mb); mem_deref(conn->req); mem_deref(conn->sock); } static void conn_close(struct websock_conn *conn, int err) { tmr_cancel(&conn->tmr); conn->sc = mem_deref(conn->sc); conn->tc = mem_deref(conn->tc); conn->state = CLOSED; conn->closeh(err, conn->arg); } static void timeout_handler(void *arg) { struct websock_conn *conn = arg; conn_close(conn, ETIMEDOUT); } static void keepalive_handler(void *arg) { struct websock_conn *conn = arg; tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn); (void)websock_send(conn, WEBSOCK_PING, NULL); } static enum websock_scode websock_err2scode(int err) { switch (err) { case EOVERFLOW: return WEBSOCK_MESSAGE_TOO_BIG; case EPROTO: return WEBSOCK_PROTOCOL_ERROR; case EBADMSG: return WEBSOCK_PROTOCOL_ERROR; default: return WEBSOCK_INTERNAL_ERROR; } } static int websock_decode(struct websock_hdr *hdr, struct mbuf *mb) { uint8_t v, *p; size_t i; if (mbuf_get_left(mb) < 2) return ENODATA; v = mbuf_read_u8(mb); hdr->fin = v>>7 & 0x1; hdr->rsv1 = v>>6 & 0x1; hdr->rsv2 = v>>5 & 0x1; hdr->rsv3 = v>>4 & 0x1; hdr->opcode = v & 0x0f; v = mbuf_read_u8(mb); hdr->mask = v>>7 & 0x1; hdr->len = v & 0x7f; if (hdr->len == 126) { if (mbuf_get_left(mb) < 2) return ENODATA; hdr->len = ntohs(mbuf_read_u16(mb)); } else if (hdr->len == 127) { if (mbuf_get_left(mb) < 8) return ENODATA; hdr->len = sys_ntohll(mbuf_read_u64(mb)); } if (hdr->mask) { if (mbuf_get_left(mb) < (4 + hdr->len)) return ENODATA; hdr->mkey[0] = mbuf_read_u8(mb); hdr->mkey[1] = mbuf_read_u8(mb); hdr->mkey[2] = mbuf_read_u8(mb); hdr->mkey[3] = mbuf_read_u8(mb); for (i=0, p=mbuf_buf(mb); ilen; i++) p[i] = p[i] ^ hdr->mkey[i%4]; } else { if (mbuf_get_left(mb) < hdr->len) return ENODATA; } return 0; } static void recv_handler(struct mbuf *mb, void *arg) { struct websock_conn *conn = arg; int err = 0; if (conn->mb) { const size_t len = mbuf_get_left(mb), pos = conn->mb->pos; if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) { err = EOVERFLOW; goto out; } conn->mb->pos = conn->mb->end; err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len); if (err) goto out; conn->mb->pos = pos; } else { conn->mb = mem_ref(mb); } while (conn->mb) { struct websock_hdr hdr; size_t pos, end; pos = conn->mb->pos; err = websock_decode(&hdr, conn->mb); if (err) { if (err == ENODATA) { conn->mb->pos = pos; err = 0; break; } goto out; } if (conn->active == hdr.mask) { err = EPROTO; goto out; } if (hdr.rsv1 || hdr.rsv2 || hdr.rsv3) { err = EPROTO; goto out; } mb = conn->mb; end = mb->end; mb->end = mb->pos + (size_t)hdr.len; if (end > mb->end) { struct mbuf *mbn = mbuf_alloc(end - mb->end); if (!mbn) { err = ENOMEM; goto out; } (void)mbuf_write_mem(mbn, mb->buf + mb->end, end - mb->end); mbn->pos = 0; conn->mb = mbn; } else { conn->mb = NULL; } switch (hdr.opcode) { case WEBSOCK_CONT: case WEBSOCK_TEXT: case WEBSOCK_BIN: mem_ref(conn); conn->recvh(&hdr, mb, conn->arg); if (mem_nrefs(conn) == 1) { if (conn->state == OPEN) (void)websock_close(conn, WEBSOCK_GOING_AWAY, "Going Away"); /* * This is a hack. We enforce CLOSING * state so we know the connection will * continue to live. */ conn->state = CLOSING; } mem_deref(conn); break; case WEBSOCK_CLOSE: if (conn->state == OPEN) (void)websock_send(conn, WEBSOCK_CLOSE, "%b", mbuf_buf(mb), mbuf_get_left(mb)); conn_close(conn, 0); mem_deref(mb); return; case WEBSOCK_PING: (void)websock_send(conn, WEBSOCK_PONG, "%b", mbuf_buf(mb), mbuf_get_left(mb)); break; case WEBSOCK_PONG: break; default: mem_deref(mb); err = EPROTO; goto out; } mem_deref(mb); } out: if (err) { (void)websock_close(conn, websock_err2scode(err), NULL); conn_close(conn, err); } } static void close_handler(int err, void *arg) { struct websock_conn *conn = arg; conn_close(conn, err); } static int accept_print(struct re_printf *pf, const struct pl *key) { uint8_t digest[SHA_DIGEST_LENGTH]; uint8_t *data; size_t len = key->l + sizeof(magic)-1; data = mem_zalloc(len, NULL); if (!data) return ENOMEM; memcpy(data, key->p, key->l); memcpy(data + key->l, magic, sizeof(magic)-1); sha1(data, len, digest); mem_deref(data); return base64_print(pf, digest, sizeof(digest)); } static void http_resp_handler(int err, const struct http_msg *msg, void *arg) { struct websock_conn *conn = arg; const struct http_hdr *hdr; struct pl key; char buf[32]; if (err || msg->scode != 101) goto fail; if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket")) goto fail; if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade")) goto fail; hdr = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_ACCEPT); if (!hdr) goto fail; key.p = conn->nonce; key.l = sizeof(conn->nonce); if (re_snprintf(buf, sizeof(buf), "%H", accept_print, &key) < 0) goto fail; if (pl_strcmp(&hdr->val, buf)) goto fail; /* here we are ok */ conn->state = OPEN; if (conn->kaint) tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn); conn->estabh(conn->arg); return; fail: conn_close(conn, err ? err : EPROTO); } static void http_conn_handler(struct tcp_conn *tc, struct tls_conn *sc, void *arg) { struct websock_conn *conn = arg; conn->tc = mem_ref(tc); conn->sc = mem_ref(sc); tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn); } static int ws_connect(struct websock_conn **connp, const char *proto, struct websock *sock, struct http_cli *cli, const char *uri, unsigned kaint, websock_estab_h *estabh, websock_recv_h *recvh, websock_close_h *closeh, void *arg, const char *fmt, va_list *ap) { struct websock_conn *conn; uint8_t nonce[16]; char proto_hdr[64]; size_t len; int err, ret; if (!connp || !sock || !cli || !uri || !estabh || !recvh || !closeh) return EINVAL; if (proto) { ret = re_snprintf(proto_hdr, sizeof(proto_hdr), "Sec-WebSocket-Protocol: %s\r\n", proto); if (ret == -1) return EINVAL; } conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return ENOMEM; /* The nonce MUST be selected randomly for each connection */ rand_bytes(nonce, sizeof(nonce)); len = sizeof(conn->nonce); err = base64_encode(nonce, sizeof(nonce), conn->nonce, &len); if (err) goto out; conn->sock = mem_ref(sock); conn->kaint = kaint; conn->estabh = estabh; conn->recvh = recvh; conn->closeh = closeh; conn->arg = arg; conn->state = CONNECTING; conn->active = true; /* Protocol Handshake */ err = http_request(&conn->req, cli, "GET", uri, http_resp_handler, NULL, NULL, conn, "Upgrade: websocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: %b\r\n" "Sec-WebSocket-Version: 13\r\n" "%s" "%v" "\r\n", conn->nonce, sizeof(conn->nonce), proto ? proto_hdr : "", fmt, ap); if (err) goto out; http_req_set_conn_handler(conn->req, http_conn_handler); out: if (err) mem_deref(conn); else *connp = conn; return err; } int websock_connect(struct websock_conn **connp, struct websock *sock, struct http_cli *cli, const char *uri, unsigned kaint, websock_estab_h *estabh, websock_recv_h *recvh, websock_close_h *closeh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = ws_connect(connp, NULL, sock, cli, uri, kaint, estabh, recvh, closeh, arg, fmt, &ap); va_end(ap); return err; } int websock_connect_proto(struct websock_conn **connp, const char *proto, struct websock *sock, struct http_cli *cli, const char *uri, unsigned kaint, websock_estab_h *estabh, websock_recv_h *recvh, websock_close_h *closeh, void *arg, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = ws_connect(connp, proto, sock, cli, uri, kaint, estabh, recvh, closeh, arg, fmt, &ap); va_end(ap); return err; } int websock_accept(struct websock_conn **connp, struct websock *sock, struct http_conn *htconn, const struct http_msg *msg, unsigned kaint, websock_recv_h *recvh, websock_close_h *closeh, void *arg) { return websock_accept_proto(connp, NULL, sock, htconn, msg, kaint, recvh, closeh, arg); } int websock_accept_proto(struct websock_conn **connp, const char *proto, struct websock *sock, struct http_conn *htconn, const struct http_msg *msg, unsigned kaint, websock_recv_h *recvh, websock_close_h *closeh, void *arg) { const struct http_hdr *key; struct websock_conn *conn; char proto_hdr[64]; int err, ret; if (!connp || !sock || !htconn || !msg || !recvh || !closeh) return EINVAL; if (proto) { ret = re_snprintf(proto_hdr, sizeof(proto_hdr), "Sec-WebSocket-Protocol: %s\r\n", proto); if (ret == -1) return EINVAL; } if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket")) return EBADMSG; if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade")) return EBADMSG; if (!http_msg_hdr_has_value(msg, HTTP_HDR_SEC_WEBSOCKET_VERSION, "13")) return EBADMSG; key = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_KEY); if (!key) return EBADMSG; conn = mem_zalloc(sizeof(*conn), conn_destructor); if (!conn) return ENOMEM; err = http_reply(htconn, 101, "Switching Protocols", "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: %H\r\n" "%s" "\r\n", accept_print, &key->val, proto ? proto_hdr : ""); if (err) goto out; conn->sock = mem_ref(sock); conn->tc = mem_ref(http_conn_tcp(htconn)); conn->sc = mem_ref(http_conn_tls(htconn)); conn->kaint = kaint; conn->recvh = recvh; conn->closeh = closeh; conn->arg = arg; conn->state = OPEN; conn->active = false; tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn); http_conn_close(htconn); if (conn->kaint) tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn); out: if (err) mem_deref(conn); else *connp = conn; return err; } static int websock_encode(struct mbuf *mb, bool fin, enum websock_opcode opcode, bool mask, size_t len) { int err; err = mbuf_write_u8(mb, (fin<<7) | (opcode & 0x0f)); if (len > 0xffff) { err |= mbuf_write_u8(mb, (mask<<7) | 127); err |= mbuf_write_u64(mb, sys_htonll(len)); } else if (len > 125) { err |= mbuf_write_u8(mb, (mask<<7) | 126); err |= mbuf_write_u16(mb, htons((uint16_t)len)); } else { err |= mbuf_write_u8(mb, (mask<<7) | (uint8_t)len); } if (mask) { uint8_t mkey[4]; uint8_t *p; size_t i; rand_bytes(mkey, sizeof(mkey)); err |= mbuf_write_mem(mb, mkey, sizeof(mkey)); for (i=0, p=mbuf_buf(mb); iactive ? 14 : 10; size_t len, start; struct mbuf *mb; int err = 0; if (conn->state != OPEN) return ENOTCONN; mb = mbuf_alloc(2048); if (!mb) return ENOMEM; mb->pos = hsz; if (scode) err |= mbuf_write_u16(mb, htons(scode)); if (fmt) err |= mbuf_vprintf(mb, fmt, ap); if (err) goto out; len = mb->pos - hsz; if (len > 0xffff) start = mb->pos = 0; else if (len > 125) start = mb->pos = 6; else start = mb->pos = 8; err = websock_encode(mb, true, opcode, conn->active, len); if (err) goto out; mb->pos = start; err = tcp_send(conn->tc, mb); if (err) goto out; out: mem_deref(mb); return err; } int websock_send(struct websock_conn *conn, enum websock_opcode opcode, const char *fmt, ...) { va_list ap; int err; if (!conn) return EINVAL; va_start(ap, fmt); err = websock_vsend(conn, opcode, 0, fmt, ap); va_end(ap); return err; } int websock_close(struct websock_conn *conn, enum websock_scode scode, const char *fmt, ...) { va_list ap; int err; if (!conn) return EINVAL; if (!scode) fmt = NULL; va_start(ap, fmt); err = websock_vsend(conn, WEBSOCK_CLOSE, scode, fmt, ap); va_end(ap); if (!err) conn->state = CLOSING; return err; } struct tcp_conn *websock_tcp(const struct websock_conn *conn) { return conn ? conn->tc : NULL; } int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth, void *arg) { struct websock *sock; if (!sockp) return EINVAL; sock = mem_zalloc(sizeof(*sock), sock_destructor); if (!sock) return ENOMEM; sock->shuth = shuth; sock->arg = arg; *sockp = sock; return 0; } void websock_shutdown(struct websock *sock) { if (!sock || sock->shutdown) return; sock->shutdown = true; mem_deref(sock); } ================================================ FILE: test/CMakeLists.txt ================================================ # # CMakeLists.txt # # Copyright (C) 2010 - 2022 Alfred E. Heggestad # ############################################################################## # # Versioning # project(retest C) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) ############################################################################## # # Module/Package Includes # ############################################################################## # # Compile options/definitions # option(USE_SANITIZER "Sanitizers like: address, thread, undefined, memory") include(sanitizer) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_STANDARD 11) if(MSVC) add_compile_options("/W3") else() set(c_flags -Wall -Wbad-function-cast -Wcast-align -Wextra -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wno-strict-aliasing -Wold-style-definition -Wshadow -Wstrict-prototypes -Wuninitialized -Wvla ) add_compile_options( "$<$:${c_flags}>" ) endif() if(CMAKE_C_COMPILER_ID MATCHES "Clang") add_compile_options("$<$:-Wshorten-64-to-32>") endif() include_directories( . ) find_package(re CONFIG REQUIRED HINTS ../cmake) ############################################################################## # # Source/Header section # set(SRCS aac.c aes.c async.c au.c aubuf.c aulength.c aulevel.c aupos.c auresamp.c av1.c base64.c bfcp.c btrace.c conf.c convert.c crc32.c dbg.c dd.c dns.c dsp.c dtmf.c fir.c fmt.c g711.c h264.c h265.c hash.c hmac.c http.c httpauth.c ice.c json.c list.c main.c mbuf.c md5.c mem.c mem_pool.c mock/dnssrv.c mock/nat.c mock/sipsrv.c mock/stunsrv.c mock/turnsrv.c mqueue.c net.c odict.c pcp.c remain.c rtcp.c rtmp.c rtp.c rtpext.c sa.c sdp.c sha.c sip.c sipauth.c sipevent.c sipreg.c sipsess.c srtp.c stun.c sys.c tcp.c telev.c test.c thread.c tmr.c trace.c trice.c turn.c types.c udp.c unixsock.c uri.c vid.c vidconv.c websock.c ) if(USE_OPENSSL) list(APPEND SRCS tls.c dtls.c combo/dtls_turn.c mock/cert.c ) endif() list(APPEND SRCS cplusplus.cpp) ############################################################################## # # Main target object # set(LINKLIBS re ${OPENSSL_LIBRARIES}) if(WIN32) list(APPEND LINKLIBS qwave iphlpapi wsock32 ws2_32 crypt32) else() list(APPEND LINKLIBS m ${RESOLV_LIBRARY} stdc++) endif() if(ZLIB_FOUND) list(APPEND LINKLIBS ZLIB::ZLIB) endif() add_executable(${PROJECT_NAME} ${SRCS}) set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS 1) target_link_libraries(${PROJECT_NAME} PRIVATE ${LINKLIBS}) target_compile_definitions(${PROJECT_NAME} PRIVATE ${RE_DEFINITIONS}) if(USE_OPENSSL) target_include_directories(${PROJECT_NAME} PRIVATE ${OPENSSL_INCLUDE_DIR}) endif() ================================================ FILE: test/aac.c ================================================ /** * @file aac.c AAC (Advanced Audio Coding) Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "aactest" #define DEBUG_LEVEL 5 #include int test_aac(void) { static const uint8_t buf[2] = {0x12, 0x10}; struct aac_header hdr; int err; err = aac_header_decode(&hdr, buf, sizeof(buf)); if (err) return err; TEST_EQUALS(44100, hdr.sample_rate); TEST_EQUALS(2, hdr.channels); TEST_EQUALS(1024, hdr.frame_size); out: return err; } ================================================ FILE: test/aes.c ================================================ /** * @file aes.c AES (Advanced Encryption Standard) Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "aestest" #define DEBUG_LEVEL 5 #include /* * http://www.inconteam.com/software-development/41-encryption/ * 55-aes-test-vectors#aes-crt * * AES CTR 128-bit encryption mode */ static int test_aes_ctr_loop(void) { const char *init_vec_str = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; uint8_t encr_key[16]; uint8_t iv_enc[AES_BLOCK_SIZE]; uint8_t iv_dec[AES_BLOCK_SIZE]; size_t i; int err = 0; struct aes *enc = NULL, *dec = NULL; static const struct { char test_str[33]; char *ciph_str; } testv[] = { {"6bc1bee22e409f96e93d7e117393172a", "874d6191b620e3261bef6864990db6ce"}, {"ae2d8a571e03ac9c9eb76fac45af8e51", "9806f66b7970fdff8617187bb9fffdff"}, {"30c81c46a35ce411e5fbc1191a0a52ef", "5ae4df3edbd5d35e5b4f09020db03eab"}, {"f69f2445df4f9b17ad2b417be66c3710", "1e031dda2fbe03d1792170a0f3009cee"}, }; err |= str_hex(encr_key, sizeof(encr_key), "2b7e151628aed2a6abf7158809cf4f3c"); err |= str_hex(iv_enc, sizeof(iv_enc), init_vec_str); err |= str_hex(iv_dec, sizeof(iv_dec), init_vec_str); if (err) return err; err = aes_alloc(&enc, AES_MODE_CTR, encr_key, 128, iv_enc); err |= aes_alloc(&dec, AES_MODE_CTR, encr_key, 128, iv_dec); if (err) goto out; for (i=0; iencr_key_str); if (err) { DEBUG_WARNING("could not set key\n"); break; } err |= str_hex(iv, sizeof(iv), test->iv_str); if (err) { DEBUG_WARNING("could not set IV\n"); return err; } err = aes_alloc(&enc, AES_MODE_GCM, encr_key, key_bits, iv); if (err) goto out; if (str_isset(test->aad_str)) { err = str_hex(aad, sizeof(aad), test->aad_str); if (err) { DEBUG_WARNING("could not set aad\n"); break; } } if (str_isset(test->plain_str)) { err |= str_hex(test_vector, sizeof(test_vector), test->plain_str); clen = sizeof(test_vector); } else { clen = 0; } if (str_isset(test->ciph_str)) { err |= str_hex(cipher_text, sizeof(cipher_text), test->ciph_str); if (err) { DEBUG_WARNING("str_hex error\n"); break; } } err |= str_hex(tag_ref, sizeof(tag_ref), testv[i].tag_str); if (err) { DEBUG_WARNING("tag size mismatch\n"); break; } /* Encrypt */ if (str_isset(test->aad_str)) { err = aes_encr(enc, NULL, aad, sizeof(aad)); TEST_ERR(err); } if (clen) { err = aes_encr(enc, out, test_vector, clen); TEST_ERR(err); TEST_MEMCMP(cipher_text, sizeof(cipher_text), out, sizeof(out)); } err = aes_get_authtag(enc, tag, tagsz); TEST_ERR(err); if (test->success) { TEST_MEMCMP(tag_ref, sizeof(tag_ref), tag, tagsz); } enc = mem_deref(enc); /* Decrypt */ err = aes_alloc(&dec, AES_MODE_GCM, encr_key, key_bits, iv); if (err) goto out; if (str_isset(test->aad_str)) { err = aes_decr(dec, NULL, aad, sizeof(aad)); TEST_ERR(err); } err = aes_decr(dec, clear, out, clen); TEST_ERR(err); e = aes_authenticate(dec, tag_ref, tagsz); if (test->success) { if (e) { err = e; DEBUG_WARNING("aes_authenticate error\n"); break; } if (clen) { TEST_MEMCMP(test_vector, sizeof(test_vector), clear, sizeof(clear)); } } else { TEST_EQUALS(EAUTH, e); } dec = mem_deref(dec); } out: mem_deref(enc); mem_deref(dec); return err; } ================================================ FILE: test/async.c ================================================ /** * @file async.c Testcode for re async * * Copyright (C) 2022 Sebastian Reimers */ #define _BSD_SOURCE 1 #define _DEFAULT_SOURCE 1 #ifndef WIN32 #include #endif #include #include #include #include "test.h" #define DEBUG_MODULE "async" #define DEBUG_LEVEL 5 #include struct test_cnt { int tests; int done; }; struct test { char domain[128]; struct sa sa; int err; int err_expected; struct test_cnt *cnt; }; static int blocking_getaddr(void *arg) { int err; struct test *test = arg; struct addrinfo *res = NULL; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; #ifndef __ANDROID__ hints.ai_flags = AI_V4MAPPED; #endif /* Blocking */ err = getaddrinfo(test->domain, NULL, &hints, &res); if (err) return EADDRNOTAVAIL; sa_set_sa(&test->sa, res->ai_addr); freeaddrinfo(res); return 0; } static void completed(int err, void *arg) { struct test *test = arg; struct sa sa; if (err) goto out; err = re_thread_check(false); TEST_ERR(err); sa_set_str(&sa, "127.0.0.1", 0); if (!sa_cmp(&sa, &test->sa, SA_ADDR)) err = EINVAL; TEST_ERR(err); out: test->err = err; if (++test->cnt->done >= test->cnt->tests) re_cancel(); } static int test_re_thread_async(void) { int err; struct test_cnt cnt = {0, 0}; struct test testv[] = { {"localhost", {.len = 0}, -1, 0, &cnt}, {"test.notfound", {.len = 0}, -1, EADDRNOTAVAIL, &cnt}}; cnt.tests = RE_ARRAY_SIZE(testv); err = re_thread_async_init(2); TEST_ERR(err); for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { err = re_thread_async(blocking_getaddr, completed, &testv[i]); TEST_ERR(err); } err = re_main_timeout(200); TEST_ERR(err); for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { TEST_EQUALS(testv[i].err_expected, testv[i].err); } out: re_thread_async_close(); return err; } static void never_callback(int err, void *arg) { (void)err; (void)arg; DEBUG_WARNING("async: never_callback called!\n"); abort(); } static void timer_cancel(void *arg) { (void)arg; re_cancel(); } static int test_re_thread_async_cancel(void) { int err; struct tmr tmr; err = re_thread_async_init(2); TEST_ERR(err); err = re_thread_async_id(1, NULL, never_callback, NULL); TEST_ERR(err); re_thread_async_cancel(1); tmr_init(&tmr); tmr_start(&tmr, 0, timer_cancel, NULL); err = re_main_timeout(200); TEST_ERR(err); out: re_thread_async_close(); return err; } int test_async(void) { int err; err = test_re_thread_async(); TEST_ERR(err); err = test_re_thread_async_cancel(); TEST_ERR(err); out: return err; } ================================================ FILE: test/au.c ================================================ /** * @file au.c Audio testcode * * Copyright (C) 2024 Alfred E. Heggestad */ #include #include #include "test.h" #define DEBUG_MODULE "au" #define DEBUG_LEVEL 5 #include int test_au(void) { int err = 0; uint32_t nsamp = au_calc_nsamp(8000, 1, 20); ASSERT_EQ(160, nsamp); out: return err; } ================================================ FILE: test/aubuf.c ================================================ /** * @file aubuf.c Audio-buffer Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "test_aubuf" #define DEBUG_LEVEL 5 #include #define AUDIO_TIMEBASE 1000000U enum { FRAMES = 80, }; static int test_aubuf_raw(void) { struct aubuf *ab = NULL; int16_t sampv_in[2 * FRAMES]; int16_t sampv_out[2 * FRAMES]; struct mbuf *mb; unsigned i; int err; mb = mbuf_alloc(FRAMES * sizeof(int16_t)); if (!mb) return ENOMEM; for (i=0; ipos = 0; err = aubuf_append(ab, mb); TEST_ERR(err); TEST_EQUALS(4 * FRAMES, aubuf_cur_size(ab)); memset(sampv_out, 0, sizeof(sampv_out)); aubuf_read(ab, (uint8_t *)sampv_out, 2 * FRAMES * sizeof(int16_t)); TEST_MEMCMP(sampv_in, sizeof(sampv_in), sampv_out, sizeof(sampv_out)); TEST_EQUALS(0, aubuf_cur_size(ab)); out: mem_deref(ab); mem_deref(mb); return err; } static int test_aubuf_samp(void) { struct aubuf *ab = NULL; int16_t sampv_in[2 * FRAMES]; int16_t sampv_out[2 * FRAMES]; unsigned i; int err; for (i=0; i #include #include "test.h" #define DEBUG_MODULE "aulength" #define DEBUG_LEVEL 5 #include int test_aulength(void) { struct aufile *af = NULL; struct aufile_prm prm; char path[256]; re_snprintf(path, sizeof(path), "%s/beep.wav", test_datapath()); int err = aufile_open(&af, &prm, path, AUFILE_READ); TEST_ERR(err); size_t length = aufile_get_length(af, &prm); TEST_EQUALS(67, length); out: mem_deref(af); return err; } ================================================ FILE: test/aulevel.c ================================================ /** * @file src/aulevel.c audio levels * * Copyright (C) 2010 - 2017 Alfred E. Heggestad */ #include #include #include "test.h" #define DEBUG_MODULE "aulevel" #define DEBUG_LEVEL 5 #include #define PREC .6 int test_aulevel(void) { double level; struct auframe af; int err = 0; static struct { int16_t sampv[2]; double level; } testv[] = { { { 0, -0}, -96.0 }, { { 0, 1}, -93.0 }, { { 1, -1}, -90.0 }, { { 2, -2}, -84.0 }, { { 4, -4}, -78.0 }, { { 8, -8}, -72.0 }, { { 16, -16}, -66.0 }, { { 32, -32}, -60.0 }, { { 64, -64}, -54.0 }, { { 128, -128}, -48.0 }, { { 256, -256}, -42.0 }, { { 512, -512}, -36.0 }, { { 1024, -1024}, -30.0 }, { { 2048, -2048}, -24.0 }, { { 4096, -4096}, -18.0 }, { { 8192, -8192}, -12.0 }, { {16384, -16384}, -6.0 }, { {32767, -32768}, 0.0 }, }; static struct { int16_t sampv[4]; double level; } testv4[] = { { {32767, -32768, 16384, -16384}, -2.0 }, }; auframe_init(&af, AUFMT_RAW, testv[0].sampv, RE_ARRAY_SIZE(testv[0].sampv), 48000, 2); TEST_EQUALS(AULEVEL_UNDEF, af.level); level = auframe_level(&af); TEST_EQUALS(AULEVEL_UNDEF, level); auframe_init(&af, AUFMT_S16LE, NULL, 0, 48000, 2); level = auframe_level(&af); TEST_EQUALS(AULEVEL_UNDEF, level); for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { auframe_init(&af, AUFMT_S16LE, testv[i].sampv, RE_ARRAY_SIZE(testv[i].sampv), 48000, 2); level = auframe_level(&af); ASSERT_DOUBLE_EQ(testv[i].level, level, PREC); } for (size_t i = 0; i < RE_ARRAY_SIZE(testv4); i++) { auframe_init(&af, AUFMT_S16LE, testv4[i].sampv, RE_ARRAY_SIZE(testv4[i].sampv), 48000, 2); level = auframe_level(&af); ASSERT_DOUBLE_EQ(testv4[i].level, level, PREC); } out: return err; } ================================================ FILE: test/aupos.c ================================================ /** * @file src/aupos.c audio file setposition test * * Copyright (C) 2023 Lars Immisch */ #include #include #include "test.h" #define DEBUG_MODULE "auposition" #define DEBUG_LEVEL 5 #include int test_auposition(void) { struct aufile *af = NULL; struct aufile_prm prm; char path[256]; uint8_t buffer[512]; re_snprintf(path, sizeof(path), "%s/beep.wav", test_datapath()); int err = aufile_open(&af, &prm, path, AUFILE_READ); TEST_ERR(err); err = aufile_set_position(af, &prm, 67); TEST_ERR(err); /* That file is exactly 67 ms long, so we shouldn't read anything */ size_t size = sizeof(buffer); err = aufile_read(af, buffer, &size); TEST_ERR(err); /* It's possible we read data up to a ms */ TEST_ASSERT(size < 16); af = mem_deref(af); err = aufile_open(&af, &prm, path, AUFILE_READ); TEST_ERR(err); err = aufile_set_position(af, &prm, 37); TEST_ERR(err); size = sizeof(buffer); err = aufile_read(af, buffer, &size); TEST_ERR(err); /* 30 ms should be left, at 8000Hz/s, one channels and 16 bit samples that's 480 bytes */ TEST_ASSERT(size - 480 < 16); out: mem_deref(af); return err; } ================================================ FILE: test/auresamp.c ================================================ /** * @file auresamp.c Audio-resampler Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "test_auresamp" #define DEBUG_LEVEL 5 #include #define SRATE 44100 #define CHANNELS_IN 1 #define CHANNELS_OUT 2 #define SAMPLES 8 /* samples from random.org with atmospheric noise */ static const int16_t inv[CHANNELS_IN * SAMPLES] = { 0x513a, 0x3f11, 0x4224, 0x601d, 0x1dc6, 0x2fb1, 0x66ee, 0x7d53 }; static const int16_t ref_outv[CHANNELS_OUT * SAMPLES] = { 0x513a, 0x513a, 0x3f11, 0x3f11, 0x4224, 0x4224, 0x601d, 0x601d, 0x1dc6, 0x1dc6, 0x2fb1, 0x2fb1, 0x66ee, 0x66ee, 0x7d53, 0x7d53 }; int test_auresamp(void) { struct auresamp rs; int16_t outv[CHANNELS_OUT * SAMPLES]; size_t outc = RE_ARRAY_SIZE(outv); int err; auresamp_init(&rs); err = auresamp_setup(&rs, SRATE, CHANNELS_IN, SRATE, CHANNELS_OUT); TEST_ERR(err); /* resample from mono to stereo */ err = auresamp(&rs, outv, &outc, inv, RE_ARRAY_SIZE(inv)); TEST_ERR(err); TEST_EQUALS(RE_ARRAY_SIZE(outv), outc); #if 0 re_printf("\nInput samples:\n"); hexdump(stdout, inv, sizeof(inv)); re_printf("Output samples:\n"); hexdump(stdout, outv, sizeof(outv)); #endif TEST_MEMCMP(ref_outv, sizeof(ref_outv), outv, sizeof(outv)); out: return err; } ================================================ FILE: test/av1.c ================================================ /** * @file src/av1.c AV1 testcode * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include "test.h" #define DEBUG_MODULE "av1test" #define DEBUG_LEVEL 5 #include static int test_leb128(void) { struct mbuf *mb = NULL; int err = 0; static const uint64_t valuev[] = { 0, /* from random.org */ 449787982, 435590144, 64565769, 698509268, 524090268, 0x000000ff, /* max 8-bit */ 0x0000ffff, /* max 16-bit */ 0xffffffff /* max 32-bit */ }; for (size_t i=0; ipos = 0; err = av1_leb128_decode(mb, &val_dec); ASSERT_EQ(0, err); ASSERT_EQ(val, val_dec); mb = mem_deref(mb); } out: mem_deref(mb); return err; } static int test_av1_aggr(void) { static const struct test { uint8_t byte; unsigned z; unsigned y; unsigned w; unsigned n; } testv[] = { /* Sample aggregation headers from Chrome 102 */ {0x28, 0, 0, 2, 1}, {0x50, 0, 1, 1, 0}, }; int err = 0; for (size_t i=0; ibyte, .size = 1, .pos = 0, .end = 1 }; err = av1_aggr_hdr_decode(&hdr, &mb); if (err) break; ASSERT_EQ(test->z, hdr.z); ASSERT_EQ(test->y, hdr.y); ASSERT_EQ(test->w, hdr.w); ASSERT_EQ(test->n, hdr.n); } out: return err; } static int test_av1_obu(void) { struct av1_obu_hdr hdr; static const uint8_t buf[] = { /* libaom OBU_TEMPORAL_DELIMITER [type=2 x=0 s=1 size=0] */ 0x12, 0x00, /* libaom OBU_SEQUENCE_HEADER [type=1 x=0 s=1 size=12] */ 0x0a, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x3c, 0xff, 0xbf, 0x81, 0xb5, 0x32, 0x00, 0x80 }; struct mbuf mb = { .buf = (uint8_t *)buf, .size = sizeof(buf), .pos = 0, .end = sizeof(buf) }; int err; err = av1_obu_decode(&hdr, &mb); if (err) goto out; ASSERT_EQ(2, hdr.type); ASSERT_EQ(0, hdr.x); ASSERT_EQ(1, hdr.s); ASSERT_EQ(0, hdr.size); err = av1_obu_decode(&hdr, &mb); if (err) goto out; ASSERT_EQ(1, hdr.type); ASSERT_EQ(0, hdr.x); ASSERT_EQ(1, hdr.s); ASSERT_EQ(12, hdr.size); ASSERT_EQ(2, av1_obu_count(buf, sizeof(buf))); out: return err; } static const uint64_t dummy_ts = 0x0102030405060708ULL; #define MAX_OBUS 10 struct test { /* input: */ size_t pktsize; /* output: */ struct mbuf *obus[MAX_OBUS]; size_t obu_index; unsigned marker_count; unsigned new_count; }; static int av1_packet_handler(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg) { struct test *test = arg; struct mbuf *mb = mbuf_alloc(hdr_len + pld_len); struct av1_aggr_hdr aggr_hdr; int err = 0; unsigned count = 0; size_t size = 0; if (!mb) return ENOMEM; ASSERT_EQ(dummy_ts, rtp_ts); ASSERT_TRUE((hdr_len + pld_len) <= test->pktsize); err = mbuf_write_mem(mb, hdr, hdr_len); err |= mbuf_write_mem(mb, pld, pld_len); if (err) goto out; mb->pos = 0; err = av1_aggr_hdr_decode(&aggr_hdr, mb); if (err) goto out; if (aggr_hdr.n) ++test->new_count; if (aggr_hdr.z) { ASSERT_TRUE(test->obus[test->obu_index]->pos > 0); } else { ASSERT_EQ(0, test->obus[test->obu_index]->pos); } while (mbuf_get_left(mb) > 0) { ++count; if (aggr_hdr.w == 0 || count < aggr_hdr.w) { uint64_t decoded_size = 0; err = av1_leb128_decode(mb, &decoded_size); if (err) { goto out; } /* Note: av1_leb128_decode always uses uint64_t, * but mbuf uses size_t, which can be 32 bits */ ASSERT_TRUE(decoded_size <= SIZE_MAX); size = (size_t)decoded_size; ASSERT_TRUE(size <= mbuf_get_left(mb)); } else { size = mbuf_get_left(mb); } err = mbuf_write_mem(test->obus[test->obu_index], mbuf_buf(mb), size); if (err) { goto out; } mbuf_advance(mb, size); if (mbuf_get_left(mb) > 0 || !aggr_hdr.y) { mbuf_set_pos(test->obus[test->obu_index], 0); ++test->obu_index; } } ASSERT_TRUE(aggr_hdr.w == 0 || count == aggr_hdr.w); if (marker) { ++test->marker_count; } out: mem_deref(mb); return err; } static int copy_obu(struct mbuf *mb_bs, const uint8_t *buf, size_t size) { struct av1_obu_hdr hdr; struct mbuf wrap = { .buf = (uint8_t *)buf, .size = size, .pos = 0, .end = size }; char debug[512] = ""; bool has_size = true; int err = av1_obu_decode(&hdr, &wrap); if (err) { DEBUG_WARNING("av1: decode: could not decode OBU" " [%zu bytes]: %m\n", size, err); return err; } re_snprintf(debug, sizeof(debug), "%H\n", av1_obu_print, &hdr); ASSERT_TRUE(str_isset(debug)); switch (hdr.type) { case AV1_OBU_SEQUENCE_HEADER: case AV1_OBU_FRAME_HEADER: case AV1_OBU_METADATA: case AV1_OBU_FRAME: case AV1_OBU_REDUNDANT_FRAME_HEADER: case AV1_OBU_TILE_GROUP: err = av1_obu_encode(mb_bs, hdr.type, has_size, hdr.size, mbuf_buf(&wrap)); if (err) return err; break; case AV1_OBU_TEMPORAL_DELIMITER: case AV1_OBU_TILE_LIST: case AV1_OBU_PADDING: /* MUST be ignored by receivers. */ DEBUG_WARNING("av1: decode: copy: unexpected obu type %u (%s)" " [x=%d, s=%d, size=%zu]\n", hdr.type, av1_obu_name(hdr.type), hdr.x, hdr.s, hdr.size); return EPROTO; default: DEBUG_WARNING("av1: decode: copy: unknown obu type %u (%s)" " [x=%d, s=%d, size=%zu]\n", hdr.type, av1_obu_name(hdr.type), hdr.x, hdr.s, hdr.size); return EPROTO; } out: return err; } static int test_av1_packetize_base(unsigned count_bs, unsigned count_rtp, size_t pktsize, const uint8_t *buf, size_t size, const uint8_t *expected_buf, size_t expected_size) { struct test test; struct mbuf *mb_bs = mbuf_alloc(1024); bool new_flag = true; int err; if (!mb_bs) return ENOMEM; memset(&test, 0, sizeof(test)); ASSERT_EQ(count_bs, av1_obu_count(buf, size)); ASSERT_EQ(count_rtp, av1_obu_count_rtp(buf, size)); test.pktsize = pktsize; for (size_t i = 0; i < MAX_OBUS; ++i) { test.obus[i] = mbuf_alloc(1024); if (!test.obus[i]) { err = ENOMEM; goto out; } } err = av1_packetize(&new_flag, true, dummy_ts, buf, size, test.pktsize, av1_packet_handler, &test); if (err) goto out; ASSERT_EQ(1, test.marker_count); ASSERT_EQ(1, test.new_count); /* prepend Temporal Delimiter */ err = av1_obu_encode(mb_bs, AV1_OBU_TEMPORAL_DELIMITER, true, 0, NULL); TEST_ERR(err); for (size_t i = 0; i < test.obu_index; ++i) { err = copy_obu(mb_bs, mbuf_buf(test.obus[i]), mbuf_get_left(test.obus[i])); TEST_ERR(err); } /* compare bitstream with test-vector */ TEST_MEMCMP(expected_buf, expected_size, mb_bs->buf, mb_bs->end); out: for (size_t i = 0; i < MAX_OBUS; ++i) { mem_deref(test.obus[i]); } mem_deref(mb_bs); return err; } static const uint8_t pkt_aom[] = { /* Temporal Delimiter */ 0x12, 0x00, /* Sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, }; static const uint8_t pkt_aom5[] = { /* Temporal Delimiter */ 0x12, 0x00, /* Sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, /* Frame */ 0x32, 0x17, 0x10, 0x01, 0x92, 0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xb6, 0xd3, 0xfb, 0x3b, 0xe3, 0xe1, 0x31, 0xeb, 0x4f, 0x36, /* Frame */ 0x32, 0x17, 0x10, 0x01, 0x92, 0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xb6, 0xd3, 0xfb, 0x3b, 0xe3, 0xe1, 0x31, 0xeb, 0x4f, 0x36, /* Frame */ 0x32, 0x17, 0x10, 0x01, 0x92, 0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xb6, 0xd3, 0xfb, 0x3b, 0xe3, 0xe1, 0x31, 0xeb, 0x4f, 0x36, }; static const uint8_t pkt_aom_metadata[] = { /* Temporal Delimiter */ 0x12, 0x00, /* Sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, /* OBU frame header */ 0x1a, 0x1b, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x80, /* OBU metadata */ 0x2a, 0x1a, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x80, /* OBU metadata */ 0x2a, 0x06, 0x01, 0x01, 0x02, 0x03, 0x04, 0x80, /* Frame */ 0x32, 0x17, 0x10, 0x01, 0x92, 0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xb6, 0xd3, 0xfb, 0x3b, 0xe3, 0xe1, 0x31, 0xeb, 0x4f, 0x36, }; static const uint8_t pkt_multi_seq[] = { /* Temporal Delimiter */ 0x12, 0x00, /* Padding */ 0x7a, 0x04, 0x01, 0x02, 0x03, 0x04, /* Sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, /* Padding */ 0x7a, 0x04, 0x05, 0x06, 0x07, 0x08, /* Padding */ 0x7a, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, /* Duplicate sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, /* Frame */ 0x32, 0x17, 0x10, 0x01, 0x92, 0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xb6, 0xd3, 0xfb, 0x3b, 0xe3, 0xe1, 0x31, 0xeb, 0x4f, 0x36, }; static const uint8_t pkt_multi_seq_expected[] = { /* Temporal Delimiter */ 0x12, 0x00, /* Sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, /* Duplicate sequence header */ 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80, /* Frame */ 0x32, 0x17, 0x10, 0x01, 0x92, 0x80, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xb6, 0xd3, 0xfb, 0x3b, 0xe3, 0xe1, 0x31, 0xeb, 0x4f, 0x36, }; /* * https://dl8.webmfiles.org/BeachDrone-AV1.webm * * frame 3: size=320 pts=134 (0.134000 sec) * obu: type=2,OBU_TEMPORAL_DELIMITER x=0 s=1 size=0 * obu: type=3,OBU_FRAME_HEADER x=0 s=1 size=23 * obu: type=4,OBU_TILE_GROUP x=0 s=1 size=290 * */ static const char pkt_beach[] = "12001a17301a2049648406a21a47fbdf" "cbb4180c4002041157404022a202001c" "64b538c87ccb8807fc1658bcd98ada85" "6a35745f32824a2ee8d5e11d80476188" "917a6662c19f0ca9eace86b8ac3ae880" "0561949ecbbc26f800d904d1714219a1" "0d1d0410370c6e0b8dead1bf1e8a291b" "fd0a1254a6e038998e091c7d5233b138" "68acf6225840618dcbfd948ed99943dd" "93df6037f6fda997cd2f8467b601d94e" "09169d57f8fa9c8d6abfcab091366231" "48c89c7d5a8b86544140a827f48a2b0b" "15d6836f4ceab733dd2f2ebbb20cb69a" "684dafb9403610e0560bad66b728c8fd" "38c315a1f63ac3d2fca0da95fdbfb9f8" "e61b4f18b90a455dad2fc91a32401007" "2942753e34c95c6d3693a555e660e6ca" "628a22fed94f3618d912b84a272e00da" "44b8cf62a7abfd5d0396e8848d8bd56d" "195bb21814c15700e825a4d9fe2a64f8" ; static int test_av1_packetize_range( unsigned count_bs, unsigned count_rtp, const uint8_t *buf, size_t size, const uint8_t *expected_buf, size_t expected_size) { int err = 0; for (size_t i = 10; i <= 120; ++i) { err = test_av1_packetize_base(count_bs, count_rtp, i, buf, size, expected_buf, expected_size); if (err) { return err; } } return err; } static int test_av1_packetize(void) { uint8_t buf[320]; int err; err = test_av1_packetize_range(2, 1, pkt_aom, sizeof(pkt_aom), pkt_aom, sizeof(pkt_aom)); if (err) return err; err = test_av1_packetize_range(5, 4, pkt_aom5, sizeof(pkt_aom5), pkt_aom5, sizeof(pkt_aom5)); if (err) return err; err = test_av1_packetize_range(6, 5, pkt_aom_metadata, sizeof(pkt_aom_metadata), pkt_aom_metadata, sizeof(pkt_aom_metadata)); if (err) return err; err = test_av1_packetize_range(7, 3, pkt_multi_seq, sizeof(pkt_multi_seq), pkt_multi_seq_expected, sizeof(pkt_multi_seq_expected)); if (err) return err; err = str_hex(buf, sizeof(buf), pkt_beach); if (err) return err; err = test_av1_packetize_range(3, 2, buf, sizeof(buf), buf, sizeof(buf)); if (err) return err; return 0; } #define AV1_PACKET1_SIZE 1188 #define AV1_PACKET2_SIZE 231 struct state { uint8_t buf_packet1[AV1_PACKET1_SIZE]; uint8_t buf_packet2[AV1_PACKET2_SIZE]; unsigned count; }; static int interop_packet_handler(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg) { struct state *state = arg; struct mbuf *mb = mbuf_alloc(hdr_len + pld_len); int err = 0; (void)marker; (void)rtp_ts; if (!mb) return ENOMEM; err = mbuf_write_mem(mb, hdr, hdr_len); err |= mbuf_write_mem(mb, pld, pld_len); if (err) goto out; switch (state->count) { case 0: TEST_MEMCMP(state->buf_packet1, sizeof(state->buf_packet1), mb->buf, mb->end); break; case 1: TEST_MEMCMP(state->buf_packet2, sizeof(state->buf_packet2), mb->buf, mb->end); break; default: err = EPROTO; break; } out: state->count = state->count + 1; mem_deref(mb); return err; } /* * Test AV1 interop with Chrome. */ static int test_av1_interop(void) { #define AV1_FRAME_SIZE 1421 static const char frame[] = "12000a0a00000024cf7f0d80340132fc" "0a10717800ffffff16e6180000000b01" "bbc1318ad86995cba97034ff8767d6fd" "ade65542dbc40b9e44cc8e479f68b4b9" "5c7e78cabd7344021a5d99d51918f3e9" "b6a0afe14686c45b6dc9ff25d4dddd91" "8a4dcc3998be6af61811000b0d886601" "53036febf3c7fef1defb75b3ba396cb6" "f6a8bb84def6603617a995f270102f87" "a1eccaadac954247c9a116a1343ce905" "2aa5b90dba23bc7299b85dd829523aa0" "0a15a2db9e24dc34622ff7c772f4b0dd" "dff7afdbc95748c68d3ab706ffc4b772" "f44fd1bcbe20309a908a0dbdba8ce5db" "1dc9de2d75f48c5976e6fb941b5da795" "e96c5a8a70b5e55d0d8d8d3b084bb09d" "d32e83d121087052ee4597b17f1ac46d" "8b284742c095534146fd6dd6161e67cd" "58ef8f092a75b32585e7efecd001b4ad" "292804ce0aa4318fd7b5824497f39f19" "82174ed1ff800416f565393cfadc7c9e" "0140a4140ab96ac5d7e4b7891e2ff6d7" "6c789d81e28645f3873d1ddbb9e3152c" "4137cc1f13c743fa6454c849e7fe703e" "1e7ee19e5ea3b728b460a67b009fa952" "0608ed4dea672a6df720a892f42203c1" "13cc56903148e249e3b5f5f7266b0cf0" "539ffdac040ef551b589ee92bd4b081a" "652af89d56e7546b2b8ea35300324e2a" "3a74af972642454c5d3ba8caef3fe19c" "a2a31729113858d8f13fbde793ba7834" "b6e855f60b4302e42f8c7d32ed48e50d" "b9a87aa57ec0384293cd7fa4c02f2909" "b68d4f07afbe22059f52efaaab98d170" "ba612e8c05a68e048c3f66b7452269f6" "704346897559ab38dcc4f138e3796217" "02c0661a8f09ab7d57c1e2bbb3d58899" "28d2d189f7d33900c7fe606579a77709" "551254e1d2301f5445857e1d132edc01" "605128705cb22ff1184e70dc8985169e" "aafc996f81116ce8007f141f1908eb9c" "707c415ada0923e42f6e822453b1e330" "385b377e7f19f1d36a93a404affef91b" "6587849ef244940c636f3c458986f104" "174cb6af58160c28c0929aee986da31c" "1a0596ccecedf2dad9202ade93c4010e" "b39462aaf111aa53444fdf654e82a454" "909f97e361026a265c37a0616407589d" "01bb068ece454ba616612a29d67f61a7" "2aac84871f0503752525137a3b189c5e" "34cffb6d600c868eb54125f8861c9bac" "a580ef457eacd68b8dc30f32aa4cb7cb" "d3e20ced165b71c0617024f5423ee017" "3aad3af71a30f33609fcef771c3810b9" "fb61a350cfa97d6e5f219d593d28f4e4" "66590f89ad0851149852225eb07a042f" "9d8fb97f0f2437fb37e3102f6010794b" "e0ad882519f913c8db117aa093e663dd" "2183ac731449e62f803ba24086ea28f3" "814c33bdf9863927b544e1a74ebf6b20" "64dcb92efd8e8b71aab354601f0e75d7" "5686fe86984e6735c4ed2eef2b919236" "4c46a963e88661c5ea8f278fc1efa306" "67046926a2a75c23a5d63af373478cb8" "c55e11f9de4a61d77c5b11080fe258e4" "8509d86aa93249012678d1c40056e9f3" "44261079a1729a7b7853322b016847f4" "6ca4cdd0b107c7aa6024889ccd4b4002" "e2f69b53ed0d0063bf80936fb970bc12" "0fcabeb82b41b2c75bcb5211b6b5d404" "cbdcc175adeaad1ebac4e026989e3365" "d676ff62e674595509f48a43ee2ba010" "f12f8799e4c357fd369a108aa2f1a073" "e7a25e0cdb92be13e5267fe9d8d5e6b5" "31b8cb9f0549ad56e586670133ab39ed" "7124d942c2742f5e78c52f10c009bb48" "13b26fb55217f369c33400976663b912" "c1bd389762be20a040cee498411c47a0" "4c1e53d7b36c958dbdb56b58ebfc5a88" "faca07c3739c9bf28bfb8d7cd50f1fc5" "82d54aee4a17073b0552d989e51d6501" "35bcca12fc5f4c92924912d7a5a91b82" "edb8c0fda7e43526658c4ddd15a0d3e4" "d24a996aa902f9e51b43e67974fd59ed" "3ea2a6ede7ea3033d8d6f2d2dc624204" "558433c6a0a7315e970bba563c0dcb15" "879b64ff57418984b998bd4c70f33c95" "29d1184ad74cbcf14927771f562ae036" "fac2e439966307e5d9ae4d5984" ; static const char packet1[] = /* NOTE: W=2 */ "68" "0b0800000024cf7f0d80340130107178" "00ffffff16e6180000000b01bbc1318a" "d86995cba97034ff8767d6fdade65542" "dbc40b9e44cc8e479f68b4b95c7e78ca" "bd7344021a5d99d51918f3e9b6a0afe1" "4686c45b6dc9ff25d4dddd918a4dcc39" "98be6af61811000b0d88660153036feb" "f3c7fef1defb75b3ba396cb6f6a8bb84" "def6603617a995f270102f87a1eccaad" "ac954247c9a116a1343ce9052aa5b90d" "ba23bc7299b85dd829523aa00a15a2db" "9e24dc34622ff7c772f4b0dddff7afdb" "c95748c68d3ab706ffc4b772f44fd1bc" "be20309a908a0dbdba8ce5db1dc9de2d" "75f48c5976e6fb941b5da795e96c5a8a" "70b5e55d0d8d8d3b084bb09dd32e83d1" "21087052ee4597b17f1ac46d8b284742" "c095534146fd6dd6161e67cd58ef8f09" "2a75b32585e7efecd001b4ad292804ce" "0aa4318fd7b5824497f39f1982174ed1" "ff800416f565393cfadc7c9e0140a414" "0ab96ac5d7e4b7891e2ff6d76c789d81" "e28645f3873d1ddbb9e3152c4137cc1f" "13c743fa6454c849e7fe703e1e7ee19e" "5ea3b728b460a67b009fa9520608ed4d" "ea672a6df720a892f42203c113cc5690" "3148e249e3b5f5f7266b0cf0539ffdac" "040ef551b589ee92bd4b081a652af89d" "56e7546b2b8ea35300324e2a3a74af97" "2642454c5d3ba8caef3fe19ca2a31729" "113858d8f13fbde793ba7834b6e855f6" "0b4302e42f8c7d32ed48e50db9a87aa5" "7ec0384293cd7fa4c02f2909b68d4f07" "afbe22059f52efaaab98d170ba612e8c" "05a68e048c3f66b7452269f670434689" "7559ab38dcc4f138e379621702c0661a" "8f09ab7d57c1e2bbb3d5889928d2d189" "f7d33900c7fe606579a77709551254e1" "d2301f5445857e1d132edc0160512870" "5cb22ff1184e70dc8985169eaafc996f" "81116ce8007f141f1908eb9c707c415a" "da0923e42f6e822453b1e330385b377e" "7f19f1d36a93a404affef91b6587849e" "f244940c636f3c458986f104174cb6af" "58160c28c0929aee986da31c1a0596cc" "ecedf2dad9202ade93c4010eb39462aa" "f111aa53444fdf654e82a454909f97e3" "61026a265c37a0616407589d01bb068e" "ce454ba616612a29d67f61a72aac8487" "1f0503752525137a3b189c5e34cffb6d" "600c868eb54125f8861c9baca580ef45" "7eacd68b8dc30f32aa4cb7cbd3e20ced" "165b71c0617024f5423ee0173aad3af7" "1a30f33609fcef771c3810b9fb61a350" "cfa97d6e5f219d593d28f4e466590f89" "ad0851149852225eb07a042f9d8fb97f" "0f2437fb37e3102f6010794be0ad8825" "19f913c8db117aa093e663dd2183ac73" "1449e62f803ba24086ea28f3814c33bd" "f9863927b544e1a74ebf6b2064dcb92e" "fd8e8b71aab354601f0e75d75686fe86" "984e6735c4ed2eef2b9192364c46a963" "e88661c5ea8f278fc1efa30667046926" "a2a75c23a5d63af373478cb8c55e11f9" "de4a61d77c5b11080fe258e48509d86a" "a93249012678d1c40056e9f344261079" "a1729a7b7853322b016847f46ca4cdd0" "b107c7aa6024889ccd4b4002e2f69b53" "ed0d0063bf80936fb970bc120fcabeb8" "2b41b2c75bcb5211b6b5d404cbdcc175" "adeaad1ebac4e026989e3365d676ff62" "e674595509f48a43ee2ba010f12f8799" "e4c357fd369a108aa2f1a073e7a25e0c" "db92be13e5267fe9d8d5e6b531b8cb9f" "0549ad" ; static const char packet2[] = /* NOTE: W=1 */ "90" "56e586670133ab39ed7124d942c2742f" "5e78c52f10c009bb4813b26fb55217f3" "69c33400976663b912c1bd389762be20" "a040cee498411c47a04c1e53d7b36c95" "8dbdb56b58ebfc5a88faca07c3739c9b" "f28bfb8d7cd50f1fc582d54aee4a1707" "3b0552d989e51d650135bcca12fc5f4c" "92924912d7a5a91b82edb8c0fda7e435" "26658c4ddd15a0d3e4d24a996aa902f9" "e51b43e67974fd59ed3ea2a6ede7ea30" "33d8d6f2d2dc624204558433c6a0a731" "5e970bba563c0dcb15879b64ff574189" "84b998bd4c70f33c9529d1184ad74cbc" "f14927771f562ae036fac2e439966307" "e5d9ae4d5984" ; struct state state; uint8_t buf[AV1_FRAME_SIZE]; bool new_flag = true; int err; state.count = 0; err = str_hex(buf, sizeof(buf), frame); TEST_ERR(err); err = str_hex(state.buf_packet1, sizeof(state.buf_packet1), packet1); TEST_ERR(err); err = str_hex(state.buf_packet2, sizeof(state.buf_packet2), packet2); TEST_ERR(err); err = av1_packetize(&new_flag, true, dummy_ts, buf, sizeof(buf), 1188, interop_packet_handler, &state); if (err) goto out; out: return err; } int test_av1(void) { int err; err = test_leb128(); TEST_ERR(err); err = test_av1_aggr(); TEST_ERR(err); err = test_av1_obu(); TEST_ERR(err); err = test_av1_packetize(); TEST_ERR(err); err = test_av1_interop(); TEST_ERR(err); out: return err; } ================================================ FILE: test/base64.c ================================================ /** * @file base64.c Base64 Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_base64" #define DEBUG_LEVEL 5 #include int test_base64(void) { const struct { struct pl pl; struct pl b64; struct pl b64url; } testv[] = { {PL(""), PL(""), PL("")}, {PL("f"), PL("Zg=="), PL("Zg")}, {PL("fo"), PL("Zm8="), PL("Zm8")}, {PL("foo"), PL("Zm9v"), PL("Zm9v")}, {PL("foob"), PL("Zm9vYg=="), PL("Zm9vYg")}, {PL("fooba"), PL("Zm9vYmE="), PL("Zm9vYmE")}, {PL("foobar"), PL("Zm9vYmFy"), PL("Zm9vYmFy")}, {PL("\xff\x01\xfe\x02"), PL("/wH+Ag=="), PL("_wH-Ag")}, {PL("asdlkjqopinzidfj84r77fsgljsdf9823r"), PL("YXNkbGtqcW9waW56aWRmajg0cjc3ZnNnbGpzZGY5ODIzcg=="), PL("YXNkbGtqcW9waW56aWRmajg0cjc3ZnNnbGpzZGY5ODIzcg")}, {PL("918nvbakishdl8317237dlakskdkaldj"), PL("OTE4bnZiYWtpc2hkbDgzMTcyMzdkbGFrc2tka2FsZGo="), PL("OTE4bnZiYWtpc2hkbDgzMTcyMzdkbGFrc2tka2FsZGo")}, {PL("very10long..testxyzstring/.,-=-3029===7823#'];'#';]#'"), PL("dmVyeTEwbG9uZy4udGVzdHh5enN0cmluZy8uLC0" "9LTMwMjk9PT03ODIzIyddOycjJztdIyc="), PL("dmVyeTEwbG9uZy4udGVzdHh5enN0cmluZy8uLC0" "9LTMwMjk9PT03ODIzIyddOycjJztdIyc")}, }; uint32_t i; int err = 0; uint8_t b64_buf[128]; size_t olen; for (i=0; ip, pl->l, buf, &olen); TEST_ERR(err); if (olen != testv[i].b64.l) { DEBUG_WARNING("b64_encode %u failed: l=%u olen=%u\n", i, testv[i].b64.l, olen); err = EINVAL; TEST_ERR(err); } if (0 != memcmp(testv[i].b64.p, buf, olen)) { DEBUG_WARNING("b64_encode %u failed: ref=%r, enc=%b\n", i, &testv[i].b64, buf, olen); err = EINVAL; TEST_ERR(err); } /* Encode URL */ olen = sizeof(buf); err = base64url_encode((uint8_t *)pl->p, pl->l, buf, &olen); TEST_ERR(err); if (olen != testv[i].b64url.l) { DEBUG_WARNING("b64_encode %u failed: l=%u olen=%u\n", i, testv[i].b64url.l, olen); err = EINVAL; TEST_ERR(err); } if (0 != memcmp(testv[i].b64url.p, buf, olen)) { DEBUG_WARNING("b64_encode %u failed: ref=%r, enc=%b\n", i, &testv[i].b64url, buf, olen); err = EINVAL; TEST_ERR(err); } /* Decode */ b = &testv[i].b64; olen = sizeof(b64_buf); err = base64_decode(b->p, b->l, b64_buf, &olen); TEST_ERR(err); if (olen != testv[i].pl.l) { DEBUG_WARNING("b64_decode %u failed: l=%u olen=%u\n", i, testv[i].pl.l, olen); err = EINVAL; TEST_ERR(err); } if (0 != memcmp(testv[i].pl.p, b64_buf, olen)) { DEBUG_WARNING("b64_decode %u failed: ref=%r, enc=%b\n", i, &testv[i].pl, b64_buf, olen); err = EINVAL; TEST_ERR(err); } /* Decode Url */ b = &testv[i].b64url; olen = sizeof(b64_buf); err = base64_decode(b->p, b->l, b64_buf, &olen); TEST_ERR(err); if (olen != testv[i].pl.l) { DEBUG_WARNING( "b64_decode url %u failed: l=%u olen=%u\n", i, testv[i].pl.l, olen); err = EINVAL; TEST_ERR(err); } if (0 != memcmp(testv[i].pl.p, b64_buf, olen)) { DEBUG_WARNING( "b64_decode url %u failed: ref=%r, enc=%b\n", i, &testv[i].pl, b64_buf, olen); err = EINVAL; TEST_ERR(err); } } /* Invalid checks */ char c = 'A'; olen = sizeof(b64_buf); err = base64_decode(&c, sizeof(c), b64_buf, &olen); TEST_ERR(err); struct pl inv; pl_set_str(&inv, "Zm8="); olen = 1; err = base64_decode(inv.p, inv.l, b64_buf, &olen); TEST_EQUALS(EOVERFLOW, err); err = 0; out: return err; } ================================================ FILE: test/bfcp.c ================================================ /** * @file bfcp.c BFCP Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include "test.h" #define DEBUG_MODULE "bfcptest" #define DEBUG_LEVEL 5 #include static const uint8_t bfcp_msg[] = /* FloorRequest */ "\x20\x01\x00\x04" /* | ver | primitive | length | */ "\x01\x02\x03\x04" /* | conference id | */ "\xfe\xdc\xba\x98" /* | transaction id | user id | */ "" "\x04\x04\x00\x01" /* FLOOR-ID */ "\x02\x04\x00\x02" /* BENEFICIARY-ID */ "\x10\x03\x58\x00" /* PARTICIPANT-PROVIDED-INFO */ "\x08\x04\x40\x00" /* PRIORITY */ /* FloorRelease */ "\x20\x02\x00\x01" /* | ver | primitive | length | */ "\x01\x02\x03\x04" /* | conference id | */ "\xfe\xdc\xba\x98" /* | transaction id | user id | */ "" "\x06\x04\x00\x03" /* FLOOR-REQUEST-ID */ /* UserStatus w/FLOOR-REQUEST-INFORMATION */ "\x20\x06\x00\x12" /* | ver | primitive | length | */ "\x01\x02\x03\x04" /* | conference id | */ "\xfe\xdc\xba\x98" /* | transaction id | user id | */ "" "\x1e\x48\x88\x99" /* FLOOR-ID */ "\x24\x0c\x74\xad" /* OVERALL-REQUEST-STATUS */ "\x0a\x04\x04\x02" "\x12\x04\x4f\x4b" "\x22\x0c\x00\x02" /* FLOOR-REQUEST-STATUS #1 */ "\x0a\x04\x02\x02" "\x12\x04\x6f\x6b" "\x22\x0c\x00\x04" /* FLOOR-REQUEST-STATUS #2 */ "\x0a\x04\x07\x03" "\x12\x04\x6a\x61" "\x1c\x0c\x00\x01" /* BENEFICIARY-INFORMATION */ "\x18\x03\x61\x00" "\x1a\x03\x62\x00" "\x20\x0c\x00\x02" /* REQUESTED-BY-INFORMATION */ "\x18\x03\x63\x00" "\x1a\x03\x64\x00" "\x08\x04\x40\x00" /* PRIORITY */ "\x10\x03\x78\x00" /* PARTICIPANT-PROVIDED-INFO */ /* Hello */ "\x20\x0b\x00\x00" /* | ver | primitive | length | */ "\x01\x02\x03\x04" /* | conference id | */ "\xfe\xdc\xba\x98" /* | transaction id | user id | */ "" ""; static int parse_msg(const uint8_t *p, size_t n) { struct mbuf *mb = mbuf_alloc(512); int err; if (!mb) return ENOMEM; err = mbuf_write_mem(mb, p, n); if (err) return err; mb->pos = 0; while (mbuf_get_left(mb) >= 4) { struct bfcp_msg *msg; err = bfcp_msg_decode(&msg, mb); if (err) break; mem_deref(msg); } mem_deref(mb); return err; } int test_bfcp(void) { const size_t sz = sizeof(bfcp_msg) - 1; struct mbuf *mb; struct bfcp_reqstatus oreqstatus, reqstatus1, reqstatus2; uint16_t floorid = 1, bfid = 2, frid = 3, freqid; uint16_t ofreqid, floorid1, floorid2, rbid; enum bfcp_priority prio = BFCP_PRIO_NORMAL; int n, err = 0; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = bfcp_msg_encode(mb, 1, false, BFCP_FLOOR_REQUEST, 0x01020304, 0xfedc, 0xba98, 4, BFCP_FLOOR_ID, 0, &floorid, BFCP_BENEFICIARY_ID, 0, &bfid, BFCP_PART_PROV_INFO, 0, "X", BFCP_PRIORITY, 0, &prio); if (err) goto out; err = bfcp_msg_encode(mb, 1, false, BFCP_FLOOR_RELEASE, 0x01020304, 0xfedc, 0xba98, 1, BFCP_FLOOR_REQUEST_ID, 0, &frid); if (err) goto out; freqid = 0x8899; ofreqid = 0x74ad; oreqstatus.status = BFCP_DENIED; oreqstatus.qpos = 2; floorid1 = 2; reqstatus1.status = BFCP_ACCEPTED; reqstatus1.qpos = 2; floorid2 = 4; reqstatus2.status = BFCP_REVOKED; reqstatus2.qpos = 3; bfid = 1; rbid = 2; prio = BFCP_PRIO_NORMAL; err = bfcp_msg_encode(mb, 1, false, BFCP_USER_STATUS, 0x01020304, 0xfedc, 0xba98, 1, BFCP_FLOOR_REQ_INFO, 7, &freqid, BFCP_OVERALL_REQ_STATUS, 2, &ofreqid, BFCP_REQUEST_STATUS, 0, &oreqstatus, BFCP_STATUS_INFO, 0, "OK", BFCP_FLOOR_REQ_STATUS, 2, &floorid1, BFCP_REQUEST_STATUS, 0, &reqstatus1, BFCP_STATUS_INFO, 0, "ok", BFCP_FLOOR_REQ_STATUS, 2, &floorid2, BFCP_REQUEST_STATUS, 0, &reqstatus2, BFCP_STATUS_INFO, 0, "ja", BFCP_BENEFICIARY_INFO, 2, &bfid, BFCP_USER_DISP_NAME, 0, "a", BFCP_USER_URI, 0, "b", BFCP_REQUESTED_BY_INFO, 2, &rbid, BFCP_USER_DISP_NAME, 0, "c", BFCP_USER_URI, 0, "d", BFCP_PRIORITY, 0, &prio, BFCP_PART_PROV_INFO, 0, "x"); if (err) goto out; err = bfcp_msg_encode(mb, 1, false, BFCP_HELLO, 0x01020304, 0xfedc, 0xba98, 0); if (err) goto out; if (mb->end != sz) { DEBUG_WARNING("expected %u bytes, got %u bytes\n", sz, mb->end); (void)re_printf("\nEncoded message:\n"); hexdump(stderr, mb->buf, mb->end); err = EPROTO; goto out; } if (!err) { n = memcmp(mb->buf, bfcp_msg, mb->end); if (0 != n) { err = EBADMSG; DEBUG_WARNING("error offset: %d\n", n); } } if (err) { DEBUG_WARNING("BFCP encode error: %m\n", err); (void)re_printf("\nReference message:\n"); hexdump(stderr, bfcp_msg, sz); (void)re_printf("\nEncoded message:\n"); hexdump(stderr, mb->buf, mb->end); goto out; } out: mem_deref(mb); return err; } int test_bfcp_bin(void) { static const uint8_t msg[] = "\x20\x04\x00\x04" "\x00\x00\x00\x01" "\x00\x01\x00\x01" "\x1e\x10\x00\x01" "\x24\x08\x00\x01" "\x0a\x04\x03\x00" "\x22\x04\x00\x02" ""; int err = 0; err |= parse_msg(msg, sizeof(msg) - 1); err |= parse_msg(bfcp_msg, sizeof(bfcp_msg) - 1); return err; } enum handler_flags { conn_handler_called = 1u, estab_handler_called = 1u << 1u, recv_handler_called = 1u << 2u, resp_handler_called = 1u << 3u, close_handler_called = 1u << 4u }; struct test_bfcp_peer { struct bfcp_conn *bfcp; enum bfcp_transp transp; unsigned int flags; struct sa addr, peer; int handler_err; bool client; }; static void test_bfcp_peer_destructor(void *arg) { struct test_bfcp_peer *p = (struct test_bfcp_peer *)arg; mem_deref(p->bfcp); } static void receive_handler(const struct bfcp_msg *msg, void *arg) { struct test_bfcp_peer *p = (struct test_bfcp_peer *)arg; p->flags |= recv_handler_called; DEBUG_INFO("Receive handler called, client: %d\n", (int)p->client); p->handler_err = bfcp_reply(p->bfcp, msg, BFCP_HELLO_ACK, 0u); } static void response_handler(int err, const struct bfcp_msg *msg, void *arg) { struct test_bfcp_peer *p = (struct test_bfcp_peer *)arg; (void)err; (void)msg; p->flags |= resp_handler_called; DEBUG_INFO("Response handler called, client: %d\n", (int)p->client); re_cancel(); } static void close_handler(int err, void *arg) { struct test_bfcp_peer *p = (struct test_bfcp_peer *)arg; (void)err; p->flags |= close_handler_called; DEBUG_INFO("Close handler called, client: %d\n", (int)p->client); } static void established_handler(void *arg) { struct test_bfcp_peer *p = (struct test_bfcp_peer *)arg; p->flags |= estab_handler_called; DEBUG_INFO("Established handler called, client: %d\n", (int)p->client); if (p->transp == BFCP_TCP && p->client) { p->handler_err = bfcp_request(p->bfcp, &p->peer, BFCP_VER2, BFCP_HELLO, 0u, 0u, &response_handler, p, 0u); } } static void connection_handler(const struct sa *peer, void *arg) { struct test_bfcp_peer *p = (struct test_bfcp_peer *)arg; (void)peer; p->flags |= conn_handler_called; DEBUG_INFO("New connection handler called, client: %d\n", (int)p->client); if (p->transp == BFCP_TCP && !p->client) { if (!bfcp_sock(p->bfcp)) { p->handler_err = bfcp_accept(p->bfcp); } else { bfcp_reject(p->bfcp); p->handler_err = EALREADY; } } else { p->handler_err = ENOSYS; } } int test_bfcp_udp(void) { struct test_bfcp_peer *cli = NULL, *srv = NULL; int err = 0; if (test_mode == TEST_MEMORY) { /* OOM testing fails because some mem_alloc fails on * the receiving side, and no packet gets received. * For UDP, this is not registered as a connection timeout. */ err = ESKIPPED; goto out; } cli = (struct test_bfcp_peer *)mem_zalloc(sizeof(*cli), &test_bfcp_peer_destructor); if (!cli) { err = ENOMEM; goto out; } cli->transp = BFCP_UDP; cli->client = true; srv = (struct test_bfcp_peer *)mem_zalloc(sizeof(*srv), &test_bfcp_peer_destructor); if (!srv) { err = ENOMEM; goto out; } srv->transp = BFCP_UDP; err = sa_set_str(&cli->addr, "127.0.0.1", 0); TEST_ERR(err); srv->addr = cli->addr; err = bfcp_listen(&srv->bfcp, BFCP_UDP, &srv->addr, NULL, NULL, NULL, &receive_handler, NULL, srv); TEST_ERR(err); cli->peer = srv->addr; err = bfcp_connect(&cli->bfcp, BFCP_UDP, &cli->addr, &cli->peer, NULL, &receive_handler, NULL, cli); TEST_ERR(err); srv->peer = cli->addr; err = bfcp_request(cli->bfcp, &cli->peer, BFCP_VER1, BFCP_HELLO, 0u, 0u, &response_handler, cli, 0u); TEST_ERR(err); err = re_main_timeout(100); TEST_ERR(err); TEST_EQUALS(resp_handler_called, cli->flags); TEST_EQUALS(recv_handler_called, srv->flags); err = srv->handler_err; out: mem_deref(cli); mem_deref(srv); return err; } int test_bfcp_tcp(void) { struct test_bfcp_peer *cli = NULL, *srv = NULL; int err = 0; cli = (struct test_bfcp_peer *)mem_zalloc(sizeof(*cli), &test_bfcp_peer_destructor); if (!cli) { err = ENOMEM; goto out; } cli->transp = BFCP_TCP; cli->client = true; srv = (struct test_bfcp_peer *)mem_zalloc(sizeof(*srv), &test_bfcp_peer_destructor); if (!srv) { err = ENOMEM; goto out; } srv->transp = BFCP_TCP; err = sa_set_str(&cli->addr, "127.0.0.1", 0); TEST_ERR(err); srv->addr = cli->addr; err = bfcp_listen(&srv->bfcp, BFCP_TCP, &srv->addr, NULL, &connection_handler, &established_handler, &receive_handler, &close_handler, srv); TEST_ERR(err); cli->peer = srv->addr; err = bfcp_connect(&cli->bfcp, BFCP_TCP, &cli->addr, &cli->peer, &established_handler, &receive_handler, &close_handler, cli); TEST_ERR(err); srv->peer = cli->addr; err = re_main_timeout(100); TEST_ERR(err); if (cli->handler_err) { DEBUG_WARNING("client error: %m\n", cli->handler_err); err = cli->handler_err; goto out; } if (srv->handler_err) { DEBUG_WARNING("server error: %m\n", srv->handler_err); err = srv->handler_err; goto out; } TEST_EQUALS((estab_handler_called | resp_handler_called), cli->flags); TEST_EQUALS((conn_handler_called | estab_handler_called | recv_handler_called), srv->flags); err = srv->handler_err; out: mem_deref(cli); mem_deref(srv); return err; } ================================================ FILE: test/btrace.c ================================================ /** * @file btrace.c Backtrace testcode * * Copyright (C) 2025 Alfred E. Heggestad */ #include #include "test.h" #define DEBUG_MODULE "btrace" #define DEBUG_LEVEL 5 #include static int devnull_handler(const char *p, size_t size, void *arg) { (void)p; (void)size; (void)arg; return 0; } int test_btrace(void) { if (test_mode == TEST_THREAD) return ESKIPPED; static struct re_printf pf_devnull = { .vph = devnull_handler }; struct btrace btraces = {0}; int err = btrace(&btraces); TEST_ERR(err); err = btrace_print(&pf_devnull, &btraces); TEST_ERR(err); err = btrace_println(&pf_devnull, &btraces); TEST_ERR(err); err = btrace_print_json(&pf_devnull, &btraces); TEST_ERR(err); out: return err; } ================================================ FILE: test/combo/dtls_turn.c ================================================ /** * @file combo/dtls_turn.c DTLS over TURN combination test * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "dtls_turn" #define DEBUG_LEVEL 5 #include /* * Combined test of DTLS over TURN, involving two agents. * * Agent A: Agent B: * ------- ------- * * * DTLS DTLS * Client Server * | | * | | * | .................UDP * TURN TURN * Client Server * | | * | | * UDP............UDP */ enum { LAYER_DTLS = 100, LAYER_TURN = -100 }; struct agent { /* DTLS layer: */ struct tls *tls; struct dtls_sock *dtls_sock; struct tls_conn *dtls_conn; bool dtls_active; unsigned dtls_n_conn; unsigned dtls_n_estab; unsigned dtls_n_recv; /* TURN layer: */ bool use_turn; bool turn_channels; struct turnc *turnc; struct turnserver *turnsrv; unsigned turn_n_alloc_resp; unsigned turn_n_perm_resp; unsigned turn_n_chan_resp; /* common stuff: */ struct agent *peer; struct udp_sock *us; struct sa addr; unsigned udp_n_recv; int err; }; static int agent_start(struct agent *ag); static void complete_test(struct agent *ag, int err) { ag->err = err; re_cancel(); } static bool are_established(struct agent *ag) { return ag->dtls_n_estab && ag->peer->dtls_n_estab; } static void dtls_estab_handler(void *arg) { struct agent *ag = arg; ++ag->dtls_n_estab; if (are_established(ag)) { re_cancel(); } } static void dtls_recv_handler(struct mbuf *mb, void *arg) { struct agent *ag = arg; int err; ++ag->dtls_n_recv; if (!ag->dtls_active) { /* ECHO SERVER */ err = dtls_send(ag->dtls_conn, mb); if (err) { complete_test(ag, err); } } } static void dtls_close_handler(int err, void *arg) { struct agent *ag = arg; (void)err; ag->dtls_conn = mem_deref(ag->dtls_conn); } static void dtls_conn_handler(const struct sa *src, void *arg) { struct agent *ag = arg; int err; (void)src; TEST_ASSERT(!ag->dtls_active); ++ag->dtls_n_conn; TEST_ASSERT(ag->dtls_conn == NULL); err = dtls_accept(&ag->dtls_conn, ag->tls, ag->dtls_sock, dtls_estab_handler, dtls_recv_handler, dtls_close_handler, ag); if (err) goto out; out: if (err) complete_test(ag, err); } static void turnc_perm_handler(void *arg) { struct agent *ag = arg; ++ag->turn_n_perm_resp; /* Permission has been granted, we can start DTLS */ agent_start(ag); } static void turnc_chan_handler(void *arg) { struct agent *ag = arg; ++ag->turn_n_chan_resp; /* Channel has been created, we can start DTLS */ agent_start(ag); } static bool is_turn_ready(struct agent *ag) { if (ag->use_turn) return ag->turn_n_alloc_resp; else return true; } static bool are_turn_ready(struct agent *ag) { return is_turn_ready(ag) && is_turn_ready(ag->peer); } static int agent_permchan(struct agent *ag) { int err; /* Channels or Permission is needed for sending data */ if (ag->turn_channels) { err = turnc_add_chan(ag->turnc, &ag->peer->addr, turnc_chan_handler, ag); } else { err = turnc_add_perm(ag->turnc, &ag->peer->addr, turnc_perm_handler, ag); } return err; } static void turnc_handler(int err, uint16_t scode, const char *reason, const struct sa *relay_addr, const struct sa *mapped_addr, const struct stun_msg *msg, void *arg) { struct agent *ag = arg; (void)reason; (void)mapped_addr; (void)msg; ++ag->turn_n_alloc_resp; if (err || scode) { complete_test(ag, err ? err : EPROTO); return; } /* Public address must be updated */ ag->addr = *relay_addr; if (are_turn_ready(ag)) { agent_permchan(ag); agent_permchan(ag->peer); } } /* in this test we expect no UDP packets */ static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct agent *ag = arg; (void)src; (void)mb; ++ag->udp_n_recv; } static void destructor(void *arg) { struct agent *ag = arg; mem_deref(ag->dtls_conn); mem_deref(ag->dtls_sock); mem_deref(ag->tls); mem_deref(ag->turnc); mem_deref(ag->turnsrv); mem_deref(ag->us); } static int agent_alloc(struct agent **agp, uint16_t lport, bool use_turn, bool turn_channels, bool dtls_active) { struct agent *ag; int err; ag = mem_zalloc(sizeof(*ag), destructor); if (!ag) return ENOMEM; /* allocate common */ err = sa_set_str(&ag->addr, "127.0.0.1", lport); if (err) goto out; err = udp_listen(&ag->us, &ag->addr, udp_recv, ag); if (err) goto out; err = udp_local_get(ag->us, &ag->addr); if (err) goto out; /* allocate TURN */ ag->use_turn = use_turn; if (use_turn) { ag->turn_channels = turn_channels; err = turnserver_alloc(&ag->turnsrv, "127.0.0.1"); if (err) goto out; err = turnc_alloc(&ag->turnc, NULL, IPPROTO_UDP, ag->us, LAYER_TURN, &ag->turnsrv->laddr, "username", "password", 600, turnc_handler, ag); if (err) goto out; } /* allocate DTLS */ ag->dtls_active = dtls_active; err = tls_alloc(&ag->tls, TLS_METHOD_DTLSV1, NULL, NULL); if (err) goto out; err = tls_set_certificate(ag->tls, test_certificate_ecdsa, strlen(test_certificate_ecdsa)); if (err) goto out; err = dtls_listen(&ag->dtls_sock, NULL, ag->us, 4, LAYER_DTLS, dtls_conn_handler, ag); if (err) goto out; out: if (err) mem_deref(ag); else if (agp) *agp = ag; return err; } static int agent_start(struct agent *ag) { int err = 0; if (ag->dtls_active) { TEST_ASSERT(ag->dtls_conn == NULL); err = dtls_connect(&ag->dtls_conn, ag->tls, ag->dtls_sock, &ag->peer->addr, dtls_estab_handler, dtls_recv_handler, dtls_close_handler, ag); if (err) return err; } out: return err; } static int agent_verify(struct agent *ag) { int err = 0; /* common stuff */ TEST_EQUALS(0, ag->err); TEST_EQUALS(0, ag->udp_n_recv); /* TURN */ if (ag->use_turn) { TEST_EQUALS(1, ag->turn_n_alloc_resp); TEST_EQUALS(ag->turn_channels ? 0 : 1, ag->turn_n_perm_resp); TEST_EQUALS(ag->turn_channels ? 1u : 0, ag->turn_n_chan_resp); TEST_ASSERT(ag->turnsrv->n_allocate >= 1); if (ag->turn_channels) { TEST_ASSERT(ag->turnsrv->n_chanbind >= 1); TEST_ASSERT(ag->turnsrv->n_createperm == 0); TEST_EQUALS(0, ag->turnsrv->n_send); TEST_ASSERT(ag->turnsrv->n_raw >= 2); } else { TEST_ASSERT(ag->turnsrv->n_chanbind == 0); TEST_ASSERT(ag->turnsrv->n_createperm >= 1); TEST_EQUALS(2, ag->turnsrv->n_send); TEST_EQUALS(0, ag->turnsrv->n_raw); } } /* DTLS */ TEST_ASSERT(ag->dtls_conn != NULL); TEST_EQUALS(ag->dtls_active ? 0 : 1, ag->dtls_n_conn); TEST_EQUALS(1, ag->dtls_n_estab); TEST_EQUALS(0, ag->dtls_n_recv); out: return err; } static bool have_dtls_support(enum tls_method method) { struct tls *tls = NULL; int err; err = tls_alloc(&tls, method, NULL, NULL); mem_deref(tls); return err != ENOSYS; } int test_dtls_turn(void) { struct agent *a = NULL, *b = NULL; int err = 0; if (!have_dtls_support(TLS_METHOD_DTLSV1)) { re_fprintf(stderr, "skip DTLS/TURN test\n"); return ESKIPPED; } err = agent_alloc(&a, 0, true, true, true); if (err) goto out; err = agent_alloc(&b, 0, false, false, false); if (err) goto out; /* connect the 2 agents */ if (a) a->peer = b; if (b) b->peer = a; /* start it! */ err = re_main_timeout(1000); if (err) goto out; if (a) { TEST_EQUALS(0, a->err); } if (b) { TEST_EQUALS(0, b->err); } /* verify results after test is complete */ err |= agent_verify(a); err |= agent_verify(b); if (err) goto out; out: mem_deref(b); mem_deref(a); return err; } ================================================ FILE: test/conf.c ================================================ /** * @file conf.c Testcode for Configuration module * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_conf" #define DEBUG_LEVEL 5 #include static int conf_handler(const struct pl *val, void *arg) { uint32_t *count = arg; int err = 0; ++(*count); ASSERT_EQ(*count, pl_u32(val)); out: return err; } int test_conf(void) { static const char *cfg = "string_val\trattarei\n" "u32_val 42\n" "i32_val -23\n" "float_val 1.5\n" "bool_val_1 true\n" "bool_val_2 Yes\n" "bool_val_3 1\n" "bool_val_4 false\n" "apply_val 1\n" "apply_val 2\n" ; char str[256]; struct conf *conf; struct pl pl; uint32_t u32; int32_t i32; double fl; int err; err = conf_alloc_buf(&conf, (uint8_t *)cfg, strlen(cfg)); if (err) return err; err = conf_get_str(conf, "string_val", str, sizeof(str)); TEST_ERR(err); if (strcmp(str, "rattarei")) goto badmsg; err = conf_get_u32(conf, "u32_val", &u32); TEST_ERR(err); TEST_EQUALS(42, u32); err = conf_get_i32(conf, "i32_val", &i32); TEST_ERR(err); TEST_EQUALS(-23, i32); err = conf_get_float(conf, "float_val", &fl); TEST_ERR(err); TEST_EQUALS(1.5, fl); bool val = false; err = conf_get_bool(conf, "bool_val_1", &val); TEST_ERR(err); ASSERT_TRUE(val); err = conf_get_bool(conf, "bool_val_2", &val); TEST_ERR(err); ASSERT_TRUE(val); err = conf_get_bool(conf, "bool_val_3", &val); TEST_ERR(err); ASSERT_TRUE(val); err = conf_get_bool(conf, "bool_val_4", &val); TEST_ERR(err); ASSERT_TRUE(!val); uint32_t count = 0; err = conf_apply(conf, "apply_val", conf_handler, &count); TEST_ERR(err); ASSERT_EQ(2, count); /* Non-existing parameters */ if (0 == conf_get(conf, "rattarei", &pl)) goto badmsg; int error = conf_get(NULL, NULL, NULL); ASSERT_EQ(EINVAL, error); error = conf_get_str(NULL, NULL, NULL, 0); ASSERT_EQ(EINVAL, error); error = conf_get_u32(NULL, NULL, NULL); ASSERT_EQ(EINVAL, error); error = conf_get_i32(NULL, NULL, NULL); ASSERT_EQ(EINVAL, error); error = conf_get_float(NULL, NULL, NULL); ASSERT_EQ(EINVAL, error); out: mem_deref(conf); return err; badmsg: mem_deref(conf); return EBADMSG; } ================================================ FILE: test/convert.c ================================================ /** * @file convert.c Conversion Testcode * * Copyright (C) 2022 Sebastian Reimers */ #include #include "test.h" #define DEBUG_MODULE "testconvert" #define DEBUG_LEVEL 5 #include int test_try_into(void) { int err = 0; size_t size; uint16_t u16 = 0; int i; #if __STDC_VERSION__ >= 201112L /* Needs C11 support */ size = SIZE_MAX; err = try_into(u16, size); TEST_EQUALS(ERANGE, err); #endif size = 5000; err = try_into_u16_from_size(&u16, size); TEST_ERR(err); TEST_EQUALS(size, u16); size = SIZE_MAX; err = try_into_u16_from_size(&u16, size); TEST_EQUALS(ERANGE, err); /* Testing int -> uint16_t */ i = INT_MAX; err = try_into_u16_from_int(&u16, i); TEST_EQUALS(ERANGE, err); i = -50; err = try_into_u16_from_int(&u16, i); TEST_EQUALS(ERANGE, err); /* Testing size_t -> int */ size = SIZE_MAX; err = try_into_int_from_size(&i, size); TEST_EQUALS(ERANGE, err); size = INT_MAX; err = try_into_int_from_size(&i, size); TEST_ERR(err); TEST_EQUALS(INT_MAX, i); out: return err; } ================================================ FILE: test/cplusplus.cpp ================================================ /** * @file cplusplus.cpp Emulate C++ applications * * Copyright (C) 2025 Alfred E. Heggestad */ #include #include #include #include "test.h" #define DEBUG_MODULE "cplusplus" #define DEBUG_LEVEL 5 #include int test_cplusplus(void) { std::cout << "test\n"; DEBUG_NOTICE("%H\n", sys_kernel_get, nullptr); return 0; } ================================================ FILE: test/crc32.c ================================================ /** * @file crc32.c CRC32 Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "testcrc32" #define DEBUG_LEVEL 4 #include int test_crc32(void) { const struct { const char *str; uint32_t crc; } testv[] = { {"string", 0x9ebeb2a9 }, {"hei", 0x95610594 }, {"0ndka98d198aloidks9zaz1oqs5jilk", 0x92a398f6 }, }; size_t i; for (i=0; i #include "test.h" #define DEBUG_MODULE "dbg" #define DEBUG_LEVEL 5 #include int test_dbg(void) { int err = 0; for (int level=0; level<8; level++) { const char *str = dbg_level_str(level); ASSERT_TRUE(str_isset(str)); } out: return err; } ================================================ FILE: test/dd.c ================================================ /** * @file test/dd.c Dependency Descriptor (DD) testcode * * Copyright (C) 2010 - 2023 Alfred E. Heggestad */ #include #include #include #include "test.h" #define DEBUG_MODULE "ddtest" #define DEBUG_LEVEL 5 #include static int test_dd_mand(void) { struct dd dd = { 0 }; struct mbuf *mb = mbuf_alloc(512); if (!mb) return ENOMEM; int err = dd_encode(mb, &dd); TEST_ERR(err); /* Mandatory Descriptor Fields -- 3 bytes only */ static const uint8_t buf_exp[] = { 0, 0, 0 }; TEST_MEMCMP(buf_exp, sizeof(buf_exp), mb->buf, mb->end); struct dd dd_dec; err = dd_decode(&dd_dec, mb->buf, mb->end); TEST_ERR(err); ASSERT_EQ(0, dd_dec.start_of_frame); ASSERT_EQ(0, dd_dec.end_of_frame); ASSERT_EQ(0, dd_dec.frame_dependency_template_id); ASSERT_EQ(0, dd_dec.frame_number); ASSERT_TRUE(!dd_dec.ext); out: mem_deref(mb); return err; } /* 80012f800214eaa860414d1410208426 "startOfFrame":true, "endOfFrame":false, "frameDependencyTemplateId":0, "frameNumber":303, "templateStructure":{ "templateIdOffset":0, "templateInfo":{ "0":{ "spatialId":0, "temporalId":0, "dti":[ "SWITCH", "SWITCH", "SWITCH" ], "fdiff":[ ], "chains":[ 0 ] }, "1":{ "spatialId":0, "temporalId":0, "dti":[ "SWITCH", "SWITCH", "SWITCH" ], "fdiff":[ 4 ], "chains":[ 4 ] }, "2":{ "spatialId":0, "temporalId":1, "dti":[ "NOT_PRESENT", "DISCARDABLE", "SWITCH" ], "fdiff":[ 2 ], "chains":[ 2 ] }, "3":{ "spatialId":0, "temporalId":2, "dti":[ "NOT_PRESENT", "NOT_PRESENT", "DISCARDABLE" ], "fdiff":[ 1 ], "chains":[ 1 ] }, "4":{ "spatialId":0, "temporalId":2, "dti":[ "NOT_PRESENT", "NOT_PRESENT", "DISCARDABLE" ], "fdiff":[ 1 ], "chains":[ 3 ] } }, "decodeTargetInfo":{ "0":{ "protectedBy":0, "spatialId":0, "temporalId":0 }, "1":{ "protectedBy":0, "spatialId":0, "temporalId":1 }, "2":{ "protectedBy":0, "spatialId":0, "temporalId":2 } }, "maxSpatialId":0, "maxTemporalId":2 } } */ static int test_dd_decode(void) { static const char *str = "80012f800214eaa860414d1410208426"; struct mbuf *mb = mbuf_alloc(8); uint8_t buf[16]; int err; if (!mb) return ENOMEM; err = str_hex(buf, sizeof(buf), str); TEST_ERR(err); struct dd dd; err = dd_decode(&dd, buf, sizeof(buf)); TEST_ERR(err); #if 0 dd_print(&dd); #endif ASSERT_EQ(1, dd.start_of_frame); ASSERT_EQ(0, dd.end_of_frame); ASSERT_EQ(0, dd.frame_dependency_template_id); ASSERT_EQ(303, dd.frame_number); ASSERT_EQ(1, dd.template_dependency_structure_present_flag); ASSERT_EQ(0, dd.active_decode_targets_present_flag); ASSERT_EQ(0, dd.custom_dtis_flag); ASSERT_EQ(0, dd.custom_fdiffs_flag); ASSERT_EQ(0, dd.custom_chains_flag); ASSERT_EQ(7, dd.active_decode_targets_bitmask); ASSERT_EQ(0, dd.template_id_offset); ASSERT_EQ(3, dd.dt_cnt); ASSERT_EQ(5, dd.template_cnt); ASSERT_EQ(0, dd.max_spatial_id); ASSERT_EQ(0, dd.template_spatial_id[0]); ASSERT_EQ(0, dd.template_spatial_id[1]); ASSERT_EQ(0, dd.template_spatial_id[2]); ASSERT_EQ(0, dd.template_spatial_id[3]); ASSERT_EQ(0, dd.template_spatial_id[4]); ASSERT_EQ(0, dd.template_temporal_id[0]); ASSERT_EQ(0, dd.template_temporal_id[1]); ASSERT_EQ(1, dd.template_temporal_id[2]); ASSERT_EQ(2, dd.template_temporal_id[3]); ASSERT_EQ(2, dd.template_temporal_id[4]); ASSERT_TRUE(!dd.resolutions_present_flag); ASSERT_EQ(0, dd.render_count); ASSERT_EQ(2, dd.template_dti[0][0]); ASSERT_EQ(2, dd.template_dti[0][1]); ASSERT_EQ(2, dd.template_dti[0][2]); ASSERT_EQ(2, dd.template_dti[1][0]); ASSERT_EQ(2, dd.template_dti[1][1]); ASSERT_EQ(2, dd.template_dti[1][2]); ASSERT_EQ(0, dd.template_dti[2][0]); ASSERT_EQ(1, dd.template_dti[2][1]); ASSERT_EQ(2, dd.template_dti[2][2]); ASSERT_EQ(0, dd.template_dti[3][0]); ASSERT_EQ(0, dd.template_dti[3][1]); ASSERT_EQ(1, dd.template_dti[3][2]); ASSERT_EQ(0, dd.template_dti[4][0]); ASSERT_EQ(0, dd.template_dti[4][1]); ASSERT_EQ(1, dd.template_dti[4][2]); ASSERT_EQ(1, dd.chain_cnt); err = dd_encode(mb, &dd); TEST_ERR(err); TEST_MEMCMP(buf, sizeof(buf), mb->buf, mb->end); out: mem_deref(mb); return err; } /* * Interop test with Chrome Version 118.0.5993.70 */ static int test_dd_chrome(void) { static const char *str = "80000180003a40813f80ef80"; struct mbuf *mb = mbuf_alloc(16); char *debug = NULL; uint8_t buf[12]; int err; if (!mb) return ENOMEM; err = str_hex(buf, sizeof(buf), str); TEST_ERR(err); struct dd dd; err = dd_decode(&dd, buf, sizeof(buf)); TEST_ERR(err); err = re_sdprintf(&debug, "%H", dd_print, &dd); TEST_ERR(err); ASSERT_TRUE(str_isset(debug)); ASSERT_EQ(1, dd.start_of_frame); ASSERT_EQ(0, dd.end_of_frame); ASSERT_EQ(0, dd.frame_dependency_template_id); ASSERT_EQ(1, dd.frame_number); ASSERT_EQ(1, dd.template_dependency_structure_present_flag); ASSERT_EQ(0, dd.active_decode_targets_present_flag); ASSERT_EQ(0, dd.custom_dtis_flag); ASSERT_EQ(0, dd.custom_fdiffs_flag); ASSERT_EQ(0, dd.custom_chains_flag); ASSERT_EQ(1, dd.active_decode_targets_bitmask); ASSERT_EQ(0, dd.template_id_offset); ASSERT_EQ(1, dd.dt_cnt); ASSERT_EQ(2, dd.template_cnt); ASSERT_EQ(0, dd.max_spatial_id); ASSERT_EQ(0, dd.template_spatial_id[0]); ASSERT_EQ(0, dd.template_spatial_id[1]); ASSERT_EQ(0, dd.template_temporal_id[0]); ASSERT_EQ(0, dd.template_temporal_id[1]); ASSERT_TRUE(dd.resolutions_present_flag); ASSERT_EQ(1, dd.render_count); ASSERT_EQ(639, dd.max_render_width_minus_1[0]); ASSERT_EQ(479, dd.max_render_height_minus_1[0]); ASSERT_EQ(2, dd.template_dti[0][0]); ASSERT_EQ(2, dd.template_dti[1][0]); ASSERT_EQ(0, dd.chain_cnt); err = dd_encode(mb, &dd); TEST_ERR(err); TEST_MEMCMP(buf, sizeof(buf), mb->buf, mb->end); out: mem_deref(debug); mem_deref(mb); return err; } int test_dd(void) { int err; err = test_dd_mand(); if (err) return err; err = test_dd_decode(); if (err) return err; err = test_dd_chrome(); if (err) return err; return 0; } ================================================ FILE: test/dns.c ================================================ /** * @file dns.c DNS Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "dns" #define DEBUG_LEVEL 5 #include enum { NUM_TESTS = 32, IP_127_0_0_1 = 0x7f000001, IP_127_0_0_2 = 0x7f000002, IP_127_0_0_3 = 0x7f000003, IP_127_0_0_4 = 0x7f000004, IP_127_0_0_5 = 0x7f000005, }; static const uint8_t IP6_1[16] = { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; static const uint8_t IP6_2[16] = { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; static const uint8_t IP6_3[16] = { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; static const uint8_t IP6_4[16] = { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; static int mkstr(char **strp) { size_t sz = 8; char *str; str = mem_alloc(sz, NULL); if (!str) return ENOMEM; rand_str(str, sz); *strp = str; return 0; } static int mkrr(struct dnsrr *rr, uint16_t type) { int err; err = mkstr(&rr->name); if (err) return err; rr->type = type; rr->dnsclass = DNS_CLASS_IN; rr->ttl = 3600; rr->rdlen = 2; switch (type) { case DNS_TYPE_A: rr->rdata.a.addr = rand_u32(); break; case DNS_TYPE_NS: err |= mkstr(&rr->rdata.ns.nsdname); break; case DNS_TYPE_CNAME: err |= mkstr(&rr->rdata.cname.cname); break; case DNS_TYPE_SOA: err |= mkstr(&rr->rdata.soa.mname); err |= mkstr(&rr->rdata.soa.rname); rr->rdata.soa.serial = rand_u32(); rr->rdata.soa.refresh = rand_u32(); rr->rdata.soa.retry = rand_u32(); rr->rdata.soa.expire = rand_u32(); rr->rdata.soa.ttlmin = rand_u32(); break; case DNS_TYPE_PTR: err |= mkstr(&rr->rdata.ptr.ptrdname); break; case DNS_TYPE_MX: rr->rdata.mx.pref = rand_u16(); err |= mkstr(&rr->rdata.mx.exchange); break; case DNS_TYPE_TXT: err |= mkstr(&rr->rdata.txt.data); break; case DNS_TYPE_AAAA: rand_bytes(rr->rdata.aaaa.addr, 16); break; case DNS_TYPE_SRV: rr->rdata.srv.pri = rand_u16(); rr->rdata.srv.weight = rand_u16(); rr->rdata.srv.port = rand_u16(); err |= mkstr(&rr->rdata.srv.target); break; case DNS_TYPE_NAPTR: rr->rdata.naptr.order = rand_u16(); rr->rdata.naptr.pref = rand_u16(); err |= mkstr(&rr->rdata.naptr.flags); err |= mkstr(&rr->rdata.naptr.services); err |= mkstr(&rr->rdata.naptr.regexp); err |= mkstr(&rr->rdata.naptr.replace); break; } return err; } int test_dns_hdr(void) { struct mbuf *mb; uint16_t u16 = 9753; /* pseudo-random (predictable) */ size_t i; int err = 0; mb = mbuf_alloc(512); if (!mb) return ENOMEM; for (i=0; ipos = mb->end = 0; err = dns_hdr_encode(mb, &hdr); if (err) break; mb->pos = 0; err = dns_hdr_decode(mb, &hdr2); if (err) break; if (0 != memcmp(&hdr, &hdr2, sizeof(hdr))) { (void)re_fprintf(stderr, "dnshdr mismatch:\n%02w\n%02w\n", &hdr, sizeof(hdr), &hdr2, sizeof(hdr2)); err = EBADMSG; break; } u16 *= 17; } for (uint8_t j=0; j<10; j++) { char debug[256] = ""; re_snprintf(debug, sizeof(debug), "%s%s", dns_hdr_opcodename(j), dns_hdr_rcodename(j)); ASSERT_TRUE(str_isset(debug)); } out: mem_deref(mb); return err; } int test_dns_rr(void) { struct hash *ht = NULL; struct dnsrr *rr = NULL, *rr2 = NULL; struct mbuf *mb; size_t i; char debug[256] = ""; int err = ENOMEM; static const uint16_t typev[] = { DNS_TYPE_A, DNS_TYPE_NS, DNS_TYPE_CNAME, DNS_TYPE_SOA, DNS_TYPE_PTR, DNS_TYPE_MX, DNS_TYPE_AAAA, DNS_TYPE_SRV, DNS_TYPE_NAPTR, DNS_TYPE_TXT }; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = hash_alloc(&ht, 32); if (err) goto out; for (i=0; ipos = mb->end = 0; err = dns_rr_encode(mb, rr, 0, ht, 0); if (err) break; mb->pos = 0; err = dns_rr_decode(mb, &rr2, 0); if (err) break; if (!dns_rr_cmp(rr, rr2, true)) { (void)re_fprintf(stderr, "dns_rr:\nrr: %02w\n\nrr2: %02w\n", rr, sizeof(*rr), rr2, sizeof(*rr2)); hexdump(stderr, mb->buf, mb->end); err = EBADMSG; break; } re_snprintf(debug, sizeof(debug), "%H", dns_rr_print, rr); TEST_ASSERT(str_isset(debug)); rr = mem_deref(rr); rr2 = mem_deref(rr2); } out: hash_flush(ht); mem_deref(ht); mem_deref(rr2); mem_deref(rr); mem_deref(mb); return err; } int test_dns_rr_dup(void) { struct dnsrr *rr = NULL, *dup = NULL; size_t i; int err = ENOMEM; static const uint16_t typev[] = { DNS_TYPE_A, DNS_TYPE_NS, DNS_TYPE_CNAME, DNS_TYPE_SOA, DNS_TYPE_PTR, DNS_TYPE_MX, DNS_TYPE_AAAA, DNS_TYPE_SRV, DNS_TYPE_NAPTR, DNS_TYPE_TXT }; for (i=0; iname != dup->name); switch (typev[i]) { case DNS_TYPE_NS: ASSERT_TRUE(rr->rdata.ns.nsdname != dup->rdata.ns.nsdname); break; case DNS_TYPE_CNAME: ASSERT_TRUE(rr->rdata.cname.cname != dup->rdata.cname.cname); break; case DNS_TYPE_SOA: ASSERT_TRUE(rr->rdata.soa.mname != dup->rdata.soa.mname); ASSERT_TRUE(rr->rdata.soa.rname != dup->rdata.soa.rname); break; case DNS_TYPE_PTR: ASSERT_TRUE(rr->rdata.ptr.ptrdname != dup->rdata.ptr.ptrdname); break; case DNS_TYPE_MX: ASSERT_TRUE(rr->rdata.mx.exchange != dup->rdata.mx.exchange); break; case DNS_TYPE_TXT: ASSERT_TRUE(rr->rdata.txt.data != dup->rdata.txt.data); break; case DNS_TYPE_SRV: ASSERT_TRUE(rr->rdata.srv.target != dup->rdata.srv.target); break; case DNS_TYPE_NAPTR: ASSERT_TRUE(rr->rdata.naptr.flags != dup->rdata.naptr.flags); ASSERT_TRUE(rr->rdata.naptr.services != dup->rdata.naptr.services); ASSERT_TRUE(rr->rdata.naptr.regexp != dup->rdata.naptr.regexp); ASSERT_TRUE(rr->rdata.naptr.replace != dup->rdata.naptr.replace); break; } rr = mem_deref(rr); dup = mem_deref(dup); } out: mem_deref(dup); mem_deref(rr); return err; } /* Testcase to reproduce dname_decode looping error */ int test_dns_dname(void) { static struct test { const char *str; } testv[] = { { "c000000c000100000e10002725324a57" "4d6e3837745836435541597754705361" "4c4c626743726e3475424e3642365957" "524e00" }, { "31203700a22c9f17ea75de16785277fa" "db1094a7782b65a177715e45ffc59f9a" "73143748aaaf99aede63325c1f48e7fa" "56f9da" }, }; struct mbuf *mb; char *name = NULL; size_t i; int err = 0; mb = mbuf_alloc(4096); if (!mb) return ENOMEM; for (i=0; istr) / 2; int e; err = str_hex(mb->buf, size, test->str); if (err) goto out; mb->pos = 0; mb->end = size; mb->size = size; /* Expect EINVAL */ e = dns_dname_decode(mb, &name, 0); TEST_EQUALS(EINVAL, e); name = mem_deref(name); } out: mem_deref(mb); return err; } struct test_dns { int err; union { uint32_t ipv4; uint8_t ipv6[16]; } addr; struct dnsc *dnsc; struct dnsrr *rr; }; static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl, struct list *authl, struct list *addl, void *arg) { struct dnsrr *rr = list_ledata(list_head(ansl)); struct test_dns *data = arg; struct sa sa; (void)hdr; (void)authl; (void)addl; (void)arg; if (!data || !rr) { re_cancel(); return; } TEST_ERR(err); data->rr = mem_ref(rr); if (rr->type == DNS_TYPE_A) { sa_set_in(&sa, rr->rdata.a.addr, 0); DEBUG_INFO("%s. IN A %j\n", rr->name, &sa); } else if (rr->type == DNS_TYPE_AAAA) { sa_set_in6(&sa, rr->rdata.aaaa.addr, 0); DEBUG_INFO("%s. IN AAAA %j\n", rr->name, &sa); } out: data->err = err; re_cancel(); } static int check_dns_async(struct dns_query **qp, struct test_dns *data, const char *name, uint32_t addr) { int err; data->addr.ipv4 = addr; data->err = ENODATA; data->rr = NULL; err = dnsc_query(qp, data->dnsc, name, DNS_TYPE_A, DNS_CLASS_IN, true, query_handler, data); TEST_ERR(err); out: return err; } static int check_dns6_async(struct dns_query **qp, struct test_dns *data, const char *name, const uint8_t addr[16]) { int err; memcpy(data->addr.ipv6, addr, 16); data->err = ENODATA; data->rr = NULL; err = dnsc_query(qp, data->dnsc, name, DNS_TYPE_AAAA, DNS_CLASS_IN, true, query_handler, data); TEST_ERR(err); out: return err; } static int check_dns(struct test_dns *data, const char *name, uint32_t addr) { struct dns_query *q = NULL; int err; err = check_dns_async(&q, data, name, addr); TEST_ERR(err); err = re_main_timeout(100); TEST_ERR(err); /* check query handler result */ err = data->err; if (err) goto out; TEST_ASSERT(data->rr); TEST_EQUALS(DNS_TYPE_A, data->rr->type); TEST_EQUALS(addr, data->rr->rdata.a.addr); out: mem_deref(q); mem_deref(data->rr); return err; } static int check_dns6(struct test_dns *data, const char *name, const uint8_t addr[16]) { struct dns_query *q = NULL; int err; err = check_dns6_async(&q, data, name, addr); TEST_ERR(err); err = re_main_timeout(100); TEST_ERR(err); /* check query handler result */ err = data->err; if (err) goto out; TEST_ASSERT(data->rr); TEST_EQUALS(data->rr->type, DNS_TYPE_AAAA); TEST_EQUALS(0, memcmp(data->addr.ipv6, data->rr->rdata.aaaa.addr, 16)); out: mem_deref(q); mem_deref(data->rr); return err; } static int test_dns_integration_param(const char *laddr) { struct dns_server *srv = NULL; struct test_dns data = {0}; int err; /* Setup Mocking DNS Server */ err = dns_server_alloc(&srv, laddr); TEST_ERR(err); err = dns_server_add_a(srv, "test1.example.net", IP_127_0_0_1, 1); TEST_ERR(err); err = dns_server_add_aaaa(srv, "test1.example.net", IP6_1, 1); TEST_ERR(err); err = dnsc_alloc(&data.dnsc, NULL, &srv->addr, 1); TEST_ERR(err); /* Test system getaddrinfo */ dnsc_getaddrinfo(data.dnsc, true); err = check_dns(&data, "localhost", IP_127_0_0_1); TEST_EQUALS(dnsc_getaddrinfo_enabled(data.dnsc), true); TEST_ERR(err); dnsc_getaddrinfo(data.dnsc, false); TEST_EQUALS(dnsc_getaddrinfo_enabled(data.dnsc), false); err = check_dns(&data, "test1.example.net", IP_127_0_0_1); TEST_ERR(err); err = check_dns6(&data, "test1.example.net", IP6_1); TEST_ERR(err); /* Test does not exist */ err = check_dns(&data, "test2.example.net", IP_127_0_0_1); TEST_EQUALS(ENODATA, err); err = check_dns6(&data, "test2.example.net", IP6_1); TEST_EQUALS(ENODATA, err); dns_server_flush(srv); err = dns_server_add_a(srv, "test1.example.net", IP_127_0_0_2, 1); TEST_ERR(err); err = dns_server_add_a(srv, "test2.example.net", IP_127_0_0_3, 1); TEST_ERR(err); err = dns_server_add_a(srv, "test3.example.net", IP_127_0_0_4, 1); TEST_ERR(err); err = dns_server_add_aaaa(srv, "test1.example.net", IP6_2, 1); TEST_ERR(err); err = dns_server_add_aaaa(srv, "test2.example.net", IP6_3, 1); TEST_ERR(err); err = dns_server_add_aaaa(srv, "test3.example.net", IP6_4, 1); TEST_ERR(err); /* --- Test DNS Cache --- */ err = check_dns(&data, "test1.example.net", IP_127_0_0_1); TEST_ERR(err); err = check_dns6(&data, "test1.example.net", IP6_1); TEST_ERR(err); err = check_dns(&data, "test2.example.net", IP_127_0_0_3); TEST_ERR(err); err = check_dns(&data, "test2.example.net", IP_127_0_0_3); TEST_ERR(err); err = check_dns6(&data, "test2.example.net", IP6_3); TEST_ERR(err); err = check_dns6(&data, "test2.example.net", IP6_3); TEST_ERR(err); /* Check another resource record afterwards */ err = check_dns(&data, "test3.example.net", IP_127_0_0_4); TEST_ERR(err); err = check_dns6(&data, "test3.example.net", IP6_4); TEST_ERR(err); sys_msleep(100); re_main_timeout(1); /* --- Check expired TTL --- */ err = check_dns(&data, "test1.example.net", IP_127_0_0_2); TEST_ERR(err); err = check_dns6(&data, "test1.example.net", IP6_2); TEST_ERR(err); /* --- Test explicit DNS cache flush --- */ dns_server_flush(srv); err = dns_server_add_a(srv, "test1.example.net", IP_127_0_0_5, 1); TEST_ERR(err); dnsc_cache_flush(data.dnsc); err = check_dns(&data, "test1.example.net", IP_127_0_0_5); TEST_ERR(err); /* --- Again DNS Cache --- */ err = check_dns(&data, "test1.example.net", IP_127_0_0_5); TEST_ERR(err); out: mem_deref(data.dnsc); mem_deref(srv); return err; } int test_dns_integration(void) { int err; err = test_dns_integration_param("127.0.0.1"); TEST_ERR(err); if (test_ipv6_supported()) { err = test_dns_integration_param("::1"); TEST_ERR(err); } out: return err; } static int test_dns_reg_param(const char *laddr) { struct dns_server *srv = NULL; struct test_dns data = {0}; struct dns_query *q; int err; /* Setup Mocking DNS Server */ err = dns_server_alloc(&srv, laddr); TEST_ERR(err); err = dns_server_add_a(srv, "test1.example.net", IP_127_0_0_1, 1); TEST_ERR(err); err = dns_server_add_aaaa(srv, "test1.example.net", IP6_1, 1); TEST_ERR(err); err = dnsc_alloc(&data.dnsc, NULL, &srv->addr, 1); TEST_ERR(err); err = check_dns(&data, "test1.example.net", IP_127_0_0_1); TEST_ERR(err); err = check_dns6(&data, "test1.example.net", IP6_1); TEST_ERR(err); dns_server_flush(srv); /* --- Test DNS Cache --- */ err = check_dns(&data, "test1.example.net", IP_127_0_0_1); TEST_ERR(err); err = check_dns6(&data, "test1.example.net", IP6_1); TEST_ERR(err); dnsc_cache_flush(data.dnsc); /* --- Test early query cancellation --- */ err = dnsc_query(&q, data.dnsc, "test1.example.net", DNS_TYPE_A, DNS_CLASS_IN, true, query_handler, &data); TEST_ERR(err); mem_deref(q); /* --- Leave query open for cleanup test --- */ err = dnsc_query(NULL, data.dnsc, "test1.example.net", DNS_TYPE_A, DNS_CLASS_IN, true, query_handler, &data); TEST_ERR(err); err = dnsc_query(NULL, data.dnsc, "test1.example.net", DNS_TYPE_AAAA, DNS_CLASS_IN, true, query_handler, &data); TEST_ERR(err); out: mem_deref(data.dnsc); mem_deref(srv); return err; } int test_dns_reg(void) { int err; err = test_dns_reg_param("127.0.0.1"); TEST_ERR(err); if (test_ipv6_supported()) { err = test_dns_reg_param("::1"); TEST_ERR(err); } out: return err; } int test_dns_nameservers(void) { struct sa srvv[8]; uint32_t srvc = RE_ARRAY_SIZE(srvv); int err = dns_srv_get(NULL, 0, srvv, &srvc); TEST_ERR(err); ASSERT_TRUE(srvc >= 1); for (uint32_t i=0; ierr = err; re_cancel(); return; } fix->answers += list_count(ansl); if (fix->answers < EXPECTED_ANSWERS) { err = dnsc_query_srv(NULL, fix->dnsc, "foo.example.com", DNS_TYPE_AAAA, DNS_CLASS_IN, fix->proto, fix->srv_addr, &fix->srvc, false, dns_query_handler, fix); TEST_ERR(err); } out: if (fix->answers >= EXPECTED_ANSWERS || err) { fix->err = err; re_cancel(); } } static int test_dns_param(const char *laddr, int proto) { struct dns_server *srv = NULL; struct fixture fix = { .srvc = 1, .proto = proto }; int err = dnsc_alloc(&fix.dnsc, NULL, NULL, 0); TEST_ERR(err); dnsc_cache_max(fix.dnsc, 0); err = dns_server_alloc(&srv, laddr); TEST_ERR(err); uint8_t ipv6_addr[16] = {0}; err = dns_server_add_aaaa(srv, "foo.example.com", ipv6_addr, 3600); TEST_ERR(err); switch (proto) { case IPPROTO_UDP: fix.srv_addr = &srv->addr; break; case IPPROTO_TCP: fix.srv_addr = &srv->addr_tcp; break; } err = dnsc_query_srv(NULL, fix.dnsc, "foo.example.com", DNS_TYPE_AAAA, DNS_CLASS_IN, proto, fix.srv_addr, &fix.srvc, false, dns_query_handler, &fix); TEST_ERR(err); err = re_main_timeout(5000); TEST_ERR(err); err = fix.err; TEST_ERR(err); ASSERT_TRUE(fix.answers >= EXPECTED_ANSWERS); out: mem_deref(fix.dnsc); mem_deref(srv); return err; } int test_dns_proto(void) { int err; err = test_dns_param("127.0.0.1", IPPROTO_UDP); TEST_ERR(err); err = test_dns_param("127.0.0.1", IPPROTO_TCP); TEST_ERR(err); if (test_ipv6_supported()) { err = test_dns_param("::1", IPPROTO_UDP); TEST_ERR(err); err = test_dns_param("::1", IPPROTO_TCP); TEST_ERR(err); } out: return err; } ================================================ FILE: test/dsp.c ================================================ /** * @file dsp.c Testcode for librem's DSP module * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test/dsp" #define DEBUG_LEVEL 5 #include static int test_saturate(void) { int err = 0; /* saturate_u8 */ TEST_EQUALS( 0, saturate_u8(-100)); TEST_EQUALS( 0, saturate_u8( 0)); TEST_EQUALS( 42, saturate_u8( 42)); TEST_EQUALS(255, saturate_u8( 255)); TEST_EQUALS(255, saturate_u8( 355)); TEST_EQUALS(255, saturate_u8(9692)); /* saturate_s16 */ TEST_EQUALS(-32768, saturate_s16(-65535)); TEST_EQUALS(-32768, saturate_s16(-32768)); TEST_EQUALS( 0, saturate_s16( 0)); TEST_EQUALS( 32767, saturate_s16( 32767)); TEST_EQUALS( 32767, saturate_s16( 65535)); /* saturate_add16 */ TEST_EQUALS(-32768, saturate_add16(-30000, -30000)); TEST_EQUALS( -2000, saturate_add16( -1000, -1000)); TEST_EQUALS( 2, saturate_add16( 1, 1)); TEST_EQUALS( 32767, saturate_add16( 32766, 1)); TEST_EQUALS( 32767, saturate_add16( 30000, 30000)); /* saturate_sub16 */ TEST_EQUALS(-32768, saturate_sub16(-50000, -10000)); TEST_EQUALS( -2000, saturate_sub16( -1000, 1000)); TEST_EQUALS( 0, saturate_sub16( 1, 1)); TEST_EQUALS( 32765, saturate_sub16( 32766, 1)); TEST_EQUALS( 32767, saturate_sub16( 50000, 10000)); out: return err; } int test_dsp(void) { int err; err = test_saturate(); TEST_ERR(err); out: return err; } ================================================ FILE: test/dtls.c ================================================ /** * @file dtls.c DTLS Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "dtls_test" #define DEBUG_LEVEL 5 #include struct dtls_test { bool dtls_srtp; struct dtls_sock *sock_cli, *sock_srv; struct tls_conn *conn_cli, *conn_srv; struct tls *tls; int err; struct { enum srtp_suite suite; uint8_t cli_key[12+32]; uint8_t srv_key[12+32]; } cli, srv; uint8_t fp[32]; char cn[64]; unsigned n_srv_estab; unsigned n_srv_recv; unsigned n_cli_estab; unsigned n_cli_recv; unsigned n_conn; }; static const char *common_name = "127.0.0.1"; static const char *payload_str = "hello from a cute DTLS client"; static void abort_test(struct dtls_test *t, int err) { t->err = err; re_cancel(); } static int send_data(struct dtls_test *t, const char *data) { struct mbuf mb; int err; TEST_ASSERT(t->conn_cli != NULL); mb.buf = (void *)data; mb.pos = 0; mb.end = str_len(data); mb.size = str_len(data); err = dtls_send(t->conn_cli, &mb); TEST_ERR(err); out: return err; } static void srv_estab_handler(void *arg) { struct dtls_test *t = arg; int err = 0; ++t->n_srv_estab; if (t->dtls_srtp) { err = tls_srtp_keyinfo(t->conn_srv, &t->srv.suite, t->srv.cli_key, sizeof(t->srv.cli_key), t->srv.srv_key, sizeof(t->srv.srv_key)); TEST_ERR(err); } out: if (err) abort_test(t, err); } static void srv_recv_handler(struct mbuf *mb, void *arg) { struct dtls_test *t = arg; int err; ++t->n_srv_recv; /* echo */ err = dtls_send(t->conn_srv, mb); TEST_ERR(err); out: if (err) abort_test(t, err); } static void srv_close_handler(int err, void *arg) { struct dtls_test *t = arg; (void)err; t->conn_srv = mem_deref(t->conn_srv); } static void cli_estab_handler(void *arg) { struct dtls_test *t = arg; int err; ++t->n_cli_estab; err = tls_peer_fingerprint(t->conn_cli, TLS_FINGERPRINT_SHA256, t->fp, sizeof(t->fp)); TEST_ERR(err); err = tls_peer_common_name(t->conn_cli, t->cn, sizeof(t->cn)); TEST_ERR(err); if (t->dtls_srtp) { err = tls_srtp_keyinfo(t->conn_cli, &t->cli.suite, t->cli.cli_key, sizeof(t->cli.cli_key), t->cli.srv_key, sizeof(t->cli.srv_key)); TEST_ERR(err); } err = send_data(t, payload_str); TEST_ERR(err); out: if (err) abort_test(t, err); } static void cli_recv_handler(struct mbuf *mb, void *arg) { struct dtls_test *t = arg; int err = 0; ++t->n_cli_recv; TEST_STRCMP(payload_str, strlen(payload_str), mbuf_buf(mb), mbuf_get_left(mb)); out: abort_test(t, err); } static void cli_close_handler(int err, void *arg) { struct dtls_test *t = arg; (void)err; t->conn_cli = mem_deref(t->conn_cli); } static void conn_handler(const struct sa *src, void *arg) { struct dtls_test *t = arg; int err; (void)src; ++t->n_conn; TEST_ASSERT(t->conn_srv == NULL); err = dtls_accept(&t->conn_srv, t->tls, t->sock_srv, srv_estab_handler, srv_recv_handler, srv_close_handler, t); if (err) { if (err == EPROTO) err = ENOMEM; TEST_ERR(err); } out: if (err) abort_test(t, err); } static int test_dtls_srtp_base(enum tls_method method, bool dtls_srtp, const char *srtp_suites, const char *laddr) { struct dtls_test test; struct udp_sock *us = NULL; struct sa cli, srv; uint8_t fp[32]; int err; memset(&test, 0, sizeof(test)); test.dtls_srtp = dtls_srtp; err = tls_alloc(&test.tls, method, NULL, NULL); TEST_ERR(err); err = tls_set_certificate(test.tls, test_certificate_ecdsa, strlen(test_certificate_ecdsa)); TEST_ERR(err); if (dtls_srtp) { err = tls_set_srtp(test.tls, srtp_suites); /* SRTP not supported */ if (err == ENOSYS) { err = 0; goto out; } TEST_ERR(err); } err = tls_fingerprint(test.tls, TLS_FINGERPRINT_SHA256, fp, sizeof(fp)); TEST_EQUALS(0, err); (void)sa_set_str(&cli, laddr, 0); (void)sa_set_str(&srv, laddr, 0); err = udp_listen(&us, &srv, NULL, NULL); TEST_ERR(err); err = udp_local_get(us, &srv); TEST_ERR(err); err = dtls_listen(&test.sock_srv, NULL, us, 4, 0, conn_handler, &test); TEST_ERR(err); err = dtls_listen(&test.sock_cli, &cli, NULL, 4, 0, NULL, NULL); TEST_ERR(err); dtls_set_single(test.sock_cli, true); /* Set a low MTU to force fragmentation and reassembly */ dtls_set_mtu(test.sock_srv, 128); err = dtls_connect(&test.conn_cli, test.tls, test.sock_cli, &srv, cli_estab_handler, cli_recv_handler, cli_close_handler, &test); if (err) { if (err == EPROTO) err = ENOMEM; TEST_ERR(err); } dtls_set_handlers(test.conn_cli, cli_estab_handler, cli_recv_handler, cli_close_handler, &test); err = re_main_timeout(800); TEST_ERR(err); if (test.err) { err = test.err; goto out; } /* verify result after test is complete */ TEST_EQUALS(1, test.n_srv_estab); TEST_EQUALS(1, test.n_srv_recv); TEST_EQUALS(1, test.n_cli_estab); TEST_EQUALS(1, test.n_cli_recv); TEST_EQUALS(1, test.n_conn); TEST_MEMCMP(fp, sizeof(fp), test.fp, sizeof(test.fp)); TEST_STRCMP(common_name, strlen(common_name), test.cn, strlen(test.cn)); if (dtls_srtp) { TEST_EQUALS(test.cli.suite, test.srv.suite); TEST_MEMCMP(test.cli.cli_key, sizeof(test.cli.cli_key), test.srv.cli_key, sizeof(test.srv.cli_key)); TEST_MEMCMP(test.cli.srv_key, sizeof(test.cli.srv_key), test.srv.srv_key, sizeof(test.srv.srv_key)); } out: test.conn_cli = mem_deref(test.conn_cli); test.conn_srv = mem_deref(test.conn_srv); test.sock_cli = mem_deref(test.sock_cli); test.sock_srv = mem_deref(test.sock_srv); test.tls = mem_deref(test.tls); mem_deref(us); return err; } static bool have_dtls_support(enum tls_method method) { struct tls *tls = NULL; int err; err = tls_alloc(&tls, method, NULL, NULL); mem_deref(tls); return err != ENOSYS; } int test_dtls(void) { int err = 0; /* NOTE: DTLS v1.0 should be available on all * supported platforms. */ if (!have_dtls_support(TLS_METHOD_DTLSV1)) { (void)re_printf("skip DTLS 1.0 tests\n"); return ESKIPPED; } else { err = test_dtls_srtp_base(TLS_METHOD_DTLSV1, false, NULL, "127.0.0.1"); if (err) return err; if (test_ipv6_supported()) { err = test_dtls_srtp_base(TLS_METHOD_DTLSV1, false, NULL, "::1"); TEST_ERR(err); } } out: return err; } int test_dtls_srtp(void) { int err = 0; if (!have_dtls_support(TLS_METHOD_DTLSV1)) { (void)re_printf("skip DTLS tests\n"); return ESKIPPED; } err = test_dtls_srtp_base(TLS_METHOD_DTLSV1, true, "SRTP_AES128_CM_SHA1_80", "127.0.0.1"); TEST_ERR(err); err = test_dtls_srtp_base(TLS_METHOD_DTLSV1, true, "SRTP_AES128_CM_SHA1_32", "127.0.0.1"); TEST_ERR(err); err = test_dtls_srtp_base(TLS_METHOD_DTLSV1, true, "SRTP_AEAD_AES_128_GCM", "127.0.0.1"); TEST_ERR(err); err = test_dtls_srtp_base(TLS_METHOD_DTLSV1, true, "SRTP_AEAD_AES_256_GCM", "127.0.0.1"); TEST_ERR(err); out: return err; } ================================================ FILE: test/dtmf.c ================================================ /** * @file dtmf.c Testcode for librem's DTMF module * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "test/dtmf" #define DEBUG_LEVEL 5 #include static void dtmf_dec_handler(char digit, void *arg) { char *buf = arg; buf[str_len(buf)] = digit; } int test_dtmf(void) { #define SRATE 8000 static const char digits[] = "2*A#7"; char dbuf[256] = ""; struct dtmf_dec *dec = NULL; struct mbuf *mb = NULL; size_t i; int err = 0; mb = mbuf_alloc(1024); if (!mb) return ENOMEM; err = dtmf_dec_alloc(&dec, SRATE, 1, dtmf_dec_handler, dbuf); if (err) goto out; /* generate audio samples with test digits */ for (i=0; ibuf, mb->end / 2); TEST_STRCMP(digits, str_len(digits), dbuf, str_len(dbuf)); out: mem_deref(dec); mem_deref(mb); return err; } ================================================ FILE: test/fir.c ================================================ /** * @file fir.c FIR-filter Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "fir" #define DEBUG_LEVEL 5 #include /* 48kHz sample-rate, 8kHz cutoff (pass 0-7kHz, stop 9-24kHz) */ static const int16_t fir_48_8[] = { 238, 198, -123, -738, -1268, -1204, -380, 714, 1164, 376, -1220, -2206, -1105, 2395, 6909, 10069, 10069, 6909, 2395, -1105, -2206, -1220, 376, 1164, 714, -380, -1204, -1268, -738, -123, 198, 238 }; int test_fir(void) { #define NUM_SAMPLES 8 struct fir fir; static const int16_t samp_in[NUM_SAMPLES] = {-8000, -4000, -2000, 0, 2000, 4000, 8000, 4000}; static const int16_t samp_out_exp[NUM_SAMPLES] = { -59, -78, -9, 183, 421, 534, 391, -38}; int16_t samp_out[NUM_SAMPLES]; int err = 0; fir_reset(&fir); /* verify FIR-filter state */ TEST_EQUALS(0, fir.index); /* process the FIR filter */ fir_filter(&fir, samp_out, samp_in, RE_ARRAY_SIZE(samp_in), 1, fir_48_8, RE_ARRAY_SIZE(fir_48_8)); /* verify FIR-filter state */ TEST_EQUALS(NUM_SAMPLES, fir.index); TEST_ASSERT(NUM_SAMPLES <= RE_ARRAY_SIZE(fir.history)); TEST_MEMCMP(samp_in, sizeof(samp_in), fir.history, sizeof(samp_in)); /* verify output samples */ TEST_MEMCMP(samp_out_exp, sizeof(samp_out_exp), samp_out, sizeof(samp_out)); out: return err; } ================================================ FILE: test/fmt.c ================================================ /** * @file fmt.c Formatting Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #include "test.h" #define DEBUG_MODULE "testfmt" #define DEBUG_LEVEL 5 #include int test_fmt_pl(void) { int err; const struct pl pl = PL("rattarei"); const struct pl pl0 = PL("rattarei"); const struct pl pl0_ = PL("rAtTaReI"); const struct pl pl1 = PL("foobar"); const struct pl pl2 = PL("rarei"); const struct pl pl3 = PL("8zmoijdalij32li34jkljsldsjalkfj9jshruhga" "laksjdliasjf98uasehr98wehrdisflsdifholis" "djlweijrdisfjslkdfjslfjowiejrodflaijsdla" "ksjdlaskdfjslkdfjsldfkjsdlfkjsdlkrtjqwli" "ejsldfjsldfkjsdlfkjdiajsduhiafhurjiejidi"); const struct pl pl4 = PL("rattarei4"); const char str0[] = "rattarei"; const char str1[] = "rattaray"; const char str2[] = "foo"; const char str3[] = "bar"; struct pl pl5, pl6; const struct pl pl7 = PL("hei"); const struct pl pl7_ = PL("Hei"); const struct pl pl7__ = PL("Duz"); const struct pl pl_empty = PL(""); /* pl_cmp() */ if (EINVAL != pl_cmp(NULL, NULL)) goto out; if (0 != pl_cmp(&pl, &pl0)) goto out; if (0 == pl_cmp(&pl, &pl1)) goto out; if (0 != pl_cmp(&pl, &pl0)) goto out; if (0 == pl_cmp(&pl, &pl2)) goto out; if (0 != pl_cmp(&pl3, &pl3)) goto out; if (0 != pl_cmp(&pl_null, &pl_null)) goto out; /* pl_casecmp() */ if (EINVAL != pl_casecmp(NULL, NULL)) goto out; if (0 != pl_casecmp(&pl, &pl0)) goto out; if (0 != pl_casecmp(&pl, &pl0_)) goto out; if (0 == pl_casecmp(&pl, &pl1)) goto out; if (0 == pl_casecmp(&pl, &pl4)) goto out; pl5.p = str0; pl5.l = 6; pl6.p = str1; pl6.l = 6; if (0 != pl_casecmp(&pl5, &pl6)) goto out; if (0 != pl_casecmp(&pl, &pl0)) goto out; if (0 != pl_casecmp(&pl, &pl0_)) goto out; if (0 == pl_casecmp(&pl, &pl2)) goto out; if (0 != pl_casecmp(&pl7, &pl7_)) goto out; if (0 == pl_casecmp(&pl7, &pl7__)) goto out; if (0 != pl_casecmp(&pl_null, &pl_null)) goto out; /* pl_strcmp() */ if (EINVAL != pl_strcmp(NULL, NULL)) goto out; if (0 != pl_strcmp(&pl0, str0)) goto out; if (0 == pl_strcmp(&pl0, str1)) goto out; if (0 == pl_strcmp(&pl3, str0)) goto out; /* pl_strncmp() */ err = pl_strncmp(&pl, "rat", 3); TEST_ERR(err); err = pl_strncmp(&pl, "RAT", 3); TEST_EQUALS(EINVAL, err); /* pl_strncasecmp() */ err = pl_strncasecmp(&pl, "RaT", 3); TEST_ERR(err); /* pl_strcasecmp() */ if (EINVAL != pl_strcasecmp(NULL, NULL)) goto out; if (0 != pl_strcasecmp(&pl0_, str0)) goto out; if (0 == pl_strcasecmp(&pl0_, str1)) goto out; if (0 == pl_strcasecmp(&pl3, str0)) goto out; /* pl_strchr() */ if (pl0.p != pl_strchr(&pl0, 'r')) goto out; if (NULL != pl_strchr(&pl0, 'B')) goto out; /* pl_strrchr() */ if (pl0.p + 5 != pl_strrchr(&pl0, 'r')) goto out; if (NULL != pl_strrchr(&pl0, 'B')) goto out; if (NULL != pl_strrchr(&pl_empty, 'r')) goto out; /* pl_strstr() */ if (pl.p != pl_strstr(&pl, str0)) goto out; if (pl1.p != pl_strstr(&pl1, str2)) goto out; if (pl1.p + 3 != pl_strstr(&pl1, str3)) goto out; if (NULL != pl_strstr(&pl, str1)) goto out; if (pl.p != pl_strstr(&pl, "")) goto out; if (NULL != pl_strstr(&pl1, str0)) goto out; /* pl_strip_html */ struct pl pl_html = PL_INIT; char str_html[] = "abc <= test <>")); return 0; out: return EINVAL; } int test_fmt_pl_alloc_dup(void) { int err = 0; const struct pl pl0 = PL("rAtTaReI"); struct pl *pl = pl_alloc_dup(&pl0); if (!pl) return ENOMEM; TEST_EQUALS(pl0.l, pl->l); TEST_MEMCMP(pl0.p, pl0.l, pl->p, pl->l); err = pl_cmp(&pl0, pl); TEST_ERR(err); out: mem_deref(pl); return err; } int test_fmt_pl_alloc_str(void) { int err = 0; char test_str[] = "Test String"; struct pl *pl = pl_alloc_str(test_str); if (!pl) return ENOMEM; TEST_MEMCMP(test_str, str_len(test_str), pl->p, pl->l); out: mem_deref(pl); return err; } int test_fmt_pl_i32(void) { const struct { const struct pl pl; int32_t v; } testv[] = { /* Error cases */ {PL("hei"), 0}, {PL("abc"), 0}, {PL(""), 0}, {{NULL, 2}, 0}, {{"fo", 0}, 0}, {PL("2147483648"), -2147483647 - 1}, {PL("9223372036854775808"), 0}, /* Working cases */ {PL("0"), 0}, {PL("1"), 1}, {PL("-1"), -1}, {PL("123"), 123}, {PL("-123"), -123}, {PL("5467"), 5467}, {PL("-123"), -123}, {PL("-5467"), -5467}, {PL("2147483647"), 2147483647}, {PL("+2147483647"), 2147483647}, {PL("-2147483648"), -2147483647 - 1}, }; uint32_t i; int err = 0; for (i=0; i .0); v = pl_float(&neg); TEST_ASSERT(v < .0); out: return err; } int test_fmt_regex(void) { const struct pl pl = PL("hei42sann!"); const struct pl pl1 = PL("42"); const struct pl pl2 = PL("sann"); const struct pl pl3 = PL(";foo=\"bla;bla\""); const struct pl pl4 = PL("bla;bla"); const struct pl pl5 = PL("a \"b \"1123\"\" c"); const struct pl pl6 = PL("-42"); const struct pl pla = PL("a"); const struct pl plb = PL("b \"1123\""); const struct pl plc = PL("c"); struct pl pln, pls, foo, a, b, c, d, e; int err = 0; /* Successful case */ err = re_regex(pl.p, pl.l, "Hei[0-9]+[^!]+", &pln, &pls); if (err) goto out; err = pl_cmp(&pln, &pl1); if (err) goto out; err = pl_cmp(&pls, &pl2); if (err) goto out; /* Quoted strings */ err = re_regex(pl3.p, pl3.l, ";foo=\"[^\"]+\"", &foo); if (err) goto out; err = pl_cmp(&foo, &pl4); if (err) { DEBUG_WARNING("regex quoted string failed (%r)\n", &foo); goto out; } err = re_regex(pl3.p, pl3.l, ";foo=[~]+", &foo); if (err) goto out; err = pl_cmp(&foo, &pl4); if (err) goto out; /* re_regex('a "b \"1123\"" c', "[^ ]+ [~ ]+ [^ ]+", &a, &b, &c); result: a='a', b='b \"1123\"', c='c' */ err = re_regex(pl5.p, pl5.l, "[^ ]+ [~ ]+ [^ ]+", &a, &b, &c); if (err) goto out; err = pl_cmp(&a, &pla); if (err) goto out; err = pl_cmp(&b, &plb); if (err) goto out; err = pl_cmp(&c, &plc); if (err) goto out; /* Failing case */ if (0 == re_regex(pl.p, pl.l, "tull")) { err = EINVAL; goto out; } if (0 == re_regex(pl.p, pl.l, "[^\r\n]+\r\n", &pln)) { err = EINVAL; goto out; } /* Test escaping */ if (0 == re_regex(pl.p, pl.l, "[\\^0-9]*\\]", NULL)) { err = EINVAL; goto out; } err = re_regex(pl6.p, pl6.l, "[\\-0-9\\^]+", &pln); if (err) goto out; err = pl_strcmp(&pln, "-42"); if (err) goto out; /* verify that optional matching sets the PL to zero if there is no match */ e.p = "x"; e.l = 42; err = re_regex(pl4.p, pl4.l, "[a-z]+;[0-9]*", &d, &e); if (err) goto out; TEST_ASSERT(!pl_isset(&e)); return 0; out: DEBUG_WARNING("regex failed (%d)\n", err); return err; } static int fooprint(struct re_printf *pf, void *arg) { int a = *(int *)arg; return re_hprintf(pf, "[a=%d]", a); } static int va_printf(struct mbuf *mb, const char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = mbuf_printf(mb, "[%v]", fmt, &ap); va_end(ap); return err; } int test_fmt_print(void) { const struct pl ref1 = PL("-12345 -1234567890 -1234567890123456789"); const struct pl ref2 = PL("12345 1234567890 1234567890123456789"); const struct pl ref3 = PL("65535 4294967295 18446744073709551615"); struct pl pl; struct mbuf mb; const int a = 42; char *s = NULL; int err; mbuf_init(&mb); err = mbuf_printf(&mb, "%d %ld %lld", -12345, -1234567890L, -1234567890123456789LL); if (err) goto out; pl.p = (char *)mb.buf; pl.l = mb.end; err = pl_cmp(&pl, &ref1); if (err) { DEBUG_WARNING("print 1: ref=(%r) buf=(%r)\n", &ref1, &pl); goto out; } mbuf_reset(&mb); err = mbuf_printf(&mb, "%u %lu %llu", 12345, 1234567890UL, 1234567890123456789ULL); if (err) goto out; pl.p = (char *)mb.buf; pl.l = mb.end; err = pl_cmp(&pl, &ref2); if (err) { DEBUG_WARNING("print 2: buf: %r\n", &pl); goto out; } mbuf_reset(&mb); err = mbuf_printf(&mb, "%u %lu %llu", 65535, 4294967295UL, 18446744073709551615ULL); if (err) goto out; pl.p = (char *)mb.buf; pl.l = mb.end; err = pl_cmp(&pl, &ref3); if (err) { DEBUG_WARNING("print 3: buf: %r\n", &pl); goto out; } mbuf_reset(&mb); err = mbuf_printf(&mb, "fookokaoskdokoskdookokokq%Hbar", fooprint, &a); if (err) goto out; pl.p = (char *)mb.buf; pl.l = mb.end; err = pl_strcmp(&pl, "fookokaoskdokoskdookokokq[a=42]bar"); if (err) { DEBUG_WARNING("print 4: buf: %r\n", &pl); goto out; } mbuf_reset(&mb); err = va_printf(&mb, "foo%d%s", 42, "barrompabarplaplsdpalspdlplplp"); if (err) goto out; pl.p = (char *)mb.buf; pl.l = mb.end; err = pl_strcmp(&pl, "[foo42barrompabarplaplsdpalspdlplplp]"); if (err) { DEBUG_WARNING("print 5: buf: %r\n", &pl); goto out; } /* dynamic print */ err = re_sdprintf(&s, "okaspdokaspodkjalsj%fkmzl12kpdokasdlkj", 3.14); if (err) goto out; pl_set_str(&pl, s); err = pl_strcmp(&pl, "okaspdokaspodkjalsj3.140000kmzl12kpdokasdlkj"); if (err) { DEBUG_WARNING("sdprintf: %r\n", &pl); goto out; } out: mbuf_reset(&mb); mem_deref(s); return err; } int test_fmt_snprintf(void) { const struct pl ref3 = PL("65535 4294967295 18446744073709551615"); const uint8_t v[] = {0xfa, 0xce, 0xb0, 0x0c}; struct sa sa4; const char addr4[] = "1.2.3.4"; struct sa sa6; const char addr6[] = "2001:5c0:8fff:ffff::d"; char buf[128], sbuf[8]; int n, err; /* Test binary vector printing */ n = re_snprintf(buf, sizeof(buf), "%w", v, sizeof(v)); if (2*sizeof(v) != n) { err = EINVAL; goto out; } if (0 != strcmp(buf, "faceb00c")) { err = EINVAL; goto out; } /* Test sockaddr printing */ err = sa_set_str(&sa4, addr4, 0); if (err) { DEBUG_WARNING("sa_set_str4: %m\n", err); goto out; } err = sa_set_str(&sa6, addr6, 0); if (err) { DEBUG_WARNING("sa_set_str6: %m\n", err); goto out; } (void)re_snprintf(buf, sizeof(buf), "%j", &sa4); if (0 != strcmp(buf, addr4)) { err = EINVAL; goto out; } (void)re_snprintf(buf, sizeof(buf), "%j", &sa6); if (0 != strcmp(buf, addr6)) { err = EINVAL; goto out; } /* Overflow */ n = re_snprintf(buf, 3, "12"); if (2 != n) { err = EINVAL; goto out; } n = re_snprintf(buf, 3, "123"); if (-1 != n) { err = EINVAL; goto out; } n = re_snprintf(buf, 4, "%u", 12345); if (-1 != n) { err = EINVAL; goto out; } n = re_snprintf(buf, 4, "%s", "asdasd"); if (-1 != n) { err = EINVAL; goto out; } n = re_snprintf(buf, 37, "%r", &ref3); if (-1 != n) { err = EINVAL; goto out; } /* Double */ (void)re_snprintf(buf, sizeof(buf), "%f", 123.456); if (0 != strcmp(buf, "123.456000")) goto perr; (void)re_snprintf(buf, sizeof(buf), "%f", -123.456); if (0 != strcmp(buf, "-123.456000")) goto perr; (void)re_snprintf(buf, sizeof(buf), "%.3f", 123.456); if (0 != strcmp(buf, "123.456")) goto perr; (void)re_snprintf(buf, sizeof(buf), "%6.3f", 3.14); if (0 != strcmp(buf, " 3.140")) goto perr; (void)re_snprintf(buf, sizeof(buf), "%06.3f", 3.14); if (0 != strcmp(buf, "03.140")) goto perr; (void)re_snprintf(buf, sizeof(buf), "%6.3f", -3.14); if (0 != strcmp(buf, "-3.140")) goto perr; #if 0 (void)re_snprintf(buf, sizeof(buf), "%05f", strtod("inf", NULL)); if (0 != strcmp(buf, " inf")) goto perr; #endif (void)re_snprintf(buf, sizeof(buf), "%.2f", 123123123123.00); if (0 != strcmp(buf, "123123123123.00")) goto perr; memset(sbuf, 0xff, sizeof(sbuf)); n = re_snprintf(sbuf, sizeof(sbuf), "ab %d cd", 42); if (n != -1 || strcmp(sbuf, "ab 42")) { DEBUG_WARNING("n=%d sbuf='%s'\n", n, sbuf); goto perr; } out: return err; perr: DEBUG_WARNING("bad msg: '%s'\n", buf); return EBADMSG; } int test_fmt_str(void) { const struct { const char *dst; const char *src; uint32_t n; } testv[] = { {"foo", "foo", 64}, {"foo", "foo", 4}, {"fo", "foo", 3}, {"123456789", "1234567890", 10} }; char buf[64]; size_t i; int err = 0; for (i=0; i= RE_ARRAY_SIZE(testv)) { DEBUG_WARNING("param: too many parameters (%u > %u)\n", *i, RE_ARRAY_SIZE(testv)); *err = EOVERFLOW; return; } if (!testv[*i].present) { DEBUG_WARNING("param: %u: unexpected param '%r'\n", *i, name); *err = EBADMSG; } if (0 != pl_strcmp(name, testv[*i].pname)) { DEBUG_WARNING("param: %u: name mismatch: '%r' != '%s'\n", *i, name, testv[*i].pname); *err = EBADMSG; } if (testv[*i].pval && 0 != pl_strcmp(val, testv[*i].pval)) { DEBUG_WARNING("param: %u: value mismatch: '%r' != '%s'\n", *i, val, testv[*i].pval); *err = EBADMSG; } ++(*i); } int test_fmt_param(void) { size_t i; int err = 0; void *argv[2]; argv[0] = &i; argv[1] = &err; for (i=0; i= 0); pl_set_str(&pl, buf); TEST_EQUALS(pl.l, (size_t)n); TEST_EQUALS(pl.l, 12); err = re_regex(pl.p, pl.l, "[0-2]1[0-9]1:[0-5]1[0-9]1:[0-5]1[0-9]1\\.[0-9]3", NULL, NULL, NULL, NULL, NULL, NULL, NULL); TEST_ERR(err); out: return err; } int test_fmt_str_error(void) { char buf[256]; int err = 0; TEST_ASSERT(str_isset(str_error(EINVAL, buf, sizeof(buf)))); out: return err; } int test_fmt_unicode(void) { const char input[] = "abc\\b\\f\\n\\r\\t\\u0001"; char buf[1024], buf2[1024]; struct pl pl; int err = 0; pl_set_str(&pl, input); re_snprintf(buf, sizeof(buf), "%H", utf8_decode, &pl); TEST_STRCMP("abc\b\f\n\r\t\x01", 9U, buf, str_len(buf)); re_snprintf(buf2, sizeof(buf2), "%H", utf8_encode, buf); TEST_STRCMP(input, str_len(input), buf2, str_len(buf2)); out: return err; } int test_fmt_unicode_decode(void) { static const struct test { const char *utf8_ref; const char *str; } unitestv[] = { /* UTF8 Binary: Unicode: */ { "\x40", "\\u0040" }, /* The '@' symbol */ { "\xc3\x85", "\\u00C5" }, { "\xE2\x82\xAC", "\\u20AC" }, /* Euro symbol */ { "\xF0\x9D\x84\x9E", "\\uD834\\uDD1E" }, /* G-key */ { "\xF0\x9F\x98\x82", "\\uD83D\\uDE02" }, /* Face with tears of joy */ }; size_t i; char buf[256]; int err = 0; for (i=0; iutf8_ref); pl_set_str(&pl, test->str); n = re_snprintf(buf, sizeof(buf), "%H", utf8_decode, &pl); #if 0 re_printf("printed: '%s'\n", buf); #endif TEST_MEMCMP(test->utf8_ref, n_exp, buf, n); } out: return err; } int test_fmt_str_bool(void) { bool en; int err = 0; size_t i; enum curstate { STATE_TRUE, STATE_FALSE, STATE_UNSUP }; enum curstate state = STATE_TRUE; static const char *truestr[] = { "1", "true", "enable", "on", "yes", "Yes", "YEs", }; static const char *falsestr[] = { "0", "false", "disable", "off", "no", "NO", }; static const char *notsup[] = { "xxx", "not", "sure", "notsure", "YESS", }; for (i = 0; i < RE_ARRAY_SIZE(truestr); i++) { err = str_bool(&en, truestr[i]); TEST_ERR(err); TEST_EQUALS(true, en); struct pl s; pl_set_str(&s, truestr[i]); err = pl_bool(&en, &s); TEST_ERR(err); TEST_EQUALS(true, en); } state = STATE_FALSE; for (i = 0; i < RE_ARRAY_SIZE(falsestr); i++) { err = str_bool(&en, falsestr[i]); TEST_ERR(err); TEST_EQUALS(false, en); struct pl s; pl_set_str(&s, falsestr[i]); err = pl_bool(&en, &s); TEST_ERR(err); TEST_EQUALS(false, en); } state = STATE_UNSUP; for (i = 0; i < RE_ARRAY_SIZE(notsup); i++) { err = str_bool(&en, notsup[i]); TEST_EQUALS(err, EINVAL); struct pl s; pl_set_str(&s, notsup[i]); err = pl_bool(&en, &s); TEST_EQUALS(err, EINVAL); } err = 0; out: if (err && state == STATE_UNSUP) { DEBUG_WARNING("processed unsupported string number %d: %s\n", i, notsup[i]); } else if (err) { DEBUG_WARNING("could not successfully convert %s\n", state == STATE_TRUE ? truestr[i] : falsestr[i]); } return err; } int test_fmt_str_itoa(void) { char buf[ITOA_BUFSZ]; char *s; int err = 0; s = str_itoa(0, buf, 10); TEST_ASSERT(!str_casecmp(s, "0")); s = str_itoa(42, buf, 10); TEST_ASSERT(!str_casecmp(s, "42")); s = str_itoa(UINT32_MAX, buf, 10); TEST_ASSERT(!str_casecmp(s, "4294967295")); s = str_itoa(UINT32_MAX, buf, 16); TEST_ASSERT(!str_casecmp(s, "FFFFFFFF")); s = str_itoa(23, buf, 2); TEST_ASSERT(!str_casecmp(s, "10111")); s = str_itoa(UINT32_MAX, buf, 2); TEST_ASSERT(!str_casecmp(s, "11111111111111111111111111111111")); out: if (err) DEBUG_WARNING("err itoa string: %s\n", s); return err; } int test_fmt_str_wchar(void) { wchar_t s1[] = L"Test String"; wchar_t *s2; int err = 0; s2 = str_wchar("Test String"); if (!s2) return ENOMEM; TEST_EQUALS(0, wcscmp(s1, s2)); out: mem_deref(s2); return err; } int test_fmt_hexdump(void) { const char buf[] = "0lnmdj2ihickdspjkm2ffd0jcpbk5l1n" "8abcjt5m950gxvkuvippcvt60me9z5zh" ; #ifdef WIN32 FILE *f = fopen("nul", "w"); #else FILE *f = fopen("/dev/null", "w"); #endif if (!f) return EINVAL; hexdump(f, buf, str_len(buf)); fclose(f); return 0; } int test_text2pcap(void) { char test[64]; struct mbuf *mb; int err = 0; mb = mbuf_alloc(2); if (!mb) return ENOMEM; mbuf_write_u8(mb, 42); mbuf_write_u8(mb, 23); mbuf_set_pos(mb, 0); struct re_text2pcap pcap = {.id = "test", .in = true, .mb = mb}; int ret = re_snprintf(test, sizeof(test), "%H", re_text2pcap, &pcap); TEST_EQUALS(35, ret); mbuf_set_pos(mb, 0); re_text2pcap_trace("retest", "RETEST", true, mb); out: mem_deref(mb); return err; } int test_fmt_trim(void) { const struct pl pla = PL(" heisann "); const struct pl pll = PL("heisann "); const struct pl plr = PL(" heisann"); const struct pl plb = PL("heisann"); const struct pl pl = PL(" \r \n heisann \n \r "); const struct pl pl1 = PL("heisann \n \r "); const struct pl pl2 = PL(" \r \n heisann"); const struct pl pl3 = PL("heisann"); const struct pl pl4 = PL(" \r \n"); const struct pl pl5 = PL(""); struct pl pln; int err = 0; pln = pla; err = pl_ltrim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pll); TEST_ERR(err); pln = pla; err = pl_rtrim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &plr); TEST_ERR(err); pln = pla; err = pl_trim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &plb); TEST_ERR(err); pln = pl1; err = pl_ltrim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pl1); TEST_ERR(err); pln = pl; err = pl_rtrim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pl2); TEST_ERR(err); pln = pl; err = pl_trim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pl3); TEST_ERR(err); pln = pl4; err = pl_ltrim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pl5); TEST_ERR(err); pln = pl4; err = pl_rtrim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pl5); TEST_ERR(err); pln = pl4; err = pl_trim(&pln); TEST_ERR(err); err = pl_cmp(&pln, &pl5); TEST_ERR(err); out: return err; } ================================================ FILE: test/g711.c ================================================ /** * @file g711.c G.711 test vectors * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "test_g711" #define DEBUG_LEVEL 5 #include /* G.711 reference tables - copied from re/src/g711/g711.c */ static const signed short _st_alaw2linear16[256] = { -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 }; static const signed short _st_ulaw2linear16[256] = { -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876, -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372, -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120, -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, -2, 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876, 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372, 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 2 }; int test_g711_alaw(void) { uint32_t i; uint8_t val; int n = 0; int err = 0; val = g711_pcm2alaw(-32768); TEST_EQUALS(42, val); for (i=0; i<256; i++) { uint8_t alaw = i, alaw2; int16_t pcm_ref = _st_alaw2linear16[alaw]; int16_t pcm = g711_alaw2pcm(alaw); if (pcm_ref != pcm) { DEBUG_INFO("alaw: %u: bogus sample: ref=%d gen=%d\n", i, pcm_ref, pcm); ++n; } alaw2 = g711_pcm2alaw(pcm); if (alaw2 != alaw) { DEBUG_INFO("alaw: %u: bogus alaw %d != %d\n", i, alaw, alaw2); ++n; } } if (n) { DEBUG_WARNING("alaw: error samples: %d\n", n); } out: return n ? EINVAL : err; } int test_g711_ulaw(void) { uint32_t i; uint8_t val; int n = 0; int err = 0; val = g711_pcm2ulaw(-32768); TEST_EQUALS(0, val); for (i=0; i<256; i++) { uint8_t ulaw = i, ulaw2; int16_t pcm_ref = _st_ulaw2linear16[ulaw]; int16_t pcm = g711_ulaw2pcm(ulaw); if (pcm != pcm_ref) { DEBUG_INFO("ulaw: %u: bogus sample ref=%d gen=%d\n", i, pcm_ref, pcm); ++n; } ulaw2 = g711_pcm2ulaw(pcm_ref); if (ulaw2 != ulaw) { DEBUG_INFO("ulaw: %u: bogus ulaw %d != %d\n", i, ulaw, ulaw2); ++n; } } if (n) { DEBUG_WARNING("ulaw: error samples: %d\n", n); } out: return n ? EINVAL : err; } ================================================ FILE: test/h264.c ================================================ /** * @file h264.c H.264 Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "h264test" #define DEBUG_LEVEL 5 #include enum { DUMMY_TS = 36000 }; #if 0 static void dump_annexb(const uint8_t *start, size_t size) { const uint8_t *end = start + size; unsigned count = 0; re_printf("---- H.264 Annex-B: ----\n"); const uint8_t *r = h264_find_startcode(start, end); while (r < end) { struct h264_nal_header hdr; /* skip zeros */ while (!*(r++)) ; const uint8_t *r1 = h264_find_startcode(r, end); size_t nal_len = r1 - r; h264_nal_header_decode_buf(&hdr, r); re_printf(".... nal: len=%2zu nri=%u type=%s\n", nal_len, hdr.nri, h264_nal_unit_name(hdr.type)); r = r1; ++count; } re_printf("Total NAL units: %u\n", count); re_printf("\n"); } static void dump_rtp(const uint8_t *p, size_t size) { struct h264_nal_header hdr; h264_nal_header_decode_buf(&hdr, p); re_printf("RTP NAL: size=%zu nri=%u type=%u(%s)\n", size, hdr.nri, hdr.type, h264_nal_unit_name(hdr.type)); re_printf("\n"); } #endif static int test_h264_stap_a_encode_base(const uint8_t *frame, size_t len, bool long_startcode) { enum { MAX_NRI = 3 }; struct mbuf *mb_pkt = mbuf_alloc(256); struct mbuf *mb_frame = mbuf_alloc(256); struct h264_nal_header hdr; int err; if (!mb_pkt || !mb_frame) { err = ENOMEM; goto out; } err = h264_stap_encode(mb_pkt, frame, len); if (err) goto out; mb_pkt->pos = 0; err = h264_nal_header_decode(&hdr, mb_pkt); ASSERT_EQ(0, err); ASSERT_EQ(MAX_NRI, hdr.nri); /* NOTE: max NRI */ ASSERT_EQ(H264_NALU_STAP_A, hdr.type); if (long_startcode) { err = h264_stap_decode_annexb_long(mb_frame, mb_pkt); ASSERT_EQ(0, err); } else { err = h264_stap_decode_annexb(mb_frame, mb_pkt); ASSERT_EQ(0, err); } TEST_MEMCMP(frame, len, mb_frame->buf, mb_frame->end); out: mem_deref(mb_frame); mem_deref(mb_pkt); return err; } static int test_h264_stap_a_encode(void) { static const uint8_t frame[] = { /* AUD */ 0x00, 0x00, 0x01, 0x09, 0x10, /* SPS */ 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x8c, 0x8d, 0x40, /* PPS */ 0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80, /* IDR_SLICE */ 0x00, 0x00, 0x01, 0x65, 0xb8, 0x00, 0x04, 0x00, 0x00, 0x05, 0x39, }; static const uint8_t frame_long[] = { /* AUD */ 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, /* SPS */ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x8c, 0x8d, 0x40, /* PPS */ 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80, /* IDR_SLICE */ 0x00, 0x00, 0x00, 0x01, 0x65, 0xb8, 0x00, 0x04, 0x00, 0x00, 0x05, 0x39, }; int err; err = test_h264_stap_a_encode_base(frame, sizeof(frame), false); TEST_ERR(err); err = test_h264_stap_a_encode_base(frame_long, sizeof(frame_long), true); TEST_ERR(err); out: return err; } static int test_h264_stap_a_decode(void) { static const uint8_t pkt[] = { /* SPS */ 0x00, 0x0e, 0x67, 0x42, 0xc0, 0x1f, 0x8c, 0x8d, 0x40, 0x50, 0x1e, 0xd0, 0x0f, 0x08, 0x84, 0x6a, /* PPS */ 0x00, 0x04, 0x68, 0xce, 0x3c, 0x80, /* AUD */ 0x00, 0x02, 0x09, 0x10, }; struct mbuf *mb_pkt = mbuf_alloc(256); struct mbuf *mb_frame = mbuf_alloc(256); struct mbuf *mb_pkt2 = mbuf_alloc(256); int err; if (!mb_pkt || !mb_frame || !mb_pkt2) { err = ENOMEM; goto out; } err = mbuf_write_mem(mb_pkt, pkt, sizeof(pkt)); ASSERT_EQ(0, err); mb_pkt->pos = 0; err = h264_stap_decode_annexb(mb_frame, mb_pkt); TEST_ERR(err); err = h264_stap_encode(mb_pkt2, mb_frame->buf, mb_frame->end); ASSERT_EQ(0, err); TEST_MEMCMP(pkt, sizeof(pkt), mb_pkt2->buf+1, mb_pkt2->end-1); out: mem_deref(mb_frame); mem_deref(mb_pkt2); mem_deref(mb_pkt); return err; } int test_h264(void) { struct h264_nal_header hdr, hdr2; static const uint8_t nal = 0x25; int err; struct mbuf *mb = mbuf_alloc(1); if (!mb) return ENOMEM; hdr.f = 0; hdr.nri = 1; hdr.type = H264_NALU_IDR_SLICE; err = h264_nal_header_encode(mb, &hdr); if (err) goto out; TEST_EQUALS(1, mb->pos); TEST_EQUALS(1, mb->end); TEST_EQUALS(nal, mb->buf[0]); mb->pos = 0; err = h264_nal_header_decode(&hdr2, mb); if (err) goto out; TEST_EQUALS(1, mb->pos); TEST_EQUALS(1, mb->end); TEST_EQUALS(0, hdr2.f); TEST_EQUALS(1, hdr2.nri); TEST_EQUALS(5, hdr2.type); err = test_h264_stap_a_encode(); if (err) goto out; err = test_h264_stap_a_decode(); if (err) goto out; out: mem_deref(mb); return err; } int test_h264_sps(void) { static const struct test { const char *buf; struct h264_sps sps; struct vidsz size; } testv[] = { /* sony 1920 x 1080 (scaling list) * * sps:0 profile:122/41 poc:0 ref:0 120x68 MB-AFF 8B8 * crop:0/0/0/8 VUI 422 1/50 b10 reo:-1 * */ { .buf = "7a1029b6d420223319c6632321011198ce33191821033a46" "656a6524ade91232141a2634ada441822301502b1a246948" "30402e111208c68c0441284c34f01e0113f2e03c60202028" "0000030008000003019420", .sps = { 122,41,0,2, 4,0,0,120,68, 0,0,0,8 }, .size = {1920, 1080} }, /* rv * * sps:0 profile:66/52 poc:2 ref:1 120x68 FRM 8B8 * crop:0/0/0/8 VUI 420 1/360 b8 reo:0 */ { .buf = "42c034da01e0089f961000000300", .sps = { 66,52,0,1, 4,2,1,120,68, 0,0,0,8 }, .size = {1920, 1080} }, /* confcall * * sps:0 profile:100/40 poc:0 ref:3 120x68 FRM * 8B8 crop:0/0/0/8 VUI 420 1/60 b8 reo:1 */ { .buf = "640028acd100780227e5c05a808080" "a0000003002000000781e3062240", .sps = { 100,40,0,1, 4,0,3,120,68, 0,0,0,8 }, .size = {1920, 1080} }, /* expert * * sps:0 profile:100/31 poc:0 ref:4 80x45 FRM */ { .buf = "64001facd9405005bb011000000300100000030320f1831960", .sps = { 100,31,0,1, 4,0,4,80,45, 0,0,0,0 }, .size = {1280, 720} }, /* px * * sps:0 profile:66/31 poc:2 ref:1 80x45 FRM * crop:0/0/0/0 VUI 420 2000/120000 b8 reo:0 */ { .buf = "42c01f95a014016c8400001f40000753023c2211a8", .sps = { 66,31,0,1, 8,2,1,80,45, 0,0,0,0 }, .size = {1280, 720} }, /* allonsy 854x480 * * sps:0 profile:77/30 poc:0 ref:3 54x30 FRM 8B8 * crop:0/10/0/0 VUI 420 1/50 b8 reo:1 * */ { .buf = "4d401ee8806c1ef37808800000030080000019078b1689", .sps = { 77,30,0,1, 4,0,3,54,30, 0,10,0,0 }, .size = {854, 480} }, /* sony 1920x1080 * * sps:0 profile:122/40 poc:0 ref:4 120x68 FRM 8B8 * crop:0/0/0/8 VUI 422 1/50 b10 reo:2 * */ { .buf = "7a0028b6cd940780227e2701100" "0000300100000030320f1831960", .sps = { 122,40,0,2, 4,0,4,120,68, 0,0,0,8 }, .size = {1920, 1080} }, /* testsrc2 yuv444 400x200 * * sps:0 profile:244/13 poc:0 ref:4 25x13 FRM 8B8 * crop:0/0/0/8 VUI 444 1/50 b8 reo:2 * */ { .buf = "f4000d919b283237f13808800000030080000019078a14cb", .sps = { 244,13,0,3, 4,0,4,25,13, 0,0,0,8 }, .size = {400, 200} }, /* jellyfish 4K 3840 x 2160 * * sps:0 profile:100/51 poc:0 ref:3 240x135 FRM 8B8 * crop:0/0/0/0 VUI 420 1001/60000 b8 reo:1 * */ { .buf = "640033ac2ca400f0010fbff0001000152020202800001f" "4800075307510001cd9400000568bc37e31c1da162d120", .sps = { 100,51,0,1, 8,0,3,240,135, 0,0,0,0 }, .size = {3840, 2160} }, }; const struct test *test_short; struct h264_sps sps; uint8_t buf[256]; size_t i; size_t max_len; int e, err; for (i=0; isps; size_t len = str_len(test->buf)/2; struct vidsz size; err = str_hex(buf, len, test->buf); if (err) return err; err = h264_sps_decode(&sps, buf, len); if (err) return err; h264_sps_resolution(&sps, &size.w, &size.h); TEST_EQUALS(ref.profile_idc, sps.profile_idc); TEST_EQUALS(ref.level_idc, sps.level_idc); TEST_EQUALS(ref.seq_parameter_set_id, sps.seq_parameter_set_id); TEST_EQUALS(ref.chroma_format_idc, sps.chroma_format_idc); TEST_EQUALS(ref.log2_max_frame_num, sps.log2_max_frame_num); TEST_EQUALS(ref.pic_order_cnt_type, sps.pic_order_cnt_type); TEST_EQUALS(ref.max_num_ref_frames, sps.max_num_ref_frames); TEST_EQUALS(ref.pic_width_in_mbs, sps.pic_width_in_mbs); TEST_EQUALS(ref.pic_height_in_map_units, sps.pic_height_in_map_units); TEST_EQUALS(ref.frame_crop_left_offset, sps.frame_crop_left_offset); TEST_EQUALS(ref.frame_crop_right_offset, sps.frame_crop_right_offset); TEST_EQUALS(ref.frame_crop_top_offset, sps.frame_crop_top_offset); TEST_EQUALS(ref.frame_crop_bottom_offset, sps.frame_crop_bottom_offset); /* verify correct resolution */ TEST_EQUALS(test->size.w, size.w); TEST_EQUALS(test->size.h, size.h); } test_short = &testv[0]; max_len = str_len(test_short->buf) / 2; err = str_hex(buf, max_len, test_short->buf); if (err) return err; for (i = 1; i <= max_len; i++) { size_t len = i; e = h264_sps_decode(&sps, buf, len); switch (e) { case EBADMSG: case EINVAL: case 0: break; default: DEBUG_WARNING("unexpected error code %d (%m)\n", e, e); return EINVAL; } } out: return err; } struct state { /* depacketizer */ struct mbuf *mb; size_t frag_start; bool frag; bool long_startcode; /* test */ uint8_t buf[256]; size_t len; unsigned count; bool complete; }; static void fragment_rewind(struct state *vds) { vds->mb->pos = vds->frag_start; vds->mb->end = vds->frag_start; } static int depack_handle_h264(struct state *st, bool marker, struct mbuf *src) { static const uint8_t nal_seq3[3] = {0, 0, 1}; static const uint8_t nal_seq4[4] = {0, 0, 0, 1}; struct h264_nal_header h264_hdr; size_t nal_seq_len = st->long_startcode ? sizeof(nal_seq4) : sizeof(nal_seq3); int err; err = h264_nal_header_decode(&h264_hdr, src); if (err) return err; #if 0 re_printf("decode: %s %s type=%2d %s \n", marker ? "[M]" : " ", h264_is_keyframe(h264_hdr.type) ? "" : " ", h264_hdr.type, h264_nal_unit_name(h264_hdr.type)); #endif if (h264_hdr.f) { DEBUG_WARNING("H264 forbidden bit set!\n"); return EBADMSG; } /* handle NAL types */ if (1 <= h264_hdr.type && h264_hdr.type <= 23) { --src->pos; /* prepend H.264 NAL start sequence */ err = mbuf_write_mem(st->mb, st->long_startcode ? nal_seq4 : nal_seq3, nal_seq_len); err |= mbuf_write_mem(st->mb, mbuf_buf(src), mbuf_get_left(src)); if (err) goto out; } else if (H264_NALU_FU_A == h264_hdr.type) { struct h264_fu fu; err = h264_fu_hdr_decode(&fu, src); if (err) return err; h264_hdr.type = fu.type; if (fu.s) { if (st->frag) { DEBUG_WARNING("start: lost fragments;" " ignoring previous NAL\n"); fragment_rewind(st); } st->frag_start = st->mb->pos; st->frag = true; /* prepend H.264 NAL start sequence */ mbuf_write_mem(st->mb, st->long_startcode ? nal_seq4 : nal_seq3, nal_seq_len); /* encode NAL header back to buffer */ err = h264_nal_header_encode(st->mb, &h264_hdr); if (err) goto out; } else { if (!st->frag) { re_printf("ignoring fragment" " (nal=%u)\n", fu.type); return 0; } } err = mbuf_write_mem(st->mb, mbuf_buf(src), mbuf_get_left(src)); if (err) goto out; if (fu.e) st->frag = false; } else if (H264_NALU_STAP_A == h264_hdr.type) { if (st->long_startcode) err = h264_stap_decode_annexb_long(st->mb, src); else err = h264_stap_decode_annexb(st->mb, src); if (err) goto out; } else { DEBUG_WARNING("decode: unknown NAL type %u\n", h264_hdr.type); return EBADMSG; } if (!marker) return 0; /* verify complete packet */ st->complete = true; TEST_MEMCMP(st->buf, st->len, st->mb->buf, st->mb->end); out: mbuf_rewind(st->mb); st->frag = false; return err; } static int packet_handler(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg) { struct state *state = arg; struct mbuf *mb_pkt = mbuf_alloc(hdr_len + pld_len); int err = 0; if (!mb_pkt) return ENOMEM; ASSERT_EQ(DUMMY_TS, rtp_ts); ++state->count; if (hdr && hdr_len) err |= mbuf_write_mem(mb_pkt, hdr, hdr_len); if (pld && pld_len) err |= mbuf_write_mem(mb_pkt, pld, pld_len); if (err) goto out; mb_pkt->pos = 0; err = depack_handle_h264(state, marker, mb_pkt); out: mem_deref(mb_pkt); return err; } static int test_h264_packet_base(const char *bs, bool long_startcode, size_t max_pktsize) { struct state state; int err; memset(&state, 0, sizeof(state)); state.long_startcode = long_startcode; state.len = strlen(bs)/2; err = str_hex(state.buf, state.len, bs); if (err) return err; state.mb = mbuf_alloc(1024); if (!state.mb) return ENOMEM; err = h264_packetize(DUMMY_TS, state.buf, state.len, max_pktsize, packet_handler, &state); if (err) goto out; ASSERT_TRUE(state.count >= 1); ASSERT_TRUE(state.complete); out: mem_deref(state.mb); return err; } /* bitstream in Annex-B format (with startcode 00 00 01) */ static const char *bitstream = "0000016701020304" "0000016801020304" "000001650010e2238712983719283719823798"; /* bitstream in Annex-B format (with startcode 00 00 00 01) */ static const char *bitstream_long = "000000016701020304" "000000016801020304" "00000001650010e2238712983719283719823798"; int test_h264_packet(void) { const size_t MAX_PKTSIZE = 8; int err; err = test_h264_packet_base(bitstream, false, MAX_PKTSIZE); TEST_ERR(err); err = test_h264_packet_base(bitstream_long, true, MAX_PKTSIZE); TEST_ERR(err); out: return err; } ================================================ FILE: test/h265.c ================================================ /** * @file h265.c H.265 Testcode * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include #include #include "test.h" #define DEBUG_MODULE "h265test" #define DEBUG_LEVEL 5 #include int test_h265(void) { uint8_t buf[H265_HDR_SIZE]; struct h265_nal hdr; enum {TID = 1}; int err; h265_nal_encode(buf, H265_NAL_VPS_NUT, TID); err = h265_nal_decode(&hdr, buf); if (err) goto out; ASSERT_EQ(32, hdr.nal_unit_type); ASSERT_EQ(TID, hdr.nuh_temporal_id_plus1); ASSERT_TRUE(!h265_is_keyframe(H265_NAL_VPS_NUT)); ASSERT_TRUE( h265_is_keyframe(H265_NAL_IDR_W_RADL)); for (unsigned i=0; i<=49; i++) { char debug[256] = ""; struct h265_nal nal = { .nal_unit_type = i }; re_snprintf(debug, sizeof(debug), "%H", h265_nal_print, &nal); ASSERT_TRUE(str_isset(debug)); } out: return err; } enum { H265_FU_HDR_SIZE = 1 }; struct h265_fu { unsigned s:1; unsigned e:1; unsigned type:6; }; static inline int h265_fu_decode(struct h265_fu *fu, struct mbuf *mb) { uint8_t v; if (mbuf_get_left(mb) < 1) return EBADMSG; v = mbuf_read_u8(mb); fu->s = v>>7 & 0x1; fu->e = v>>6 & 0x1; fu->type = v>>0 & 0x3f; return 0; } struct state { /* depacketizer */ struct mbuf *mb; /* test */ uint8_t buf[256]; size_t len; unsigned count; bool complete; }; static int depack_handle_h265(struct state *st, bool marker, struct mbuf *mb) { static const uint8_t nal_seq[3] = {0, 0, 1}; struct h265_nal hdr; int err; if (mbuf_get_left(mb) < H265_HDR_SIZE) return EBADMSG; err = h265_nal_decode(&hdr, mbuf_buf(mb)); if (err) return err; mbuf_advance(mb, H265_HDR_SIZE); #if 0 re_printf("h265: decode: [%s] %s type=%2d %s\n", marker ? "M" : " ", h265_is_keyframe(hdr.nal_unit_type) ? "" : " ", hdr.nal_unit_type, h265_nalunit_name(hdr.nal_unit_type)); #endif /* handle NAL types */ if (hdr.nal_unit_type <= 40) { mb->pos -= H265_HDR_SIZE; err = mbuf_write_mem(st->mb, nal_seq, 3); err |= mbuf_write_mem(st->mb, mbuf_buf(mb),mbuf_get_left(mb)); if (err) goto out; } else if (H265_NAL_FU == hdr.nal_unit_type) { struct h265_fu fu; err = h265_fu_decode(&fu, mb); if (err) return err; if (fu.s) { hdr.nal_unit_type = fu.type; err = mbuf_write_mem(st->mb, nal_seq, 3); err |= h265_nal_encode_mbuf(st->mb, &hdr); if (err) goto out; } err = mbuf_write_mem(st->mb, mbuf_buf(mb), mbuf_get_left(mb)); if (err) goto out; } else if (hdr.nal_unit_type == H265_NAL_AP) { while (mbuf_get_left(mb) >= 2) { const uint16_t len = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < len) return EBADMSG; err = mbuf_write_mem(st->mb, nal_seq, 3); err |= mbuf_write_mem(st->mb, mbuf_buf(mb), len); if (err) goto out; mb->pos += len; } } else { DEBUG_WARNING("unknown H265 NAL type %u (%s) [%zu bytes]\n", hdr.nal_unit_type, h265_nalunit_name(hdr.nal_unit_type), mbuf_get_left(mb)); return EPROTO; } if (!marker) return 0; /* verify complete packet */ st->complete = true; TEST_MEMCMP(st->buf, st->len, st->mb->buf, st->mb->end); out: mbuf_rewind(st->mb); return err; } enum { DUMMY_TS = 36000 }; static int packet_handler(bool marker, uint64_t rtp_ts, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t pld_len, void *arg) { struct state *state = arg; struct mbuf *mb_pkt = mbuf_alloc(hdr_len + pld_len); int err; if (!mb_pkt) return ENOMEM; ASSERT_EQ(DUMMY_TS, rtp_ts); ++state->count; if (hdr && hdr_len) { err = mbuf_write_mem(mb_pkt, hdr, hdr_len); if (err) goto out; } err = mbuf_write_mem(mb_pkt, pld, pld_len); if (err) goto out; mb_pkt->pos = 0; err = depack_handle_h265(state, marker, mb_pkt); out: mem_deref(mb_pkt); return err; } /* bitstream in Annex-B format (with startcode 00 00 01) */ /* H265_NAL_VPS_NUT */ static const char *bitstream = "00000140010c01ffff01600000030090000003000003003cba024000000001"; static int test_h265_packet_param(size_t pktsize) { struct state state; int err; memset(&state, 0, sizeof(state)); state.len = strlen(bitstream)/2; err = str_hex(state.buf, state.len, bitstream); if (err) return err; state.mb = mbuf_alloc(1024); if (!state.mb) return ENOMEM; err = h265_packetize(DUMMY_TS, state.buf, state.len, pktsize, packet_handler, &state); if (err) goto out; ASSERT_TRUE(state.count >= 1); ASSERT_TRUE(state.complete); out: mem_deref(state.mb); return err; } int test_h265_packet(void) { int err; err = test_h265_packet_param(8); TEST_ERR(err); err = test_h265_packet_param(1500); TEST_ERR(err); out: return err; } ================================================ FILE: test/hash.c ================================================ /** * @file hash.c Hash Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "testhash" #define DEBUG_LEVEL 4 #include struct my_elem { struct le he; const struct pl *name; /* hash key */ }; const struct pl martin = PL("Martin"), alfred = PL("Alfred"), atle = PL("Atle"); static bool hash_cmp_handler(struct le *le, void *arg) { const struct my_elem *me = le->data; const struct pl *name = arg; return 0==pl_cmp(me->name, name); } static int test_hash_basic(void) { #define BUCKET_SIZE 4 struct my_elem elems[3]; struct hash *h; struct my_elem *elem; int err; /* Clear hash elements */ memset(elems, 0, sizeof(elems)); elems[0].name = &martin; elems[1].name = &alfred; elems[2].name = &atle; err = hash_alloc(&h, BUCKET_SIZE); if (err) return err; TEST_EQUALS(BUCKET_SIZE, hash_bsize(h)); /* API test */ if (hash_lookup(NULL, hash_joaat_pl(elems[0].name), hash_cmp_handler, (void *)elems[0].name)) { err = EINVAL; goto out; } if (hash_lookup(h, hash_joaat_pl(elems[0].name), NULL, (void *)elems[0].name)) { err = EINVAL; goto out; } /* Hashtable is empty */ hash_unlink(&elems[0].he); hash_unlink(&elems[1].he); /* Hashtable with 1 element */ hash_append(h, hash_joaat_pl(elems[0].name), &elems[0].he, &elems[0]); elem = list_ledata(hash_lookup(h, hash_joaat_pl(elems[0].name), hash_cmp_handler, (void *)elems[0].name)); if (elem != &elems[0]) { err = EINVAL; goto out; } elem = list_ledata(hash_lookup(h, hash_joaat_pl(elems[1].name), hash_cmp_handler, (void *)elems[1].name)); if (elem) { err = EINVAL; goto out; } /* Hashtable with 2 elements */ hash_append(h, hash_joaat_pl(elems[1].name), &elems[1].he, &elems[1]); elem = list_ledata(hash_lookup(h, hash_joaat_pl(elems[0].name), hash_cmp_handler, (void *)elems[0].name)); if (elem != &elems[0]) { err = EINVAL; goto out; } elem = list_ledata(hash_lookup(h, hash_joaat_pl(elems[1].name), hash_cmp_handler, (void *)elems[1].name)); if (elem != &elems[1]) { err = EINVAL; goto out; } hash_unlink(&elems[0].he); hash_unlink(&elems[1].he); err = 0; out: h = mem_deref(h); return err; } static int test_hash_robustapi(void) { struct hash *h = NULL; struct le he; int err = 0; TEST_EQUALS(EINVAL, hash_alloc(NULL, 4)); TEST_EQUALS(EINVAL, hash_alloc(&h, 0)); hash_append(h, 0, NULL, NULL); hash_append(NULL, 0, &he, NULL); hash_unlink(NULL); out: return err; } #define MAGIC1 0x7fbb0001 #define MAGIC2 0x7fbb0002 struct object { uint32_t magic1; struct le he; uint32_t magic2; char buffer[32]; uint32_t key; }; static void obj_destructor(void *arg) { struct object *obj = arg; list_unlink(&obj->he); } static bool cmp_handler(struct le *le, void *arg) { struct object *obj = le->data; return obj->key == *(uint32_t *)arg; } static int test_hash_large(void) { #define SZ 8 #define NUM_ENTRIES SZ*SZ struct hash *ht = NULL; unsigned i; int err = 0; err = hash_alloc(&ht, SZ); if (err) goto out; /* add a lot of objects to hash-table */ for (i=0; imagic1 = MAGIC1; obj->magic2 = MAGIC2; obj->key = key; /* ownership of 'obj' transferred to the hash-table */ hash_append(ht, key, &obj->he, obj); } /* verify that all objects can be found */ for (i=0; imagic1); TEST_EQUALS(MAGIC2, obj->magic2); TEST_EQUALS(i, obj->key); } out: hash_flush(ht); /* destroys all the objects */ mem_deref(ht); return err; } int test_hash(void) { int err; err = test_hash_basic(); if (err) return err; err = test_hash_robustapi(); if (err) return err; err = test_hash_large(); if (err) return err; return 0; } ================================================ FILE: test/hmac.c ================================================ /** * @file hmac.c HMAC Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "testhmac" #define DEBUG_LEVEL 4 #include #ifndef SHA256_DIGEST_LENGTH #define SHA256_DIGEST_LENGTH 32 #endif int test_hmac_sha1(void) { /* RFC 2202 */ const struct { const void *key; uint32_t key_len; const void *data; uint32_t data_len; char digest[40 + 1]; } testv[] = { {"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", 20, "Hi There", 8, "b617318655057264e28bc0b6fb378c8ef146be00"}, {"Jefe", 4, "what do ya want for nothing?", 28, "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"}, {"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", 20, "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd", 50, "125d7342b9ac11cd91a39af48aa17b4f63f175d3"}, {"01234567890123456789", 20, "dalskdmlkasndoiqwjeoi3hjoijqweolk6y52fsdfsfgh66h" "91928ha8shdoalijelwjeoriwjeorijwe98fj98j98j384jo" "dalskdmlkasndoiqwjeoi3hjoijqweolk6y52fsdfsfgh66h" "91928ha8shdoalijelwjeoriwjeorijwe98fj98j98j38s4f" "dalskdmlkasndoiqwjeoi3hjoijqweolk6y52fsdfsfghsda" "91928ha8shdoalijelwjeoriwjeorijwe98fj98j98j384jo" "dalskdmlkasndoiqwjeoi3hjoijqweolk6y52fsdfsfgh66h" "91928ha8shdoalijelwjeoriwjeorijwe98fj98jqwe98j38", 384, "4b00628735c763b3c0dc398deb4370e99f822490"} }; struct hmac *hmac = NULL; uint32_t i; int err = 0; for (i=0; ikey) / 2; uint8_t data[MAX_DATA_LEN]; size_t data_len = str_len(test->data) / 2; uint8_t digest[SHA256_DIGEST_LENGTH]; uint8_t md[SHA256_DIGEST_LENGTH]; TEST_ASSERT(key_len <= sizeof(key)); TEST_ASSERT(data_len <= sizeof(data)); err |= str_hex(key, key_len, test->key); err |= str_hex(data, data_len, test->data); err |= str_hex(digest, sizeof(digest), test->digest); TEST_ERR(err); err = hmac_create(&hmac, HMAC_HASH_SHA256, key, key_len); if (err) break; err = hmac_digest(hmac, md, sizeof(md), data, data_len); if (err) break; TEST_MEMCMP(digest, sizeof(digest), md, sizeof(md)); hmac = mem_deref(hmac); /* Test Stateless API */ uint8_t md2[SHA256_DIGEST_LENGTH]; hmac_sha256(key, key_len, data, data_len, md2, sizeof(md2)); TEST_MEMCMP(digest, sizeof(digest), md2, sizeof(md2)); } out: mem_deref(hmac); if (err == ENOTSUP) err = ESKIPPED; return err; } ================================================ FILE: test/http.c ================================================ /** * @file http.c HTTP Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_http" #define DEBUG_LEVEL 5 #include enum large_body_test { REQ_BODY_CHUNK_SIZE = 26 * 42, REQ_BODY_SIZE = REQ_BODY_CHUNK_SIZE * 480 - 26, REQ_HTTP_REQUESTS = 3 }; enum { IP_127_0_0_1 = 0x7f000001, }; static int test_http_response_no_reasonphrase(void) { struct http_msg *msg = NULL; struct mbuf *mb; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = mbuf_write_str(mb, /* _---- no space here! */ "HTTP/1.1 429\r\n" "Server: nginx\r\n" "Content-Length: 0\r\n" "\r\n"); if (err) goto out; mb->pos = 0; err = http_msg_decode(&msg, mb, false); if (err) goto out; TEST_STRCMP("1.1", 3, msg->ver.p, msg->ver.l); TEST_EQUALS(429, msg->scode); TEST_STRCMP("", 0, msg->reason.p, msg->reason.l); out: mem_deref(msg); mem_deref(mb); return err; } int test_http(void) { static const char req[] = "GET /path/file.html HTTP/1.1\r\n" "From: plopp@klukk.no\r\n" "User-Agent: libre HTTP client/1.0\r\n" "Allow: GET, HEAD, PUT\r\n" "\r\n" ""; struct mbuf *mb; struct http_msg *msg = NULL; const struct http_hdr *hdr; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = mbuf_write_str(mb, req); if (err) goto out; mb->pos = 0; err = http_msg_decode(&msg, mb, true); if (err) goto out; if (0 != pl_strcmp(&msg->met, "GET")) goto badmsg; if (0 != pl_strcmp(&msg->path, "/path/file.html")) goto badmsg; if (0 != pl_strcmp(&msg->ver, "1.1")) goto badmsg; if (pl_isset(&msg->prm)) goto badmsg; hdr = http_msg_hdr(msg, HTTP_HDR_FROM); if (!hdr || 0 != pl_strcmp(&hdr->val, "plopp@klukk.no")) goto badmsg; hdr = http_msg_hdr(msg, HTTP_HDR_USER_AGENT); if (!hdr || 0 != pl_strcmp(&hdr->val, "libre HTTP client/1.0")) goto badmsg; hdr = http_msg_hdr(msg, HTTP_HDR_CONTENT_TYPE); if (hdr) goto badmsg; if (msg->clen != 0) goto badmsg; if (!http_msg_hdr_has_value(msg, HTTP_HDR_ALLOW, "GET") || !http_msg_hdr_has_value(msg, HTTP_HDR_ALLOW, "HEAD") || !http_msg_hdr_has_value(msg, HTTP_HDR_ALLOW, "PUT")) goto badmsg; if (3 != http_msg_hdr_count(msg, HTTP_HDR_ALLOW)) goto badmsg; err = test_http_response_no_reasonphrase(); if (err) goto out; goto out; badmsg: (void)re_fprintf(stderr, "%H\n", http_msg_print, msg); err = EBADMSG; out: mem_deref(msg); mem_deref(mb); return err; } struct test { struct mbuf *mb_body; size_t clen; uint32_t n_request; uint32_t n_response; size_t i_req_body; bool secure; bool cert_auth; int err; }; static void abort_test(struct test *t, int err) { t->err = err; re_cancel(); } static enum re_https_verify_msg https_verify_msg_handler( struct http_conn *conn, const struct http_msg *msg, void *arg) { (void) conn; struct test *t = arg; int err = 0; enum re_https_verify_msg res = HTTPS_MSG_OK; if (!t->cert_auth) goto out; TEST_STRCMP("/auth/index.html", 11+5, msg->path.p, msg->path.l); /* cert authorisation required, request client certificate */ res = HTTPS_MSG_REQUEST_CERT; out: if (err) { res = HTTPS_MSG_IGNORE; abort_test(t, err); } return res; } static void http_req_handler(struct http_conn *conn, const struct http_msg *msg, void *arg) { struct test *t = arg; struct mbuf *mb_body = mbuf_alloc(1024); int err = 0; if (!mb_body) { err = ENOMEM; goto out; } ++t->n_request; if (t->secure) { TEST_ASSERT(http_conn_tls(conn) != NULL); } else { TEST_ASSERT(http_conn_tls(conn) == NULL); } /* verify HTTP request */ TEST_STRCMP("1.1", 3, msg->ver.p, msg->ver.l); TEST_STRCMP("GET", 3, msg->met.p, msg->met.l); if (t->cert_auth) { TEST_STRCMP("/auth/index.html", 11+5, msg->path.p, msg->path.l); } else { TEST_STRCMP("/index.html", 11, msg->path.p, msg->path.l); } TEST_STRCMP("", 0, msg->prm.p, msg->prm.l); TEST_EQUALS(t->clen, msg->clen); TEST_STRCMP("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", 52, mbuf_buf(msg->mb), mbuf_get_left(msg->mb)); /* Create a chunked response body */ err = mbuf_write_str(mb_body, "2\r\n" "ab\r\n" "4\r\n" "cdef\r\n" "8\r\n" "ghijklmn\r\n" "c\r\n" "opqrstuvwxyz\r\n" "0\r\n" "\r\n" ); if (err) goto out; t->clen = mb_body->end; err = http_reply(conn, 200, "OK", "Transfer-Encoding: chunked\r\n" "Content-Type: text/plain\r\n" "Content-Length: %zu\r\n" "\r\n" "%b", mb_body->end, mb_body->buf, mb_body->end ); out: mem_deref(mb_body); if (err) abort_test(t, err); } static void http_put_req_handler(struct http_conn *conn, const struct http_msg *msg, void *arg) { struct test *t = arg; struct mbuf *mb_body = mbuf_alloc(1024); int err = 0; size_t l = 0; size_t cmp_len; if (!mb_body) { err = ENOMEM; goto out; } ++t->n_request; if (t->secure) { TEST_ASSERT(http_conn_tls(conn) != NULL); } else { TEST_ASSERT(http_conn_tls(conn) == NULL); } /* verify HTTP request */ TEST_STRCMP("1.1", 3, msg->ver.p, msg->ver.l); TEST_STRCMP("PUT", 3, msg->met.p, msg->met.l); TEST_STRCMP("/index.html", 11, msg->path.p, msg->path.l); TEST_STRCMP("", 0, msg->prm.p, msg->prm.l); TEST_EQUALS(t->clen, msg->clen); l = mbuf_get_left(msg->mb); while (l > 0) { cmp_len = min(l, 26); TEST_STRCMP("abcdefghijklmnopqrstuvwxyz", cmp_len, mbuf_buf(msg->mb), cmp_len); mbuf_advance(msg->mb, cmp_len); l -= cmp_len; } /* Create a chunked response body */ err = mbuf_write_str(mb_body, "2\r\n" "ab\r\n" "4\r\n" "cdef\r\n" "8\r\n" "ghijklmn\r\n" "c\r\n" "opqrstuvwxyz\r\n" "0\r\n" "\r\n" ); if (err) goto out; t->clen = mb_body->end; err = http_reply(conn, 200, "OK", "Transfer-Encoding: chunked\r\n" "Content-Type: text/plain\r\n" "Content-Length: %zu\r\n" "\r\n" "%b", mb_body->end, mb_body->buf, mb_body->end ); out: mem_deref(mb_body); if (err) abort_test(t, err); } static void http_resp_handler(int err, const struct http_msg *msg, void *arg) { struct test *t = arg; bool chunked; if (err) { /* translate error code */ err = ENOMEM; goto out; } #if 0 re_printf("%H\n", http_msg_print, msg); re_printf("BODY: %b\n", msg->mb->buf, msg->mb->end); #endif ++t->n_response; /* verify HTTP response */ TEST_STRCMP("1.1", 3, msg->ver.p, msg->ver.l); TEST_ASSERT(!msg->met.p); TEST_ASSERT(!msg->path.p); TEST_ASSERT(!msg->prm.p); TEST_EQUALS(200, msg->scode); TEST_STRCMP("OK", 2, msg->reason.p, msg->reason.l); TEST_EQUALS(t->clen, msg->clen); chunked = http_msg_hdr_has_value(msg, HTTP_HDR_TRANSFER_ENCODING, "chunked"); TEST_ASSERT(chunked); TEST_STRCMP("text", 4, msg->ctyp.type.p, msg->ctyp.type.l); TEST_STRCMP("plain", 5, msg->ctyp.subtype.p, msg->ctyp.subtype.l); re_cancel(); out: if (err) abort_test(t, err); } static int http_data_handler(const uint8_t *buf, size_t size, const struct http_msg *msg, void *arg) { struct test *t = arg; (void)msg; if (!t->mb_body) t->mb_body = mbuf_alloc(256); if (!t->mb_body) return 0; return mbuf_write_mem(t->mb_body, buf, size); } static size_t http_req_body_handler(struct mbuf *mb, void *arg) { struct test *t = arg; if (t->i_req_body >= t->clen) return 0; if (mbuf_write_mem(mb, (const uint8_t*) "abcdefghijklmnopqrstuvwxyz", strlen("abcdefghijklmnopqrstuvwxyz"))) { mbuf_reset(mb); return 0; } t->i_req_body += strlen("abcdefghijklmnopqrstuvwxyz"); return strlen("abcdefghijklmnopqrstuvwxyz"); } static size_t http_req_long_body_handler(struct mbuf *mb, void *arg) { struct test *t = arg; size_t l = 0; size_t wlen; /* Create a chunked response body */ while ( l < REQ_BODY_CHUNK_SIZE && t->i_req_body < t->clen) { wlen = min(min(26, REQ_BODY_CHUNK_SIZE - l), t->clen - t->i_req_body); if (wlen <= 0) return l; if (mbuf_write_mem(mb, (const uint8_t*) "abcdefghijklmnopqrstuvwxyz", wlen)) { mbuf_reset(mb); return 0; } l += wlen; t->i_req_body += (uint32_t)wlen; } return l; } static int test_http_loop_base(bool secure, const char *met, bool http_conn, bool dns_srv_query, bool dns_set_conf_test, bool post_handshake) { struct http_sock *sock = NULL; struct http_cli *cli = NULL; struct http_req *req = NULL; struct http_reqconn *conn = NULL; struct dnsc *dnsc = NULL; struct dns_server *dns_srv = NULL; struct sa srv, dns; struct test t; char url[256]; char path[256]; int err = 0; unsigned int i; bool put = false; struct mbuf *mb_body = NULL; struct pl pl; struct dnsc_conf dconf; struct http_conf hconf = { 30000, 30000, 900000, }; if (!strcmp(met, "PUT")) put = true; memset(&t, 0, sizeof(t)); t.secure = secure; t.cert_auth = secure && post_handshake; if (dns_srv_query) { /* Setup Mocking DNS Server */ err = dns_server_alloc(&dns_srv, "127.0.0.1"); TEST_ERR(err); err = dns_server_add_a(dns_srv, "test1.example.net", IP_127_0_0_1, 1); TEST_ERR(err); } err |= sa_set_str(&srv, "127.0.0.1", 0); err |= sa_set_str(&dns, "127.0.0.1", 53); /* note: unused */ TEST_ERR(err); if (secure) { if (t.cert_auth) re_snprintf(path, sizeof(path), "%s/sni/server-interm.pem", test_datapath()); else re_snprintf(path, sizeof(path), "%s/server-ecdsa.pem", test_datapath()); err = https_listen(&sock, &srv, path, put ? http_put_req_handler : http_req_handler, &t); if (err) goto out; if (t.cert_auth) err = https_set_verify_msgh(sock, https_verify_msg_handler); } else { err = http_listen(&sock, &srv, put ? http_put_req_handler : http_req_handler, &t); } if (err) goto out; http_set_max_body_size(sock, 1024 * 1024 / 2); err = tcp_sock_local_get(http_sock_tcp(sock), &srv); if (err) goto out; err = dnsc_alloc(&dnsc, NULL, dns_srv ? &dns_srv->addr : &dns, 1); if (err) goto out; dconf.query_hash_size = 16; dconf.tcp_hash_size = 2; dconf.conn_timeout = hconf.conn_timeout; dconf.idle_timeout = hconf.idle_timeout; dconf.cache_ttl_max = 1800; dconf.getaddrinfo = dnsc_getaddrinfo_enabled(dnsc); if (dns_set_conf_test) { err = dnsc_conf_set(dnsc, &dconf); if (err) goto out; } err = http_client_alloc(&cli, dnsc); if (err) goto out; err = http_client_set_config(cli, &hconf); if (err) goto out; #ifdef USE_TLS if (secure) { struct tls* cli_tls; http_client_get_tls(cli, &cli_tls); if (t.cert_auth) { re_snprintf(path, sizeof(path), "%s/sni/client-interm.pem", test_datapath()); err |= http_client_set_cert(cli, path); err |= http_client_set_key(cli, path); if (err) goto out; tls_set_posthandshake_auth(cli_tls, 1); /* add CAs to http server */ re_snprintf(path, sizeof(path), "%s/sni/root-ca.pem", test_datapath()); err |= tls_add_ca(http_sock_tls(sock), path); re_snprintf(path, sizeof(path), "%s/sni/server-interm.pem", test_datapath()); err |= tls_add_ca(http_sock_tls(sock), path); if (err) goto out; } if (http_conn && !t.cert_auth) err = tls_set_session_reuse(cli_tls, true); if (err) goto out; } #endif if (put) http_client_set_bufsize_max(cli, REQ_BODY_CHUNK_SIZE + 128); #ifdef USE_TLS /* add root CA to http client */ if (secure) { if (t.cert_auth) re_snprintf(path, sizeof(path), "%s/sni/root-ca.pem", test_datapath()); else re_snprintf(path, sizeof(path), "%s/server-ecdsa.pem", test_datapath()); err = http_client_add_ca(cli, path); if (err) goto out; } if (t.cert_auth) { re_snprintf(path, sizeof(path), "%s/sni/client-interm.pem", test_datapath()); err |= http_client_set_cert(cli, path); err |= http_client_set_key(cli, path); if (err) goto out; } #endif (void)re_snprintf(url, sizeof(url), "http%s://%s:%u/%sindex.html", secure ? "s" : "", dns_srv_query ? "test1.example.net" : "127.0.0.1", sa_port(&srv), t.cert_auth ? "auth/" : ""); for (i = 1; i <= REQ_HTTP_REQUESTS; i++) { t.i_req_body = 0; t.clen = put ? REQ_BODY_SIZE : 2 * strlen("abcdefghijklmnopqrstuvwxyz"); if (http_conn) { err = http_reqconn_alloc(&conn, cli, http_resp_handler, http_data_handler, &t); if (err) goto out; if (put) { err = http_reqconn_set_req_bodyh(conn, put ? http_req_long_body_handler : http_req_body_handler, t.clen); if (err) goto out; pl_set_str(&pl, "PUT"); err = http_reqconn_set_method(conn, &pl); if (err) goto out; } else { mb_body = mbuf_alloc(t.clen); if (!mb_body) goto out; if (mbuf_write_str(mb_body, "abcdefghijklmnopqrstuvwxyz"\ "abcdefghijklmnopqrstuvwxyz")) goto out; err = http_reqconn_set_body(conn, mb_body); mb_body = mem_deref(mb_body); if (err) goto out; } pl_set_str(&pl, url); err = http_reqconn_send(conn, &pl); } else { err = http_request(&req, cli, met, url, http_resp_handler, http_data_handler, put ? http_req_long_body_handler : http_req_body_handler, &t, "Content-Length: %zu\r\n%s\r\n%s", t.clen, t.clen > REQ_BODY_CHUNK_SIZE ? "Expect: 100-continue\r\n" : "", "abcdefghijklmnopqrstuvwxyz"); } if (err) goto out; err = re_main_timeout(secure ? 1800 : 900); if (err) goto out; if (t.err) { err = t.err; goto out; } /* verify results after HTTP traffic */ TEST_EQUALS(i, t.n_request); TEST_EQUALS(i, t.n_response); if (t.mb_body) TEST_STRCMP("abcdefghijklmnopqrstuvwxyz", 26, t.mb_body->buf, t.mb_body->end); t.mb_body = mem_deref(t.mb_body); req = mem_deref(req); conn = mem_deref(conn); } out: mem_deref(t.mb_body); mem_deref(mb_body); mem_deref(req); mem_deref(conn); mem_deref(cli); mem_deref(dnsc); mem_deref(sock); mem_deref(dns_srv); return err; } #ifdef USE_TLS int test_http_client_set_tls(void) { struct sa dns; struct dnsc *dnsc = NULL; struct http_cli *cli = NULL; struct tls *tls = NULL, *tls_test = NULL, *tls_cli = NULL; int err; TEST_EINVAL(http_client_get_tls, NULL, NULL); TEST_EINVAL(http_client_set_tls, NULL, NULL); /* Setup */ err = sa_set_str(&dns, "127.0.0.1", 53); /* note: unused */ TEST_ERR(err); err = dnsc_alloc(&dnsc, NULL, &dns, 1); TEST_ERR(err); err = http_client_alloc(&cli, dnsc); TEST_ERR(err); /* Test original Http Client TLS Context */ TEST_EINVAL(http_client_get_tls, cli, NULL); err = http_client_get_tls(cli, &tls_cli); TEST_ERR(err); tls_cli = mem_ref(tls_cli); TEST_EQUALS(2, mem_nrefs(tls_cli)); /* Allocate new TLS Context */ err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); TEST_ERR(err); TEST_NOT_EQUALS(tls, tls_cli); /* Set and verify new TLS Context */ TEST_EINVAL(http_client_set_tls, cli, NULL); err = http_client_set_tls(cli, tls); TEST_ERR(err); TEST_EQUALS(2, mem_nrefs(tls)); TEST_EQUALS(1, mem_nrefs(tls_cli)); err = http_client_get_tls(cli, &tls_test); TEST_ERR(err); TEST_EQUALS(tls, tls_test); out: if (cli) { mem_deref(cli); mem_deref(tls_cli); } if (dnsc) mem_deref(dnsc); if (tls) { TEST_EQUALS(1, mem_nrefs(tls)); mem_deref(tls); } return err; } #endif int test_http_loop(void) { return test_http_loop_base(false, "GET", false, false, false, false); } #ifdef USE_TLS int test_https_loop(void) { return test_http_loop_base(true, "GET", false, false, false, false); } #endif int test_http_large_body(void) { return test_http_loop_base(false, "PUT", false, false, false, false); } #ifdef USE_TLS int test_https_large_body(void) { return test_http_loop_base(true, "PUT", false, false, false, false); } #endif int test_http_conn(void) { return test_http_loop_base(false, "GET", true, false, false, false); } int test_http_conn_large_body(void) { return test_http_loop_base(false, "PUT", true, false, false, false); } int test_dns_http_integration(void) { return test_http_loop_base(false, "GET", true, true, false, false); } int test_dns_cache_http_integration(void) { return test_http_loop_base(false, "GET", true, true, true, false); } #ifdef HAVE_TLS1_3_POST_HANDSHAKE_AUTH int test_https_conn_post_handshake(void) { return test_http_loop_base(true, "GET", true, false, false, true); } #endif static void http_req_addr_handler(struct http_conn *conn, const struct http_msg *msg, void *arg) { struct test *t = arg; (void)msg; ++t->n_request; http_reply(conn, 200, "OK", "Content-Length: 0\r\n\r\n"); } static void http_resp_addr_handler(int err, const struct http_msg *msg, void *arg) { struct test *t = arg; if (err) goto out; ++t->n_response; TEST_EQUALS(200, msg->scode); re_cancel(); out: if (err) abort_test(t, err); } static int test_http_request_addr_base(bool secure) { struct http_sock *sock = NULL; struct http_cli *cli = NULL; struct http_req *req = NULL; struct dnsc *dnsc = NULL; struct dns_server *dns_srv = NULL; struct sa srv; struct test t; char url[256]; int err = 0; #ifdef USE_TLS char path[256]; #endif memset(&t, 0, sizeof(t)); t.secure = secure; err = sa_set_str(&srv, "127.0.0.1", 0); TEST_ERR(err); /* Mock DNS server with no records */ err = dns_server_alloc(&dns_srv, "127.0.0.1"); TEST_ERR(err); #ifdef USE_TLS if (secure) { re_snprintf(path, sizeof(path), "%s/server-ecdsa.pem", test_datapath()); err = https_listen(&sock, &srv, path, http_req_addr_handler, &t); } else #endif { err = http_listen(&sock, &srv, http_req_addr_handler, &t); } if (err) goto out; err = tcp_sock_local_get(http_sock_tcp(sock), &srv); if (err) goto out; err = dnsc_alloc(&dnsc, NULL, &dns_srv->addr, 1); if (err) goto out; err = http_client_alloc(&cli, dnsc); if (err) goto out; #ifdef USE_TLS if (secure) { re_snprintf(path, sizeof(path), "%s/server-ecdsa.pem", test_datapath()); err = http_client_add_ca(cli, path); if (err) goto out; } #endif /* Port 1 in URL — without explicit addr the connect goes to port 1 * (nothing listens), proving http_request_addr bypasses ip and port * in the URL and uses the given ip and port */ re_snprintf(url, sizeof(url), "http%s://127.0.0.1:1/index.html", secure ? "s" : ""); err = http_request_addr(&req, cli, "GET", url, &srv, http_resp_addr_handler, NULL, NULL, &t, "\r\n"); if (err) goto out; err = re_main_timeout(secure ? 1800 : 900); if (err) goto out; if (t.err) { err = t.err; goto out; } TEST_EQUALS(1, t.n_request); TEST_EQUALS(1, t.n_response); out: mem_deref(t.mb_body); mem_deref(req); mem_deref(cli); mem_deref(dnsc); mem_deref(sock); mem_deref(dns_srv); return err; } int test_http_request_addr(void) { return test_http_request_addr_base(false); } #ifdef USE_TLS int test_https_request_addr(void) { return test_http_request_addr_base(true); } #endif ================================================ FILE: test/httpauth.c ================================================ /** * @file httpauth.c HTTP Authentication Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "httpauth" #define DEBUG_LEVEL 5 #include static int pl_equal(const char *name, const struct pl *a, const struct pl *b) { static const struct pl plnil = PL("nil"); int err; if ((a->p && !b->p) || (!a->p && b->p)) err = EINVAL; else err = pl_cmp(a, b); if (err) { DEBUG_WARNING("%s mismatch: '%r' vs '%r'\n", name, a->p ? a : &plnil, b->p ? b : &plnil); } return err; } static bool chall_equal(const struct httpauth_digest_chall *a, const struct httpauth_digest_chall *b) { int err = 0; err |= pl_equal("realm", &a->realm, &b->realm); err |= pl_equal("nonce", &a->nonce, &b->nonce); err |= pl_equal("opaque", &a->opaque, &b->opaque); err |= pl_equal("stale", &a->stale, &b->stale); err |= pl_equal("algorithm", &a->algorithm, &b->algorithm); err |= pl_equal("qop", &a->qop, &b->qop); err |= pl_equal("domain", &a->domain, &b->domain); err |= pl_equal("charset", &a->charset, &b->charset); err |= pl_equal("userhash", &a->userhash, &b->userhash); return err == 0; } static bool resp_equal(const struct httpauth_digest_resp *a, const struct httpauth_digest_resp *b) { int err = 0; err |= pl_equal("realm", &a->realm, &b->realm); err |= pl_equal("nonce", &a->nonce, &b->nonce); err |= pl_equal("response", &a->response, &b->response); err |= pl_equal("username", &a->username, &b->username); err |= pl_equal("uri", &a->uri, &b->uri); err |= pl_equal("nc", &a->nc, &b->nc); err |= pl_equal("cnonce", &a->cnonce, &b->cnonce); err |= pl_equal("qop", &a->qop, &b->qop); return err == 0; } int test_httpauth_chall(void) { static const struct { const char *hval; struct httpauth_digest_chall chall; int err; } testv[] = { { "Digest realm=\"realm\"," " nonce=\"4ee102da2fb730e04a26e8da913249b264f391c3\"," " opaque=\"123\", stale=\"true\"" " algorithm=\"MD5\"" , {PL("realm"), PL("4ee102da2fb730e04a26e8da913249b264f391c3"), PL("123"), PL("true"), PL("MD5"), PL_INIT, PL_INIT, PL_INIT, PL_INIT}, 0 }, { "Digest realm=\"creytiv.com\"," " nonce=\"9c916919cbc6ad7f54a4f64e5b5115074ee109fa\"" ", qop=\"auth\"", {PL("creytiv.com"), PL("9c916919cbc6ad7f54a4f64e5b5115074ee109fa"), PL_INIT, PL_INIT, PL_INIT, PL("auth"), PL_INIT, PL_INIT, PL_INIT }, 0 }, { "Basic bogus", {PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT}, EBADMSG }, }; size_t i; int err = 0; for (i=0; irealm, testv[i].realm) != 0) { DEBUG_WARNING("basic req: expected realm %s, got %s\n", testv[i].realm, req->realm); err = EBADMSG; mem_deref(req); break; } if (testv[i].charset) { if (str_casecmp(req->charset, testv[i].charset) != 0) { DEBUG_WARNING("basic req: expected charset" "%s, got %s\n", testv[i].charset, req->charset); err = EBADMSG; mem_deref(req); break; } } mb = mbuf_alloc(512); if (!mb) { err = ENOMEM; mem_deref(req); break; } err = mbuf_printf(mb, "%H", httpauth_basic_request_print, req); if (err) { mem_deref(mb); mem_deref(req); break; } if (memcmp(testv[i].hval, mb->buf, str_len(testv[i].hval)) != 0) { DEBUG_WARNING("basic req: expected hval %s, got %s\n", testv[i].hval, mb->buf); err = EBADMSG; mem_deref(mb); mem_deref(req); break; } mem_deref(mb); tauth_err = httpauth_basic_verify(&testv[i].hval_response, testv[i].user, testv[i].passwd); if (tauth_err != testv[i].auth_err) { DEBUG_WARNING("basic req:" "authentication expected %d, got %d\n", testv[i].auth_err, tauth_err); mem_deref(req); break; } mem_deref(req); } return err; } int test_httpauth_digest_request(void) { static const struct { const char *hval_fmt; const char *realm; const char *domain; const char *etag; const char *opaque; const bool stale; const char *algorithm; const char *qop; const char *charset; const bool userhash; int err; } testv [] = { { "", NULL, NULL, "", NULL, false, NULL, "auth", NULL, false, EINVAL }, { "Digest realm=\"/my/home\", qop=\"\"," " nonce=\"%s\", algorithm=MD5", "/my/home", NULL, "localhost:5060", NULL, false, NULL, "", NULL, false, 0 }, { "Digest realm=\"/my/home\", qop=\"\"," " nonce=\"%s\", algorithm=MD5", "/my/home", NULL, "localhost:5060", NULL, false, NULL, "", NULL, false, 0 }, { "Digest realm=\"/my/home\", qop=\"auth\"," " nonce=\"%s\", algorithm=SHA-256", "/my/home", NULL, "localhost:5060", NULL, false, "SHA-256", "auth", NULL, false, 0 }, { "Digest realm=\"/my/home\", qop=\"auth\"," " nonce=\"%s\", algorithm=SHA-256-sess, stale=true", "/my/home", NULL, "localhost:5060", NULL, true, "SHA-256-sess", "auth", NULL, false, 0 }, { "Digest realm=\"/my/home\", qop=\"auth\"," " nonce=\"%s\", algorithm=SHA-256," " domain=\"example.com\", stale=true," " charset=\"UTF-8\", userhash=true", "/my/home", "example.com", "localhost:5060", NULL, true, "SHA-256", "auth", "UTF-8", true, 0 }, { "Digest realm=\"/my/home\", qop=\"auth-int\"," " nonce=\"%s\", algorithm=MD5-sess," " domain=\"example.com\", stale=true," " charset=\"UTF-8\", userhash=true", "/my/home", "example.com", "localhost:5060", NULL, true, "MD5-sess", "auth-int", "UTF-8", true, 0 }, { "Digest realm=\"/my/home\", qop=\"auth-int\"," " nonce=\"%s\", algorithm=MD5," " domain=\"example.com\", stale=true," " charset=\"UTF-8\", userhash=true", "/my/home", "example.com", "129842", NULL, true, NULL, "auth-int", "UTF-8", true, 0 }, }; int err = 0; for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { struct httpauth_digest_chall_req *req = NULL; struct mbuf *mb_refval = NULL; struct mbuf *mb_printed = NULL; mb_refval = mbuf_alloc(512); mb_printed = mbuf_alloc(512); if (!mb_refval || !mb_printed) { err = ENOMEM; goto out; } err = httpauth_digest_chall_request_full(&req, testv[i].realm, testv[i].domain, testv[i].etag, testv[i].opaque, testv[i].stale, testv[i].algorithm, testv[i].qop, testv[i].charset, testv[i].userhash); if (err == ENOMEM) { goto out; } else if (err != testv[i].err) { DEBUG_WARNING("[%d]" " Expected return value %m, got %m\n", i, testv[i].err, err); } else if (err) { goto for_continue; } err = mbuf_printf(mb_refval, testv[i].hval_fmt, req->nonce); if (err) { DEBUG_WARNING("[%d]" " No reference created %m\n", i, err); goto out; } err = mbuf_printf(mb_printed, "%H", httpauth_digest_chall_req_print, req); if (err) { DEBUG_WARNING("[%d]" " Digest request print error %m\n", i, err); goto out; } if (mb_refval->end != mb_printed->end) { DEBUG_WARNING("[%d] Expected header len %d, got %d\n", i, mb_refval->end, mb_printed->end); err = EINVAL; goto out; } if (memcmp(mb_refval->buf, mb_printed->buf, mb_refval->end)) { DEBUG_WARNING("[%d] Expected header %b, got %b\n", i, mb_refval->buf, mb_refval->end, mb_printed->buf, mb_printed->end); err = EINVAL; goto out; } for_continue: mem_deref(req); mem_deref(mb_refval); mem_deref(mb_printed); continue; out: mem_deref(req); mem_deref(mb_refval); mem_deref(mb_printed); break; } return err; } int test_httpauth_digest_response(void) { static const struct { const struct httpauth_digest_chall chall; const char *user; const char *passwd; const char *qop; const struct pl method; const char *uri; const char *entitybody; const char *precalc_digest; const char *resp_hval; } testv [] = { { { PL("/my/home"), PL("b5c64f319d37323ac652b77012817ccaa" "6e9a7e4e7563155f1f9556414dd4615"), PL("324DF3428BCF42D29A"), PL_INIT, PL("MD5"), PL("auth"), PL_INIT, PL_INIT, PL_INIT }, "retest", "sec_pwd_retest", "auth", PL("GET"), "example.com/my/home/something", NULL, "88f41f7227700e07d0d65256714a5a1a", "Digest realm=\"/my/home\"," " nonce=\"b5c64f319d37323ac652b77012817ccaa6e" "9a7e4e7563155f1f9556414dd4615\"," " username=\"retest\"," " uri=\"example.com/my/home/something\"," " response=\"88f41f7227700e07d0d65256714a5a1a\"," " opaque=\"324DF3428BCF42D29A\", algorithm=MD5," " qop=auth, cnonce=\"deadbeef\", nc=\"00000001\"", }, { { PL("/my/home"), PL("b5c64f319d37323ac652b77012817ccaa" "6e9a7e4e7563155f1f9556414dd4615"), PL("324DF3428BCF42D29A"), PL_INIT, PL("SHA-256"), PL("auth"), PL_INIT, PL_INIT, PL_INIT }, "retest", "sec_pwd_retest", "auth", PL("GET"), "example.com/my/home/something", NULL, "c22b56ce81bbb59570f0fbbc0ba27210dbbfcb2b23fe" "a371d214722f319dc41c", "Digest realm=\"/my/home\"," " nonce=\"b5c64f319d37323ac652b77012817ccaa6e" "9a7e4e7563155f1f9556414dd4615\", username=\"retest\"," " uri=\"example.com/my/home/something\"," " response=\"c22b56ce81bbb59570f0fbbc0ba27210dbbfcb2b2" "3fea371d214722f319dc41c\"," " opaque=\"324DF3428BCF42D29A\", algorithm=SHA-256," " qop=auth, cnonce=\"deadbeef\", nc=\"00000001\"", }, { { PL("/my/home"), PL("b5c64f319d37323ac652b77012817ccaa" "6e9a7e4e7563155f1f9556414dd4615"), PL("324DF3428BCF42D29A"), PL_INIT, PL("MD5-sess"), PL("auth"), PL_INIT, PL_INIT, PL_INIT }, "retest", "sec_pwd_retest", "auth", PL("GET"), "example.com/my/home/something", NULL, "1e79ac7105a4fdf416aaacfc50349110", "Digest realm=\"/my/home\"," " nonce=\"b5c64f319d37323ac652b77012817ccaa6e9a7e4e756" "3155f1f9556414dd4615\", username=\"retest\"," " uri=\"example.com/my/home/something\"," " response=\"1e79ac7105a4fdf416aaacfc50349110\"," " opaque=\"324DF3428BCF42D29A\", algorithm=MD5-sess," " qop=auth, cnonce=\"deadbeef\", nc=\"00000001\"", }, { { PL("/my/home"), PL("b5c64f319d37323ac652b77012817ccaa" "6e9a7e4e7563155f1f9556414dd4615"), PL("324DF3428BCF42D29A"), PL_INIT, PL("SHA-256"), PL("auth-int"), PL_INIT, PL_INIT, PL_INIT }, "retest", "sec_pwd_retest", "auth-int", PL("GET"), "example.com/my/home/something", "", "2c0746b7174441314164d8d9a980d8920732de32e163" "03f0e6a82970230e79e4", "Digest realm=\"/my/home\"," " nonce=\"b5c64f319d37323ac652b77012817ccaa6e9a7e4e756" "3155f1f9556414dd4615\", username=\"retest\"," " uri=\"example.com/my/home/something\"," " response=\"2c0746b7174441314164d8d9a980d8920732de32e" "16303f0e6a82970230e79e4\"," " opaque=\"324DF3428BCF42D29A\", algorithm=SHA-256," " qop=auth-int, cnonce=\"deadbeef\", nc=\"00000001\"", }, }; int err = 0; for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { struct httpauth_digest_enc_resp *resp = NULL; struct mbuf *mb_printed = NULL; mb_printed = mbuf_alloc(512); if (!mb_printed) { err = ENOMEM; goto out; } err = httpauth_digest_response_full(&resp, &testv[i].chall, &testv[i].method, testv[i].uri, testv[i].user, testv[i].passwd, testv[i].qop, testv[i].entitybody, NULL, false); if (err == ENOMEM) { goto out; } else if (err) { DEBUG_WARNING("[%d]" " Could not generate response %m\n", i, err); goto out; } err = httpauth_digest_response_set_cnonce(resp, &testv[i].chall, &testv[i].method, testv[i].user, testv[i].passwd, testv[i].entitybody, 0xdeadbeef, 0x00000001); if (err) { DEBUG_WARNING("[%d]" " Response recalculation failed %m\n", i, err); goto out; } err = mbuf_printf(mb_printed, "%H", httpauth_digest_response_print, resp); if (err) goto out; if (str_casecmp(resp->response, testv[i].precalc_digest) != 0) { err = EINVAL; DEBUG_WARNING("[%d]" " Expected response %s, got %s\n", i, testv[i].precalc_digest, resp->response ? resp->response : "(nil)"); goto out; } if (memcmp(testv[i].resp_hval, mb_printed->buf, mb_printed->end)) { err = EINVAL; DEBUG_WARNING("[%d]" " Expected header %s, got %b\n", i, testv[i].resp_hval, mb_printed->buf, mb_printed->end); goto out; } mem_deref(mb_printed); mem_deref(resp); continue; out: mem_deref(mb_printed); mem_deref(resp); break; } return err; } int test_httpauth_digest_verification(void) { static const struct { const char *realm; const char *domain; const char *opaque; const bool stale; const char *algorithm; const char *qop; const char *charset; const bool userhash; const char *etag; const char *entitybody; const char *user; const char *passwd; const char *uri; const struct pl method; const char *huser; } testv [] = { /* qop=auth & normal algorithm */ { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "MD5", "auth", NULL, false, "localhost:5060", NULL, "retest", "sec_passwd", "example.com/my/home/something", PL("GET"), NULL, }, { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "SHA-256", "auth", NULL, false, "localhost:5060", NULL, "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, /* qop=auth & session algorithm */ { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "MD5-sess", "auth", NULL, false, "localhost:5060", NULL, "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "SHA-256-sess", "auth", NULL, false, "localhost:5060", NULL, "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, /* qop=auth-int & normal algorithm */ { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "MD5", "auth-int", NULL, false, "localhost:5060", NULL, "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "SHA-256", "auth-int", NULL, false, "localhost:5060", "Strange body with content", "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, /* qop=auth-int & session algorithm */ { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "MD5-sess", "auth-int", NULL, false, "localhost:5060", "NOT NULL", "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, { "/my/home", "example.com", "185803523d335c8fe52cf633391d47f7", false, "SHA-256-sess", "auth-int", NULL, false, "localhost:5060", "NULL as String :D", "retest", "sec_passed", "example.com/my/home/something", PL("GET"), NULL }, }; int err = 0; for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { struct httpauth_digest_chall_req *req = NULL; struct httpauth_digest_enc_resp *resp = NULL; struct httpauth_digest_chall chall; struct mbuf *mb_req = NULL; struct mbuf *mb_resp = NULL; struct pl plreq; struct pl plresp; mb_req = mbuf_alloc(512); mb_resp = mbuf_alloc(512); if (!mb_req || !mb_resp) { err = ENOMEM; DEBUG_WARNING("[%d]" " Could not allocate memory buffers \n", i); goto out; } err = httpauth_digest_chall_request_full(&req, testv[i].realm, testv[i].domain, testv[i].etag, testv[i].opaque, testv[i].stale, testv[i].algorithm, testv[i].qop, testv[i].charset, testv[i].userhash); if (err) { DEBUG_WARNING("[%d]" " Could not generate request (%m)\n", i, err); goto out; } err = mbuf_printf(mb_req, "%H", httpauth_digest_chall_req_print, req); if (err) { DEBUG_WARNING("[%d]" " Could not write digest request (%m)", i, err); goto out; } mbuf_set_pos(mb_req, 0); pl_set_mbuf(&plreq, mb_req); err = httpauth_digest_challenge_decode(&chall, &plreq); if (err) { DEBUG_WARNING("[%d] Could not" " decode \"received\" challenge (%m)", i, err); goto out; } err = httpauth_digest_response_full(&resp, &chall, &testv[i].method, testv[i].uri, testv[i].user, testv[i].passwd, testv[i].qop, testv[i].entitybody, testv[i].charset, testv[i].userhash); if (err) { DEBUG_WARNING("[%d]" " Could not generate response (%m)\n", i, err); goto out; } err = mbuf_printf(mb_resp, "%H", httpauth_digest_response_print, resp); if (err) { DEBUG_WARNING("[%d] Could not" " decode \"received\" response (%m)\n", i, err); goto out; } mbuf_set_pos(mb_resp, 0); pl_set_mbuf(&plresp, mb_resp); err = httpauth_digest_verify(req, &plresp, &testv[i].method, testv[i].etag, testv[i].user, testv[i].passwd, testv[i].entitybody); if (err) { DEBUG_WARNING("[%d]" " Verification failed (%m)\n", i, err); goto out; } mem_deref(req); mem_deref(resp); mem_deref(mb_req); mem_deref(mb_resp); continue; out: mem_deref(req); mem_deref(resp); mem_deref(mb_req); mem_deref(mb_resp); break; } return err; } ================================================ FILE: test/ice.c ================================================ /** * @file ice.c ICE Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_ice" #define DEBUG_LEVEL 5 #include /* * Protocol testcode for 2 ICE agents. We setup 2 ICE agents A and B, * with only a basic host candidate. Gathering is done using a small * STUN Server running on localhost, SDP is exchanged with Offer/Answer. * Finally the connectivity checks are run and the result is verified. * * * .-------------. * | STUN Server | * '-------------' * STUN / \ STUN * / \ * / \ * .-------------. .-------------. * | ICE-Agent A |---------------------| ICE-Agent B | * '-------------' Connectivity '-------------' * checks * */ struct attrs { struct attr { char name[16]; char value[128]; } attrv[16]; unsigned attrc; }; struct agent { struct icem *icem; struct udp_sock *us; struct sa laddr; struct attrs attr_s; struct attrs attr_m; struct ice_test *it; /* parent */ struct stunserver *stun; struct turnserver *turn; char name[16]; uint8_t compid; bool offerer; bool use_turn; size_t n_cand; char lufrag[8]; char lpwd[32]; /* results: */ bool gathering_ok; bool conncheck_ok; struct stun_ctrans *ct_gath; }; struct ice_test { struct agent *a; struct agent *b; struct tmr tmr; int err; }; static void icetest_check_gatherings(struct ice_test *it); static void icetest_check_connchecks(struct ice_test *it); /* * Test tools */ static void complete_test(struct ice_test *it, int err) { it->err = err; #if 0 re_printf("\n\x1b[32m%H\x1b[;m\n", icem_debug, it->a->icem); re_printf("\n\x1b[36m%H\x1b[;m\n", icem_debug, it->b->icem); #endif re_cancel(); } static bool find_debug_string(struct icem *icem, const char *str) { char buf[1024]; if (re_snprintf(buf, sizeof(buf), "%H", icem_debug, icem) < 0) return false; return 0 == re_regex(buf, strlen(buf), str); } static int attr_add(struct attrs *attrs, const char *name, const char *value, ...) { struct attr *attr = &attrs->attrv[attrs->attrc]; va_list ap; int r, err = 0; TEST_ASSERT(attrs->attrc <= RE_ARRAY_SIZE(attrs->attrv)); TEST_ASSERT(strlen(name) < sizeof(attr->name)); str_ncpy(attr->name, name, sizeof(attr->name)); if (value) { va_start(ap, value); r = re_vsnprintf(attr->value, sizeof(attr->value), value, ap); va_end(ap); TEST_ASSERT(r > 0); } attrs->attrc++; out: return err; } static const char *attr_find(const struct attrs *attrs, const char *name) { unsigned i; if (!attrs || !name) return NULL; for (i=0; iattrc; i++) { const struct attr *attr = &attrs->attrv[i]; if (0 == str_casecmp(attr->name, name)) return attr->value; } return NULL; } /* * ICE Agent */ static void agent_destructor(void *arg) { struct agent *agent = arg; mem_deref(agent->icem); mem_deref(agent->us); mem_deref(agent->stun); mem_deref(agent->turn); } static struct agent *agent_other(struct agent *agent) { if (agent->it->a == agent) return agent->it->b; else return agent->it->a; } static int agent_encode_sdp(struct agent *ag) { struct le *le; int err = 0; for (le = icem_lcandl(ag->icem)->head; le; le = le->next) { struct cand *cand = le->data; err = attr_add(&ag->attr_m, "candidate", "%H", ice_cand_encode, cand); if (err) break; } err |= attr_add(&ag->attr_m, "ice-ufrag", ag->lufrag); err |= attr_add(&ag->attr_m, "ice-pwd", ag->lpwd); return err; } static int agent_verify_outgoing_sdp(const struct agent *agent) { const char *cand, *ufrag, *pwd; char buf[1024]; int err = 0; if (re_snprintf(buf, sizeof(buf), "7f000001 %u UDP 2113929465 127.0.0.1 %u typ host", agent->compid, sa_port(&agent->laddr)) < 0) { return ENOMEM; } cand = attr_find(&agent->attr_m, "candidate"); TEST_STRCMP(buf, str_len(buf), cand, str_len(cand)); ufrag = attr_find(&agent->attr_m, "ice-ufrag"); pwd = attr_find(&agent->attr_m, "ice-pwd"); TEST_STRCMP(agent->lufrag, str_len(agent->lufrag), ufrag, str_len(ufrag)); TEST_STRCMP(agent->lpwd, str_len(agent->lpwd), pwd, str_len(pwd)); TEST_ASSERT(NULL == attr_find(&agent->attr_s, "ice-lite")); out: return err; } static int agent_decode_sdp(struct agent *agent, struct agent *other) { unsigned i; int err = 0; for (i=0; iattr_s.attrc; i++) { struct attr *attr = &other->attr_s.attrv[i]; err = ice_sdp_decode(agent->icem, attr->name, attr->value); if (err) return err; } for (i=0; iattr_m.attrc; i++) { struct attr *attr = &other->attr_m.attrv[i]; err = icem_sdp_decode(agent->icem, attr->name, attr->value); if (err) return err; } return err; } static int send_sdp(struct agent *agent) { int err; /* verify ICE states */ TEST_ASSERT(!icem_mismatch(agent->icem)); /* after gathering is complete we expect: * 1 local candidate * 0 remote candidates * checklist and validlist is empty */ TEST_EQUALS(agent->n_cand, list_count(icem_lcandl(agent->icem))); TEST_EQUALS(0, list_count(icem_rcandl(agent->icem))); TEST_EQUALS(0, list_count(icem_checkl(agent->icem))); TEST_EQUALS(0, list_count(icem_validl(agent->icem))); if (agent->use_turn) { /* verify that default candidate is the relayed address */ TEST_SACMP(&agent->turn->relay, icem_cand_default(agent->icem, agent->compid), SA_ALL); } else { /* verify that default candidate is our local address */ TEST_SACMP(&agent->laddr, icem_cand_default(agent->icem, agent->compid), SA_ALL); } /* we should not have selected candidate-pairs yet */ TEST_ASSERT(!icem_selected_laddr(agent->icem, agent->compid)); err = agent_encode_sdp(agent); TEST_ERR(err); err = agent_verify_outgoing_sdp(agent); TEST_ERR(err); out: return err; } static void agent_gather_handler(int err, uint16_t scode, const char *reason, void *arg) { struct agent *agent = arg; if (err) goto out; if (scode) { DEBUG_WARNING("gathering failed: %u %s\n", scode, reason); complete_test(agent->it, EPROTO); return; } /* Eliminate redundant local candidates */ icem_cand_redund_elim(agent->icem); err = icem_comps_set_default_cand(agent->icem); if (err) { DEBUG_WARNING("ice: set default cands failed (%m)\n", err); goto out; } agent->gathering_ok = true; err = send_sdp(agent); if (err) goto out; icetest_check_gatherings(agent->it); return; out: complete_test(agent->it, err); } static void stun_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct agent *ag = arg; struct stun_attr *attr; struct ice_cand *lcand; if (err || scode > 0) { DEBUG_WARNING("STUN Request failed: %m\n", err); goto out; } /* base candidate */ lcand = icem_cand_find(icem_lcandl(ag->icem), ag->compid, NULL); if (!lcand) goto out; attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr) attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR); if (!attr) { DEBUG_WARNING("no Mapped Address in Response\n"); err = EPROTO; goto out; } err = icem_lcand_add(ag->icem, icem_lcand_base(lcand), ICE_CAND_TYPE_SRFLX, &attr->v.sa); out: agent_gather_handler(err, scode, reason, ag); } static int icem_gather_srflx(struct agent *ag, const struct sa *srv) { int err; err = stun_request(&ag->ct_gath, icem_stun(ag->icem), IPPROTO_UDP, ag->us, srv, 0, STUN_METHOD_BINDING, NULL, false, 0, stun_resp_handler, ag, 1, STUN_ATTR_SOFTWARE, stun_software); if (err) return err; return 0; } static void agent_connchk_handler(int err, bool update, void *arg) { struct agent *agent = arg; struct agent *other = agent_other(agent); const struct sa *laddr, *raddr; if (err) { if (err != ENOMEM) { DEBUG_WARNING("%s: connectivity checks failed: %m\n", agent->name, err); } complete_test(agent->it, err); return; } if (agent->offerer ^ update) { DEBUG_WARNING("error in update flag\n"); complete_test(agent->it, EPROTO); return; } agent->conncheck_ok = true; /* verify ICE states */ TEST_ASSERT(!icem_mismatch(agent->icem)); /* after connectivity checks are complete we expect: * 1 local candidate * 1 remote candidates */ TEST_EQUALS(agent->n_cand, list_count(icem_lcandl(agent->icem))); TEST_EQUALS(other->n_cand, list_count(icem_rcandl(agent->icem))); TEST_EQUALS(0, list_count(icem_checkl(agent->icem))); TEST_EQUALS(agent->n_cand * other->n_cand, list_count(icem_validl(agent->icem))); laddr = icem_selected_laddr(agent->icem, agent->compid); raddr = &agent_other(agent)->laddr; if (!sa_cmp(&agent->laddr, laddr, SA_ALL)) { DEBUG_WARNING("unexpected selected address: %J\n", laddr); complete_test(agent->it, EPROTO); return; } if (!icem_verify_support(agent->icem, agent->compid, raddr)) { complete_test(agent->it, EPROTO); return; } #if 0 (void)re_printf("Agent %s -- Selected address: local=%J remote=%J\n", agent->name, laddr, raddr); #endif icetest_check_connchecks(agent->it); out: if (err) complete_test(agent->it, err); } static int agent_alloc(struct agent **agentp, struct ice_test *it, bool use_turn, const char *name, uint8_t compid, bool offerer) { struct agent *agent; enum ice_role lrole; uint64_t tiebrk; int err; agent = mem_zalloc(sizeof(*agent), agent_destructor); if (!agent) return ENOMEM; re_snprintf(agent->lufrag, sizeof(agent->lufrag), "ufrag-%s", name); re_snprintf(agent->lpwd, sizeof(agent->lpwd), "password-0123456789abcdef-%s", name); agent->use_turn = use_turn; agent->it = it; str_ncpy(agent->name, name, sizeof(agent->name)); agent->compid = compid; agent->offerer = offerer; if (agent->use_turn) { err = turnserver_alloc(&agent->turn, "127.0.0.1"); if (err) goto out; } else { err = stunserver_alloc(&agent->stun, "127.0.0.1"); if (err) goto out; } err = sa_set_str(&agent->laddr, "127.0.0.1", 0); if (err) goto out; err = udp_listen(&agent->us, &agent->laddr, 0, 0); if (err) goto out; err = udp_local_get(agent->us, &agent->laddr); if (err) goto out; lrole = offerer ? ICE_ROLE_CONTROLLING : ICE_ROLE_CONTROLLED; tiebrk = offerer ? 2 : 1; err = icem_alloc(&agent->icem, lrole, IPPROTO_UDP, 0, tiebrk, agent->lufrag, agent->lpwd, agent_connchk_handler, agent); if (err) goto out; #if 0 icem_conf(agent->icem)->debug = true; #endif if (offerer) { TEST_EQUALS(ICE_ROLE_CONTROLLING, icem_local_role(agent->icem)); } else { TEST_EQUALS(ICE_ROLE_CONTROLLED, icem_local_role(agent->icem)); } icem_set_name(agent->icem, name); err = icem_comp_add(agent->icem, compid, agent->us); if (err) goto out; err = icem_lcand_add_base(agent->icem, ICE_CAND_TYPE_HOST, compid, 0, "eth0", ICE_TRANSP_UDP, &agent->laddr); TEST_ERR(err); ++agent->n_cand; /* Start gathering now -- full mode only * * A lite implementation doesn't gather candidates; * it includes only host candidates for any media stream. */ if (agent->use_turn) { re_printf("turn disabled\n"); } else { err = icem_gather_srflx(agent, &agent->stun->laddr); } if (err) goto out; out: if (err) mem_deref(agent); else *agentp = agent; return err; } /* * ICE Test */ static int verify_after_sdp_exchange(struct agent *agent) { struct agent *other = agent_other(agent); int err = 0; /* verify remote mode (after SDP exchange) */ TEST_ASSERT(find_debug_string(agent->icem, "remote_mode=Full")); /* verify ICE states */ TEST_ASSERT(!icem_mismatch(agent->icem)); /* after SDP was exchanged, we expect: * 1 local candidate * 1 remote candidates * checklist and validlist is empty */ TEST_EQUALS(agent->n_cand, list_count(icem_lcandl(agent->icem))); TEST_EQUALS(other->n_cand, list_count(icem_rcandl(agent->icem))); TEST_EQUALS(0, list_count(icem_checkl(agent->icem))); TEST_EQUALS(0, list_count(icem_validl(agent->icem))); if (agent->use_turn) { /* verify that default candidate is the relayed address */ TEST_SACMP(&agent->turn->relay, icem_cand_default(agent->icem, agent->compid), SA_ALL); } else { /* verify that default candidate is our local address */ TEST_SACMP(&agent->laddr, icem_cand_default(agent->icem, agent->compid), SA_ALL); } /* we should not have selected candidate-pairs yet */ TEST_ASSERT(!icem_selected_laddr(agent->icem, agent->compid)); out: if (err) { DEBUG_WARNING("agent %s failed\n", agent->name); } return err; } static int agent_start(struct agent *agent) { struct agent *other = agent_other(agent); int err = 0; /* verify that check-list is empty before we start */ TEST_EQUALS(0, list_count(icem_checkl(agent->icem))); TEST_EQUALS(0, list_count(icem_validl(agent->icem))); err = icem_conncheck_start(agent->icem); if (err) return err; TEST_EQUALS(agent->n_cand * other->n_cand, list_count(icem_checkl(agent->icem))); TEST_EQUALS(0, list_count(icem_validl(agent->icem))); out: return err; } static int agent_verify_completed(struct agent *agent) { struct agent *other = agent_other(agent); uint32_t validc; int err = 0; TEST_ASSERT(agent->gathering_ok); TEST_ASSERT(agent->conncheck_ok); TEST_EQUALS(0, list_count(icem_checkl(agent->icem))); validc = list_count(icem_validl(agent->icem)); TEST_EQUALS(agent->n_cand * other->n_cand, validc); /* verify state of STUN/TURN server */ if (agent->use_turn) { TEST_ASSERT(agent->turn->n_allocate >= 1); TEST_ASSERT(agent->turn->n_chanbind >= 1); } else { TEST_ASSERT(agent->stun->nrecv >= 1); } out: return err; } static void icetest_check_gatherings(struct ice_test *it) { int err; if (!it->a->gathering_ok) return; if (!it->b->gathering_ok) return; /* both gatherings are complete * exchange SDP and start conncheck */ err = agent_decode_sdp(it->a, it->b); if (err) goto out; err = agent_decode_sdp(it->b, it->a); if (err) goto out; err = verify_after_sdp_exchange(it->a); if (err) goto error; err = verify_after_sdp_exchange(it->b); if (err) goto error; err = agent_start(it->a); if (err) goto out; err = agent_start(it->b); if (err) goto out; return; out: error: complete_test(it, err); } static void tmr_handler(void *arg) { struct ice_test *it = arg; #if 0 re_printf("\n\x1b[32m%H\x1b[;m\n", icem_debug, it->a->icem); re_printf("\n\x1b[36m%H\x1b[;m\n", icem_debug, it->b->icem); #endif complete_test(it, 0); } static void icetest_check_connchecks(struct ice_test *it) { if (!it->a->conncheck_ok) return; if (!it->b->conncheck_ok) return; /* start an async timer to let the socket traffic complete */ tmr_start(&it->tmr, 1, tmr_handler, it); } static void icetest_destructor(void *arg) { struct ice_test *it = arg; tmr_cancel(&it->tmr); mem_deref(it->b); mem_deref(it->a); } static int icetest_alloc(struct ice_test **itp, bool turn_a, bool turn_b) { struct ice_test *it; int err; it = mem_zalloc(sizeof(*it), icetest_destructor); if (!it) return ENOMEM; err = agent_alloc(&it->a, it, turn_a, "A", 7, true); if (err) goto out; err = agent_alloc(&it->b, it, turn_b, "B", 7, false); if (err) goto out; out: if (err) mem_deref(it); else *itp = it; return err; } static int _test_ice_loop(bool turn_a, bool turn_b) { struct ice_test *it = NULL; int err; err = icetest_alloc(&it, turn_a, turn_b); if (err) goto out; err = re_main_timeout(300); if (err) goto out; /* read back global errorcode */ if (it->err) { err = it->err; goto out; } /* now verify all results after test was finished */ err = agent_verify_completed(it->a); err |= agent_verify_completed(it->b); if (err) goto out; out: mem_deref(it); return err; } /* also verify that these symbols are exported */ static int test_ice_basic_candidate(void) { static const enum ice_cand_type typev[4] = { ICE_CAND_TYPE_HOST, ICE_CAND_TYPE_SRFLX, ICE_CAND_TYPE_PRFLX, ICE_CAND_TYPE_RELAY }; unsigned i; int err = 0; for (i=0; i 0); TEST_ASSERT(sa_isset(&cand.addr, SA_ALL)); n = re_snprintf(buf, sizeof(buf), "%H", ice_cand_attr_encode, &cand); if (n < 0) return ENOMEM; TEST_STRCMP(testv[i], strlen(testv[i]), buf, (unsigned)n); } out: return err; } int test_ice_cand(void) { int err = 0; err = test_ice_basic_candidate(); TEST_ERR(err); err = test_ice_cand_prio(); TEST_ERR(err); err = test_ice_cand_attribute(); TEST_ERR(err); out: return err; } int test_ice_loop(void) { return _test_ice_loop(false, false); } ================================================ FILE: test/json.c ================================================ /** * @file json.c Testcode for JSON parser * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "json" #define DEBUG_LEVEL 5 #include enum { DICT_BSIZE = 32, MAX_LEVELS = 8, }; static int test_json_basic_parser(void) { static const char *str = "{" " \"name\" : \"Herr Alfred\"," " \"height\" : 1.86," " \"weight\" : 90," " \"has_depth\" : false," " \"has_money\" : true," " \"array\" : [1, 2, 3, \"x\", \"y\"]," " \"negative\" : -42," " \"negativef\" : -0.0042," " \"expo_pos\" : 2.0E3," " \"expo_neg\" : 2.0E-3," " \"foo\x1d\" : \"foo\x1d\"," " \"object\" : {" " \"one\" : 1," " \"two\" : 2" " }" "}"; struct odict *dict = NULL, *sub; const struct odict_entry *o, *e; int err; err = json_decode_odict(&dict, DICT_BSIZE, str, strlen(str), MAX_LEVELS); if (err) goto out; TEST_EQUALS(12U, odict_count(dict, false)); TEST_EQUALS(17U, odict_count(dict, true)); o = odict_lookup(dict, "name"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_STRING, odict_entry_type(o)); TEST_STRCMP("Herr Alfred", 11, odict_entry_str(o), str_len(odict_entry_str(o))); o = odict_lookup(dict, "height"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_DOUBLE, odict_entry_type(o)); TEST_ASSERT(odict_entry_dbl(o) > .0); o = odict_lookup(dict, "weight"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_INT, odict_entry_type(o)); TEST_EQUALS(90, odict_entry_int(o)); o = odict_lookup(dict, "has_depth"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_BOOL, odict_entry_type(o)); TEST_ASSERT(!odict_entry_boolean(o)); o = odict_lookup(dict, "has_money"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_BOOL, odict_entry_type(o)); TEST_ASSERT(odict_entry_boolean(o)); o = odict_lookup(dict, "array"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_ARRAY, odict_entry_type(o)); TEST_EQUALS(5U, odict_count(odict_entry_array(o), false)); e = odict_get_type(odict_entry_array(o), ODICT_INT, "0"); TEST_EQUALS(1, odict_entry_int(e)); e = odict_get_type(odict_entry_array(o), ODICT_INT, "1"); TEST_EQUALS(2, odict_entry_int(e)); e = odict_get_type(odict_entry_array(o), ODICT_INT, "2"); TEST_EQUALS(3, odict_entry_int(e)); o = odict_lookup(dict, "negative"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_INT, odict_entry_type(o)); TEST_EQUALS(-42, odict_entry_int(o)); o = odict_lookup(dict, "negativef"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_DOUBLE, odict_entry_type(o)); TEST_ASSERT(odict_entry_dbl(o) < .0); o = odict_lookup(dict, "expo_pos"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_DOUBLE, odict_entry_type(o)); TEST_ASSERT(odict_entry_dbl(o) > .0); o = odict_lookup(dict, "expo_neg"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_DOUBLE, odict_entry_type(o)); TEST_ASSERT(odict_entry_dbl(o) > .0); o = odict_lookup(dict, "foo\x1d"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_STRING, odict_entry_type(o)); TEST_STRCMP("foo\x1d", 4, odict_entry_str(o), str_len(odict_entry_str(o))); /* object */ o = odict_lookup(dict, "object"); TEST_ASSERT(o != NULL); TEST_EQUALS(ODICT_OBJECT, odict_entry_type(o)); sub = odict_entry_object(o); e = odict_lookup(sub, "one"); TEST_ASSERT(e != NULL); TEST_EQUALS(ODICT_INT, odict_entry_type(e)); TEST_EQUALS(1, odict_entry_int(e)); e = odict_lookup(sub, "two"); TEST_ASSERT(e != NULL); TEST_EQUALS(ODICT_INT, odict_entry_type(e)); TEST_EQUALS(2, odict_entry_int(e)); /* non-existing entry */ o = odict_lookup(dict, "not-found"); TEST_ASSERT(o == NULL); out: mem_deref(dict); return err; } /* verify a bunch of JSON messages */ static int test_json_verify_decode(void) { static const struct test { unsigned num; unsigned num_total; char *str; } testv[] = { { 0, 0, "{}" }, { 1, 1, "\"yyyyyyyyyy\"" }, { 1, 1, "42" }, { 1, 1, "1.30142114406914976E17" }, { 1, 1, "true" }, { 1, 1, "{\"a\":1}" }, { 2, 2, "{\"a\":1,\"b\":2}" }, { 5, 5, "{" " \"aaaaa\" : \"yyyyyyyyyy\"," " \"bbbbb\" : \"yyyyyyyyyy\"," " \"ccccc\" : \"yyyyyyyyyy\"," " \"ddddd\" : \"yyyyyyyyyy\"," " \"eeeee\" : \"yyyyyyyyyy\"" "}" }, { 2, 2, "{\"num\":42,\"str\":\"hei du\"}" }, { 6, 6, "{" " \"zero\" : 0," " \"one\" : 1," " \"false\" : 0," " \"true\" : 1," " \"0\" : false," " \"1\" : true" "}" }, /* arrays */ { 2, 8, "{" " \"array\" : [1,2,3,4,5]," " \"arraz\" : [\"ole\", \"dole\", \"doffen\"]" "}" }, { 1, 0, "{" " \"empty_array\" : []" "}" }, { 1, 1, "{" " \"array_with_object\" : [ { \"key\" : 42 } ]" "}" }, { 1, 3, "{" " \"array_with_bool_and_null\" : [" " true, false, null" " ]" "}" }, { 1, 30, "{" " \"array\" : [" " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9," " 10,11,12,13,14,15,16,17,18,19," " 20,21,22,23,24,25,26,27,28,29" " ]" "}" }, /* nested arrays */ { 1, 4, "{" " \"array\": [" " 1," " 2," " [" " \"[][][][\"," " \"][][][\"" " ]" " ]" "}" }, /* null */ { 1, 1, "{" " \"empty\": null" "}" }, /* escaped string */ { 2, 2, "{" " \"string1\": \"\\\"\\/\\b\\f\\n\\r\\t\", " " \"string2\": \"\\\"/\\b\\f\\n\\r\\t\"" " }" }, { 2, 2, "{" " \"string\" : \"\\r\\n\" , " " \"boolean\" : true" "}" }, { 2, 2, "{" " \"string\" : \"a\\r\\n\" , " " \"null\" : null" "}" }, /* key with escaped string */ { 1, 1, "{ \"\\\"\\b\\f\\n\\r\\t\":\"value\"}" }, { 2, 3, "{" " \"type\": \"object\"," " \"properties\": {" " \"id\": {" " \"description\": \"The unique identifier\"," " \"type\": \"integer\"" " }" " }" "}" }, { 1, 2, "{" " \"a\": {" " \"b\": {" " \"c\": {" " \"d\": {" " \"e\": {" " \"f\": {" " \"string\": \"hei hei\"," " \"number\": 4242" " }" " }" " }" " }" " }" " }" "}" }, /* unicode */ { 1, 1, "{ \"\\u0001key\": \"val\\u0002\" }" }, /* numbers */ { 2, 2, "{ \"start\": 1372701600000, " " \"stop\": -1372701600000 }" }, { 4, 4, "{" " \"a\": 1.30142114406914976E17, " " \"b\": 1.7555215491128452E-19, " " \"c\": -4.57371918053102129E18, " " \"d\": -1.3014211440691497E-17 " "}" }, /* array with objects (legal JSON) */ { 2, 4, "[" " {" " \"foo\" : 111," " \"bar\" : 111" " }," " {" " \"foo\" : 222," " \"bar\" : 222" " }" "]" } }; struct odict *dict = NULL, *dict2 = NULL; struct mbuf *mb_enc = NULL; unsigned i; int err = 0; for (i=0; istr, str_len(t->str), MAX_LEVELS); if (err) goto out; TEST_EQUALS(t->num, odict_count(dict, false)); TEST_EQUALS(t->num_total, odict_count(dict, true)); mb_enc = mbuf_alloc(1024); if (!mb_enc) { err = ENOMEM; goto out; } /* verify that the JSON object can be encoded */ err = mbuf_printf(mb_enc, "%H", json_encode_odict, dict); TEST_ERR(err); /* decode it again */ err = json_decode_odict(&dict2, DICT_BSIZE, (void *)mb_enc->buf, mb_enc->end, MAX_LEVELS); if (err) { goto out; } TEST_ASSERT(odict_compare(dict, dict2, false)); dict = mem_deref(dict); dict2 = mem_deref(dict2); mb_enc = mem_deref(mb_enc); } out: mem_deref(dict2); mem_deref(dict); mem_deref(mb_enc); return err; } static int test_json_exponent(void) { static const char *str = "{" " \"exponents\" : [1e2, 1e-2, 9E18, -9E18]" "}"; struct odict *dict = NULL; const struct odict_entry *arr, *e; static const double values[] = { 100.0, 0.01, 9000000000000000000.0, -9000000000000000000.0, }; struct le *le; unsigned i; int err; err = json_decode_odict(&dict, DICT_BSIZE, str, strlen(str), MAX_LEVELS); if (err) goto out; arr = odict_lookup(dict, "exponents"); TEST_EQUALS(RE_ARRAY_SIZE(values), odict_count(odict_entry_array(arr), false)); for (le = list_head(&odict_entry_array(arr)->lst), i = 0; le; le = le->next, ++i) { e = le->data; TEST_ASSERT(e != NULL); TEST_EQUALS(ODICT_DOUBLE, odict_entry_type(e)); TEST_EQUALS( values[i], odict_entry_dbl(e)); } out: mem_deref(dict); return err; } int test_json(void) { int err = 0; err = test_json_exponent(); TEST_ERR(err); err = test_json_basic_parser(); TEST_ERR(err); err = test_json_verify_decode(); TEST_ERR(err); out: return err; } /* check a bunch of bad JSON messages, unparsable */ int test_json_bad(void) { static const struct test { int err; char *str; } testv[] = { { EBADMSG, "}" }, { EBADMSG, "{]" }, { EBADMSG, "{[}" }, { EBADMSG, "]" }, /* boolean values */ { EBADMSG, "{ \"short_true\" : t }" }, { EBADMSG, "{ \"short_false\" : f }" }, { EBADMSG, "{ \"short_null\" : n }" }, { EBADMSG, "{ \"a\" : frue }" }, { EBADMSG, "{ \"a\" : talse }" }, /* string values */ { EBADMSG, "{ \"invalid_unicode\" : \"\\u000g\" }" }, /* corrupt data */ { EBADMSG, "10t[3e9e66\"49\"[[72677:[f58{.fn}0{59\":8\"e}[" }, { EBADMSG, "1t34:{{:f{1.n{\"\"n8[0f7e}:53e6{7:28:{n{00:7" }, { EBADMSG, "}3][ne5}.5n41ef96f99\":n47{9[n[1:0f5\"}985}{" }, { EBADMSG, "}3][ne5}.5n41ef96f99\":n47{9[n[1:0f5\"}985}{" }, { EBADMSG, "8n0}3:28e27}8]75:[:e47968e96n[:2f]n1:]n2[t" }, { EBADMSG, "{" }, { EBADMSG, "[" }, { EBADMSG, "{ \"broken_key }" }, { EBADMSG, "{ \"key\" : \"broken_value }" }, { 0, "\"hei\"" }, { 0, "123" }, }; struct odict *dict = NULL; unsigned i; int err = 0; for (i=0; istr, str_len(t->str), MAX_LEVELS); if (e == ENOMEM) break; TEST_EQUALS(t->err, e); if (e) { TEST_ASSERT(dict == NULL); } else { TEST_ASSERT(dict != NULL); } dict = mem_deref(dict); } out: mem_deref(dict); return err; } static int test_json_file_parse(const char *filename) { struct mbuf *mb_ref = NULL, *mb_enc = NULL; struct odict *dict = NULL, *dict2 = NULL; char path[256]; unsigned max_levels = 480; int err; mb_ref = mbuf_alloc(1024); mb_enc = mbuf_alloc(1024); if (!mb_ref || !mb_enc) { err = ENOMEM; goto out; } re_snprintf(path, sizeof(path), "%s/%s", test_datapath(), filename); err = test_load_file(mb_ref, path); if (err) goto out; err = json_decode_odict(&dict, DICT_BSIZE, (void *)mb_ref->buf, mb_ref->end, max_levels); if (err) { goto out; } TEST_ASSERT(dict != NULL); TEST_ASSERT(odict_count(dict, true) > 0); #if 0 re_printf("%s: JSON parsed OK (%zu elements)\n", filename, odict_count(dict, true)); #endif /* verify that JSON object can be encoded */ err = mbuf_printf(mb_enc, "%H", json_encode_odict, dict); TEST_ERR(err); /* decode it again */ err = json_decode_odict(&dict2, DICT_BSIZE, (void *)mb_enc->buf, mb_enc->end, max_levels); if (err) { goto out; } TEST_ASSERT(odict_compare(dict, dict2, false)); out: mem_deref(dict2); mem_deref(dict); mem_deref(mb_enc); mem_deref(mb_ref); return err; } int test_json_file(void) { const char *files[] = { "fstab.json", "menu.json", "rfc7159.json", "webapp.json", "widget.json", }; unsigned i; int err = 0; for (i=0; ilst.head, i=0; le; le = le->next, ++i) { struct odict_entry *ae = le->data; unsigned key; key = atoi(odict_entry_key(ae)); TEST_EQUALS(i, key); } /* should not exist */ re_snprintf(buf, sizeof(buf), "%u", num); e = odict_lookup(arr, buf); TEST_ASSERT(e == NULL); out: return err; } int test_json_array(void) { static const char *str = "{" " \"array1\" : [0,1,2,3,4,5,6,7]," " \"array2\" : [\"ole\",\"dole\",\"doffen\"]," " \"array3\" : [ {\"x\":0}, {\"x\":0}, {\"x\":0} ]," " \"object\" : {" " \"array4\" : [0,1,2,3]" " }" "}"; struct odict *dict = NULL, *obj; int err; err = json_decode_odict(&dict, DICT_BSIZE, str, strlen(str), MAX_LEVELS); if (err) goto out; err |= verify_array(odict_get_array(dict, "array1"), 8); err |= verify_array(odict_get_array(dict, "array2"), 3); err |= verify_array(odict_get_array(dict, "array3"), 3); if (err) goto out; obj = odict_get_object(dict, "object"); TEST_ASSERT(obj != NULL); err |= verify_array(odict_get_array(obj, "array4"), 4); if (err) goto out; out: mem_deref(dict); return err; } ================================================ FILE: test/list.c ================================================ /** * @file list.c Linked-lists Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "testlist" #define DEBUG_LEVEL 5 #include struct node { struct le le; struct le le2; int value; }; int test_list(void) { struct node node1 = {.value = 1}, node2 = {.value = 2}; struct list list; int err = EINVAL; list_init(&list); /* Test empty list */ TEST_EQUALS(0, list_count(&list)); /* Test with one node */ list_append(&list, &node1.le, &node1); TEST_EQUALS(1, list_count(&list)); list_unlink(&node1.le); TEST_EQUALS(0, list_count(&list)); /* Test with two nodes */ list_append(&list, &node1.le, &node1); list_append(&list, &node2.le, &node2); TEST_EQUALS(2, list_count(&list)); list_unlink(&node1.le); TEST_EQUALS(1, list_count(&list)); list_unlink(&node2.le); /* Test empty list */ TEST_EQUALS(0, list_count(&list)); list_append(&list, &node1.le, &node1); list_append(&list, &node2.le, &node2); struct le *le; int i = 0; LIST_FOREACH(&list, le) { struct node *n = list_ledata(le); ++i; TEST_EQUALS(i, n->value); } struct le *tmp; i = 0; LIST_FOREACH_SAFE(&list, le, tmp) { struct node *n = list_ledata(le); ++i; TEST_EQUALS(i, n->value); list_unlink(le); } /* Test empty list */ TEST_EQUALS(0, list_count(&list)); err = 0; out: return err; } static void node_destructor(void *arg) { struct node *node = arg; if (node->le.prev || node->le.next || node->le.list || node->le.data) { DEBUG_WARNING("le: prev=%p next=%p data=%p\n", node->le.prev, node->le.next, node->le.data); } list_unlink(&node->le); } /** * Test linked list with external reference to objects */ int test_list_ref(void) { struct list list; struct node *node, *node2; int err = 0; list_init(&list); node = mem_zalloc(sizeof(*node), node_destructor); node2 = mem_zalloc(sizeof(*node2), node_destructor); if (!node || !node2) { err = ENOMEM; goto out; } mem_ref(node); list_append(&list, &node->le, node); list_append(&list, &node2->le, node2); out: list_flush(&list); memset(&list, 0xa5, sizeof(list)); /* mark as deleted */ /* note: done after list_flush() */ mem_deref(node); return err; } static bool sort_handler1(struct le *le1, struct le *le2, void *arg) { struct node *node1 = le1->data; struct node *node2 = le2->data; (void)arg; /* NOTE: important to use less than OR equal to, otherwise the list_sort function may be stuck in a loop */ return node1->value <= node2->value; } static bool sort_handler2(struct le *le1, struct le *le2, void *arg) { struct node *node1 = le1->data; struct node *node2 = le2->data; (void)arg; /* NOTE: important to use greater than OR equal to, otherwise the list_sort function may be stuck in a loop */ return node1->value >= node2->value; } #define NUM_ELEMENTS 100 static int test_sort(bool sorted) { struct list lst_1; struct list lst_2; struct le *le; int prev_value = 0; bool prev_value_set = false; unsigned i; unsigned value_counter = 7; int err = 0; list_init(&lst_1); list_init(&lst_2); /* add many elements with a random value */ for (i=0; ivalue = -50 + (value_counter % 100); value_counter *= 3; if (sorted) { list_insert_sorted(&lst_1, sort_handler1, NULL, &node->le, node); list_insert_sorted(&lst_2, sort_handler2, NULL, &node->le2, node); } else { list_append(&lst_1, &node->le, node); list_append(&lst_2, &node->le2, node); } } /* sort the list in ascending order */ if (!sorted) { list_sort(&lst_1, sort_handler1, NULL); list_sort(&lst_2, sort_handler2, NULL); } /* verify that the list is sorted */ for (le = lst_1.head; le; le = le->next) { struct node *node = le->data; if (prev_value_set) { TEST_ASSERT(node->value >= prev_value); } prev_value = node->value; prev_value_set = true; } prev_value_set = false; for (le = lst_2.head; le; le = le->next) { struct node *node = le->data; if (prev_value_set) { TEST_ASSERT(node->value <= prev_value); } prev_value = node->value; prev_value_set = true; } out: list_flush(&lst_1); return err; } int test_list_sort(void) { int err; err = test_sort(false); TEST_ERR(err); err = test_sort(true); TEST_ERR(err); out: return err; } struct flush_data { struct le le; struct list *flushl; }; static void data_destroy(void *arg) { struct flush_data *data = arg; struct le *le; LIST_FOREACH(data->flushl, le) { assert(list_count(data->flushl)); } } int test_list_flush(void) { struct flush_data *data[2]; struct list flushl = LIST_INIT; int err = 0; data[0] = mem_zalloc(sizeof(struct flush_data), data_destroy); if (!data[0]) return ENOMEM; data[1] = mem_zalloc(sizeof(struct flush_data), data_destroy); if (!data[1]) { mem_deref(data[0]); return ENOMEM; } data[0]->flushl = &flushl; data[1]->flushl = &flushl; list_append(&flushl, &data[0]->le, data[0]); list_append(&flushl, &data[1]->le, data[1]); list_flush(&flushl); TEST_EQUALS(0, list_count(&flushl)); out: return err; } ================================================ FILE: test/main.c ================================================ /** * @file main.c Main regression testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include #ifdef HAVE_GETOPT #include #endif #include #include "test.h" #define DEBUG_MODULE "retest" #define DEBUG_LEVEL 5 #include #ifdef HAVE_SIGNAL static void signal_handler(int num) { re_fprintf(stderr, "forced exit by signal %d -- test aborted\n", num); exit(0); } #endif #ifdef HAVE_GETOPT static void usage(void) { (void)re_fprintf(stderr, "Usage: retest [options] \n"); (void)re_fprintf(stderr, "\ntest group options:\n"); (void)re_fprintf(stderr, "\t-r Run regular tests\n"); (void)re_fprintf(stderr, "\t-o Run OOM memory tests\n"); (void)re_fprintf(stderr, "\t-i Run integration tests\n"); (void)re_fprintf(stderr, "\t-p Run performance tests\n"); (void)re_fprintf(stderr, "\t-t Run tests in multi-threads\n"); (void)re_fprintf(stderr, "\t-a Run all tests (default)\n"); (void)re_fprintf(stderr, "\t-l List all testcases and exit\n"); (void)re_fprintf(stderr, "\ncommon options:\n"); (void)re_fprintf(stderr, "\t-d Path to data files\n"); (void)re_fprintf(stderr, "\t-h Help\n"); (void)re_fprintf(stderr, "\t-m Async polling method to use\n"); (void)re_fprintf(stderr, "\t-v Verbose output\n"); } #endif static void dbg_handler(int level, const char *p, size_t len, void *arg) { (void)level; (void)arg; printf("%.*s", (int)len, p); } int main(int argc, char *argv[]) { struct memstat mstat; bool do_reg = false; bool do_oom = false; bool do_int = false; bool do_perf = false; bool do_all = true; /* run all tests is default */ bool do_list = false; bool do_thread = false; enum dbg_flags flags; bool verbose = false; const char *name = NULL; enum poll_method method = poll_method_best(); int err = 0; #ifdef HAVE_SIGNAL signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); #endif (void)sys_coredump_set(true); #ifdef HAVE_GETOPT for (;;) { const int c = getopt(argc, argv, "hroipaltvm:d:"); if (0 > c) break; switch (c) { case '?': case 'h': usage(); return -2; case 'r': do_reg = true; do_all = false; break; case 'o': do_oom = true; do_all = false; break; case 'i': do_int = true; do_all = false; break; case 'p': do_perf = true; do_all = false; break; case 'a': do_all = true; break; case 'l': do_list = true; do_all = false; break; case 't': do_thread = true; do_all = false; break; case 'v': verbose = true; break; case 'm': { struct pl pollname; pl_set_str(&pollname, optarg); err = poll_method_type(&method, &pollname); if (err) { re_fprintf(stderr, "could not resolve async polling" " method '%r'\n", &pollname); return err; } } break; case 'd': test_set_datapath(optarg); break; } } argc -= optind; if (argc < 0 || argc > 1) { usage(); return -2; } if (argc >= 1) { name = argv[optind]; printf("single testcase: %s\n", name); } #else (void)argc; (void)argv; do_reg = true; do_int = true; do_thread = true; do_oom = false; do_perf = false; do_all = false; verbose = true; #endif /* Initialise debugging */ #if defined(WIN32) flags = 0; #else flags = DBG_ANSI; #endif dbg_init(DBG_INFO, flags); /* Initialise library */ libre_exception_btrace(true); err = libre_init(); if (err) goto out; err = poll_method_set(method); if (err) { DEBUG_WARNING("could not set polling method '%s' (%m)\n", poll_method_name(method), err); goto out; } dbg_handler_set(dbg_handler, 0); DEBUG_NOTICE("libre version %s (%s/%s)\n", sys_libre_version_get(), sys_arch_get(), sys_os_get()); dbg_handler_set(NULL, 0); if (do_all) { do_reg = true; do_oom = true; do_int = true; do_thread = true; } if (do_list) { test_listcases(); goto out; } /* * Different test-groups specified below: */ re_printf("using async polling method '%s'\n", poll_method_name(method)); if (verbose) { re_printf("using datapath '%s'\n", test_datapath()); } if (do_reg) { err = test_reg(name, verbose); TEST_ERR(err); } if (do_oom) { err = test_oom(name, verbose); TEST_ERR(err); } if (do_int) { err = test_integration(name, verbose); TEST_ERR(err); } if (do_perf) { err = test_perf(name, verbose); TEST_ERR(err); } if (do_thread) { err = test_multithread(); TEST_ERR(err); } out: re_thread_async_close(); /* Check for open timers */ tmr_debug(); libre_close(); /* Check for memory leaks */ mem_debug(); if (0 == mem_get_stat(&mstat)) { if (mstat.bytes_cur || mstat.blocks_cur) return 2; } return err; } ================================================ FILE: test/mbuf.c ================================================ /** * @file mbuf.c Mbuffer Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_mbuf" #define DEBUG_LEVEL 5 #include static int test_mbuf_basic(void) { struct mbuf mb; struct pl pl, hei = PL("hei"), foo = PL("foo"); static const char *pattern = "mmmmmmmmm"; char *str = NULL; int err; mbuf_init(&mb); /* write */ err = mbuf_write_u8(&mb, 0x5a); if (err) goto out; err = mbuf_write_u16(&mb, 0x5a5a); if (err) goto out; err = mbuf_write_u32(&mb, 0x5a5a5a5a); if (err) goto out; err = mbuf_write_str(&mb, "hei foo"); if (err) goto out; /* read */ mb.pos = 0; if (0x5a != mbuf_read_u8(&mb)) { err = EINVAL; goto out; } if (0x5a5a != mbuf_read_u16(&mb)) { err = EINVAL; goto out; } if (0x5a5a5a5a != mbuf_read_u32(&mb)) { err = EINVAL; goto out; } pl.p = (char *)mbuf_buf(&mb); pl.l = 3; err = pl_cmp(&hei, &pl); if (err) goto out; mb.pos += 4; pl.p = (char *)mbuf_buf(&mb); pl.l = mbuf_get_left(&mb); err = pl_cmp(&foo, &pl); if (err) goto out; /* Test mbuf_strdup() */ err = mbuf_strdup(&mb, &str, 3); if (err) goto out; err = pl_strcmp(&foo, str); TEST_ERR(err); mb.pos = mb.end = 0; err = mbuf_fill(&mb, 'm', 9); if (err) goto out; if (mb.pos != strlen(pattern) || mb.end != strlen(pattern) || 0 != memcmp(mb.buf, pattern, 9)) { err = EBADMSG; goto out; } /* Test position and end */ mbuf_set_posend(&mb, 2, 4); ASSERT_EQ(2, mbuf_pos(&mb)); ASSERT_EQ(4, mbuf_end(&mb)); out: mbuf_reset(&mb); mem_deref(str); return err; } static int test_mbuf_shift(void) { static const uint8_t payload[10] = {0,1,2,3,4,5,6,7,8,9}; struct mbuf *mb; int err; mb = mbuf_alloc(sizeof(payload)); if (!mb) return ENOMEM; err = mbuf_write_mem(mb, payload, sizeof(payload)); if (err) goto out; mb->pos = 0; /* inject a header in the front */ err = mbuf_shift(mb, 64); if (err) goto out; TEST_EQUALS(64, mb->pos); TEST_EQUALS(64+10, mb->end); TEST_MEMCMP(payload, sizeof(payload), mbuf_buf(mb), mbuf_get_left(mb)); /* remove a header in the front */ err = mbuf_shift(mb, -1); if (err) goto out; TEST_EQUALS(63, mb->pos); TEST_EQUALS(63+10, mb->end); TEST_MEMCMP(payload, sizeof(payload), mbuf_buf(mb), mbuf_get_left(mb)); out: mem_deref(mb); return err; } static int test_mbuf_ptr(void) { struct mbuf *buf; int err; buf = mbuf_alloc(1 * sizeof(void *)); if (!buf) return ENOMEM; err = mbuf_write_ptr(buf, (intptr_t)buf); buf->pos = 0; intptr_t p = mbuf_read_ptr(buf); TEST_EQUALS((intptr_t)buf, p); out: mem_deref(buf); return err; } int test_mbuf(void) { int err; err = test_mbuf_basic(); TEST_ERR(err); err = test_mbuf_shift(); TEST_ERR(err); err = test_mbuf_ptr(); TEST_ERR(err); out: return err; } ================================================ FILE: test/md5.c ================================================ /** * @file md5.c MD5 Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "testmd5" #define DEBUG_LEVEL 4 #include int test_md5(void) { const struct pl str = PL("a93akjshdla81mx.kjda09sdkjl12jdlksaldkjas"); const uint8_t ref[16] = { 0x9d, 0x97, 0xa5, 0xf8, 0x8d, 0x1b, 0x09, 0x7c, 0x9f, 0xf9, 0xe2, 0x9d, 0xd5, 0x43, 0xb1, 0x1d }; uint8_t digest[16]; int err; /* Test constants */ if (16 != MD5_SIZE) { DEBUG_WARNING("MD5_SIZE is %u (should be 16)\n", MD5_SIZE); return EINVAL; } if (33 != MD5_STR_SIZE) { DEBUG_WARNING("MD5_STR_SIZE is %u (should be 33)\n", MD5_STR_SIZE); return EINVAL; } /* Test md5() */ md5((const uint8_t *)str.p, str.l, digest); if (0 != memcmp(digest, ref, sizeof(digest))) { DEBUG_WARNING("md5 b0Rken: %02w\n", digest, sizeof(digest)); return EINVAL; } /* Test md5_printf() */ err = md5_printf(digest, "%r", &str); if (err) goto out; if (0 != memcmp(digest, ref, sizeof(digest))) { DEBUG_WARNING("md5_printf() is b0Rken: %02w\n", digest, sizeof(digest)); return EINVAL; } out: return err; } ================================================ FILE: test/mem.c ================================================ /** * @file mem.c Memory Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include "test.h" #define DEBUG_MODULE "test_mem" #define DEBUG_LEVEL 5 #include #define PATTERN 0xfcfcfcfc enum { #if defined(__x86_64__) /* Use 16-byte alignment on x86-x32 as well */ mem_alignment = 16u, #else mem_alignment = sizeof(void*) >= 8u ? 16u : 8u, #endif }; struct obj { uint32_t pattern; }; static void destructor(void *arg) { struct obj *obj = arg; if (PATTERN != obj->pattern) { DEBUG_WARNING("destroy error: %08x\n", obj->pattern); } } int test_mem(void) { struct obj *obj, *old = NULL, *tmp = NULL; int err = EINVAL; obj = mem_alloc(sizeof(*obj), destructor); if (!obj) return ENOMEM; obj->pattern = PATTERN; TEST_EQUALS(1, mem_nrefs(obj)); TEST_ASSERT(re_is_aligned(obj, mem_alignment)); obj = mem_ref(obj); TEST_EQUALS(2, mem_nrefs(obj)); mem_deref(obj); TEST_EQUALS(1, mem_nrefs(obj)); old = obj; obj = mem_realloc(old, sizeof(*obj) + 16); if (!obj) { old = mem_deref(old); err = ENOMEM; TEST_ERR(err); } TEST_ASSERT(re_is_aligned(obj, mem_alignment)); old = mem_ref(obj); TEST_EQUALS(2, mem_nrefs(obj)); obj = mem_realloc(obj, sizeof(*obj) + 64); TEST_EQUALS(1, mem_nrefs(old)); mem_deref(old); TEST_EQUALS(1, mem_nrefs(obj)); old = mem_ref(obj); TEST_EQUALS(2, mem_nrefs(obj)); tmp = mem_realloc(obj, sizeof(*obj) + 16); if (!tmp) { mem_deref(obj); err = ENOMEM; TEST_ERR(err); } TEST_EQUALS(1, mem_nrefs(old)); TEST_EQUALS(1, mem_nrefs(tmp)); err = 0; out: mem_deref(tmp); mem_deref(old); return err; } #ifndef SIZE_MAX #define SIZE_MAX (~((size_t)0)) #endif int test_mem_reallocarray(void) { void *a, *b; int err = 0; /* expect success */ a = mem_reallocarray(NULL, 10, 10, NULL); if (!a) return ENOMEM; /* expect failure */ b = mem_reallocarray(NULL, SIZE_MAX, SIZE_MAX, NULL); TEST_ASSERT(b == NULL); out: mem_deref(a); return err; } int test_mem_secure(void) { int r, err = 0; /* compare */ r = mem_seccmp(NULL, NULL, 42); TEST_ASSERT(r < 0); r = mem_seccmp((uint8_t *)"abc", (uint8_t *)"abc", 3); TEST_EQUALS(0, r); r = mem_seccmp((uint8_t *)"aaa", (uint8_t *)"bbb", 3); TEST_ASSERT(r > 0); r = mem_seccmp((uint8_t *)"ccc", (uint8_t *)"aaa", 3); TEST_ASSERT(r > 0); out: return err; } ================================================ FILE: test/mem_pool.c ================================================ /** * @file mem_pool.c Memory Pool Testcode * * Copyright (C) 2025 Sebastian Reimers */ #include #include "test.h" #define DEBUG_MODULE "test_mem_pool" #define DEBUG_LEVEL 5 #include struct object { int a; }; enum { NUM_OBJECTS = 10, }; int test_mem_pool(void) { int err; struct mem_pool *pool = NULL; err = mem_pool_alloc(&pool, NUM_OBJECTS, sizeof(struct object), NULL); TEST_ERR(err); struct mem_pool_entry *e; struct object *o; for (int i = 0; i < NUM_OBJECTS; i++) { e = mem_pool_borrow(pool); TEST_ASSERT(e); o = mem_pool_member(e); TEST_NOT_EQUALS(o->a, i + 1); o->a = i + 1; } TEST_ASSERT(!mem_pool_borrow(pool)); e = mem_pool_release(pool, e); TEST_ASSERT(!e); e = mem_pool_borrow(pool); TEST_ASSERT(e); TEST_ASSERT(!mem_pool_borrow(pool)); mem_pool_flush(pool); for (int i = 0; i < NUM_OBJECTS; i++) { e = mem_pool_borrow(pool); TEST_ASSERT(e); } TEST_ASSERT(!mem_pool_borrow(pool)); err = mem_pool_extend(pool, 1); TEST_ERR(err); e = mem_pool_borrow(pool); TEST_ASSERT(e); out: mem_deref(pool); return err; } ================================================ FILE: test/mock/cert.c ================================================ /** * @file cert.c TLS Certificate * * Copyright (C) 2010 Creytiv.com */ #include #include "test.h" /** * X509/PEM certificate with ECDSA keypair * * $ openssl ecparam -out ec_key.pem -name prime256v1 -genkey * $ openssl req -new -key ec_key.pem -x509 -nodes -days 3650 -out cert.pem */ const char test_certificate_ecdsa[] = "-----BEGIN CERTIFICATE-----\r\n" "MIICBzCCAa2gAwIBAgIUZy0UqzsDq7fGUsZh6QxkXgCa030wCgYIKoZIzj0EAwIw\r\n" "WTELMAkGA1UEBhMCTk8xEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\r\n" "dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTE5\r\n" "MDUyNDE5NTM0OFoXDTI5MDUyMTE5NTM0OFowWTELMAkGA1UEBhMCTk8xEzARBgNV\r\n" "BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0\r\n" "ZDESMBAGA1UEAwwJMTI3LjAuMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\r\n" "inP/oBEqBbXRxDzyk7sbh8rRJbfbXBRG2uJl2g6YhSkYZkifGyEueJ7+A9D9LfBh\r\n" "b5+lKXuJc02XQW5IwUmToqNTMFEwHQYDVR0OBBYEFH1vSH2IBZvKYNDPfPOk41Dw\r\n" "hyTWMB8GA1UdIwQYMBaAFH1vSH2IBZvKYNDPfPOk41DwhyTWMA8GA1UdEwEB/wQF\r\n" "MAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAOm79QetPxioy/S0Rk9lhPgfBslgM6f4\r\n" "tihVBSpe0FdJAiAC6Usj7p3H8dvu9Oa1gtOXSJkh1MT6pkfW21YseRWP4A==\r\n" "-----END CERTIFICATE-----\r\n" "-----BEGIN EC PARAMETERS-----\r\n" "BggqhkjOPQMBBw==\r\n" "-----END EC PARAMETERS-----\r\n" "-----BEGIN EC PRIVATE KEY-----\r\n" "MHcCAQEEIMWTO9/z24fiq13MM5UF1CVD3yJjVXRe0qpTCmmZU5ppoAoGCCqGSM49\r\n" "AwEHoUQDQgAEinP/oBEqBbXRxDzyk7sbh8rRJbfbXBRG2uJl2g6YhSkYZkifGyEu\r\n" "eJ7+A9D9LfBhb5+lKXuJc02XQW5IwUmTog==\r\n" "-----END EC PRIVATE KEY-----\r\n" ; ================================================ FILE: test/mock/dnssrv.c ================================================ /** * @file mock/dnssrv.c Mock DNS server * * Copyright (C) 2010 - 2016 Alfred E. Heggestad */ #include #include #include "../test.h" #define DEBUG_MODULE "mock/dnssrv" #define DEBUG_LEVEL 5 #include #define LOCAL_PORT 0 static bool rrlist_handler(struct dnsrr *rr, void *arg) { struct list *rrl = arg; list_append(rrl, &rr->le_priv, rr); return false; } static void decode_dns_query(struct dns_server *srv, const struct sa *src, struct mbuf *mb, int proto) { struct list rrl = LIST_INIT; struct dnshdr hdr; struct le *le; char *qname = NULL; size_t start, end; uint16_t type, dnsclass; int err = 0; start = mb->pos; end = mb->end; if (dns_hdr_decode(mb, &hdr) || hdr.qr || hdr.nq != 1) { DEBUG_WARNING("unable to decode query header\n"); return; } err = dns_dname_decode(mb, &qname, start); if (err) { DEBUG_WARNING("unable to decode query name\n"); goto out; } if (mbuf_get_left(mb) < 4) { DEBUG_WARNING("unable to decode query type/class\n"); goto out; } type = ntohs(mbuf_read_u16(mb)); dnsclass = ntohs(mbuf_read_u16(mb)); DEBUG_INFO("dnssrv: type=%s query-name='%s'\n", dns_rr_typename(type), qname); if (dnsclass == DNS_CLASS_IN) { dns_rrlist_apply(&srv->rrl, qname, type, DNS_CLASS_IN, hdr.rd, rrlist_handler, &rrl); } hdr.qr = true; hdr.tc = false; hdr.rcode = DNS_RCODE_OK; hdr.nq = 1; hdr.nans = list_count(&rrl); mb->pos = start; err = dns_hdr_encode(mb, &hdr); if (err) goto out; mb->pos = end; DEBUG_INFO("dnssrv: @@ found %u answers for %s\n", list_count(&rrl), qname); for (le = rrl.head; le; le = le->next) { struct dnsrr *rr = le->data; err = dns_rr_encode(mb, rr, 0, NULL, start); if (err) goto out; } mb->pos = start; switch (proto) { case IPPROTO_UDP: (void)udp_send(srv->us, src, mb); break; case IPPROTO_TCP: { size_t length = mb->end - start; struct mbuf *mb_tcp = mbuf_alloc(sizeof(uint16_t) + length); if (!mb_tcp) goto out; mbuf_write_u16(mb_tcp, htons((uint16_t)length)); mbuf_write_mem(mb_tcp, mbuf_buf(mb), length); mbuf_set_pos(mb_tcp, 0); tcp_send(srv->tc, mb_tcp); mem_deref(mb_tcp); } break; } out: list_clear(&rrl); mem_deref(qname); } static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct dns_server *srv = arg; decode_dns_query(srv, src, mb, IPPROTO_UDP); } static void destructor(void *arg) { struct dns_server *srv = arg; list_flush(&srv->rrl); mem_deref(srv->us); mem_deref(srv->tc); mem_deref(srv->ts); mem_deref(srv->mb); } static void tcp_recv_handler(struct mbuf *mbrx, void *arg) { struct dns_server *srv = arg; struct mbuf *mb = srv->mb; int err = 0; size_t n; next: /* frame length */ if (!srv->flen) { n = min(2 - mb->end, mbuf_get_left(mbrx)); err = mbuf_write_mem(mb, mbuf_buf(mbrx), n); if (err) goto error; mbrx->pos += n; if (mb->end < 2) return; mb->pos = 0; srv->flen = ntohs(mbuf_read_u16(mb)); mbuf_rewind(mb); } n = min(srv->flen - mb->end, mbuf_get_left(mbrx)); err = mbuf_write_mem(mb, mbuf_buf(mbrx), n); if (err) goto error; mbrx->pos += n; if (mb->end < srv->flen) return; mb->pos = 0; decode_dns_query(srv, NULL, mb, IPPROTO_TCP); srv->flen = 0; mbuf_rewind(mb); if (mbuf_get_left(mbrx) > 0) { DEBUG_INFO("%zu bytes of tcp data left\n", mbuf_get_left(mbrx)); goto next; } return; error: srv->tc = mem_deref(srv->tc); } static void tcp_close_handler(int err, void *arg) { struct dns_server *srv = arg; (void)err; srv->tc = mem_deref(srv->tc); srv->mb = mem_deref(srv->mb); srv->flen = 0; } static void tcp_conn_handler(const struct sa *peer, void *arg) { struct dns_server *srv = arg; int err = 0; (void)peer; /* max 1 TCP connection */ TEST_ASSERT(srv->tc == NULL); srv->mb = mbuf_alloc(1500); if (!srv->mb) { err = ENOMEM; goto out; } srv->flen = 0; err = tcp_accept(&srv->tc, srv->ts, NULL, tcp_recv_handler, tcp_close_handler, srv); if (err) goto out; out: if (err) { tcp_reject(srv->ts); srv->mb = mem_deref(srv->mb); srv->flen = 0; } } void dns_server_flush(struct dns_server *srv) { list_flush(&srv->rrl); } int dns_server_alloc(struct dns_server **srvp, const char *laddr) { struct dns_server *srv; struct sa laddr_tcp; int err; if (!srvp) return EINVAL; sa_set_str(&laddr_tcp, laddr, 0); srv = mem_zalloc(sizeof(*srv), destructor); if (!srv) return ENOMEM; err = sa_set_str(&srv->addr, laddr, LOCAL_PORT); if (err) goto out; err = udp_listen(&srv->us, &srv->addr, udp_recv, srv); if (err) goto out; err = udp_local_get(srv->us, &srv->addr); if (err) goto out; err = tcp_listen(&srv->ts, &laddr_tcp, tcp_conn_handler, srv); if (err) goto out; err = tcp_local_get(srv->ts, &srv->addr_tcp); if (err) goto out; out: if (err) mem_deref(srv); else *srvp = srv; return err; } int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr, int64_t ttl) { struct dnsrr *rr; int err; if (!srv || !name) return EINVAL; rr = dns_rr_alloc(); if (!rr) return ENOMEM; err = str_dup(&rr->name, name); if (err) goto out; rr->type = DNS_TYPE_A; rr->dnsclass = DNS_CLASS_IN; rr->ttl = ttl; rr->rdlen = 0; rr->rdata.a.addr = addr; list_append(&srv->rrl, &rr->le, rr); out: if (err) mem_deref(rr); return err; } int dns_server_add_aaaa(struct dns_server *srv, const char *name, const uint8_t *addr, int64_t ttl) { struct dnsrr *rr; int err; if (!srv || !name) return EINVAL; rr = dns_rr_alloc(); if (!rr) return ENOMEM; err = str_dup(&rr->name, name); if (err) goto out; rr->type = DNS_TYPE_AAAA; rr->dnsclass = DNS_CLASS_IN; rr->ttl = ttl; rr->rdlen = 0; memcpy(rr->rdata.aaaa.addr, addr, 16); list_append(&srv->rrl, &rr->le, rr); out: if (err) mem_deref(rr); return err; } int dns_server_add_srv(struct dns_server *srv, const char *name, uint16_t pri, uint16_t weight, uint16_t port, const char *target, int64_t ttl) { struct dnsrr *rr; int err; if (!srv || !name || !port || !target) return EINVAL; rr = dns_rr_alloc(); if (!rr) return ENOMEM; err = str_dup(&rr->name, name); if (err) goto out; rr->type = DNS_TYPE_SRV; rr->dnsclass = DNS_CLASS_IN; rr->ttl = ttl; rr->rdlen = 0; rr->rdata.srv.pri = pri; rr->rdata.srv.weight = weight; rr->rdata.srv.port = port; str_dup(&rr->rdata.srv.target, target); list_append(&srv->rrl, &rr->le, rr); out: if (err) mem_deref(rr); return err; } ================================================ FILE: test/mock/nat.c ================================================ /** * @file mock/nat.c Mock NAT-box * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "mock/nat" #define DEBUG_LEVEL 5 #include enum { LAYER_NAT = -1000 }; static void nat_binding_add(struct nat *nat, const struct sa *addr) { if (!nat || !addr) return; if (nat->bindingc >= RE_ARRAY_SIZE(nat->bindingv)) { DEBUG_WARNING("NAT-box at max capacity\n"); return; } nat->bindingv[nat->bindingc++] = *addr; } static struct sa *nat_binding_find_addr(struct nat *nat, const struct sa *addr) { unsigned i; if (!nat || !addr) return NULL; for (i=0; ibindingc; i++) { if (sa_cmp(addr, &nat->bindingv[i], SA_ALL)) return &nat->bindingv[i]; } return NULL; } static struct sa *nat_binding_find(struct nat *nat, uint16_t port) { unsigned i; if (!nat || !port) return NULL; for (i=0; ibindingc; i++) { if (port == sa_port(&nat->bindingv[i])) return &nat->bindingv[i]; } return NULL; } static bool nat_helper_send(int *err, struct sa *dst, struct mbuf *mb, void *arg) { struct nat *nat = arg; struct sa *cli; (void)mb; cli = nat_binding_find(nat, sa_port(dst)); #if 0 re_printf("nat: send INGRESS %J -> %J\n", dst, cli); #endif if (cli) { *dst = *cli; return false; } else { *err = ENOTCONN; DEBUG_WARNING("nat: binding to %J not found\n", dst); return true; } } static bool nat_helper_recv(struct sa *src, struct mbuf *mb, void *arg) { struct nat *nat = arg; struct sa map; (void)mb; if (!nat_binding_find(nat, sa_port(src))) { nat_binding_add(nat, src); } map = nat->public_addr; sa_set_port(&map, sa_port(src)); #if 0 re_printf("nat: recv EGRESS %J -> %J\n", src, &map); #endif *src = map; return false; } static bool firewall_egress(int *err, struct sa *dst, struct mbuf *mb, void *arg) { struct nat *nat = arg; (void)err; (void)mb; /* add egress mapping to external addr */ if (!nat_binding_find_addr(nat, dst)) { nat_binding_add(nat, dst); } return false; } static bool firewall_ingress(struct sa *src, struct mbuf *mb, void *arg) { struct nat *nat = arg; (void)mb; /* check if external address has a mapping */ if (!nat_binding_find_addr(nat, src)) { DEBUG_NOTICE("firewall: drop 1 packet from %J\n", src); return true; } return false; } static void nat_destructor(void *arg) { struct nat *nat = arg; mem_deref(nat->uh); mem_deref(nat->us); } /* inbound NAT */ int nat_alloc(struct nat **natp, enum natbox_type type, struct udp_sock *us, const struct sa *public_addr) { struct nat *nat; int err = 0; if (!natp || !us) return EINVAL; if (type == NAT_INBOUND_SNAT && !public_addr) return EINVAL; if (udp_helper_find(us, LAYER_NAT)) { DEBUG_WARNING("udp helper already exist on layer %d\n", LAYER_NAT); return EPROTO; } nat = mem_zalloc(sizeof(*nat), nat_destructor); if (!nat) return ENOMEM; nat->type = type; if (public_addr) nat->public_addr = *public_addr; nat->us = mem_ref(us); switch (type) { case NAT_INBOUND_SNAT: err = udp_register_helper(&nat->uh, us, LAYER_NAT, nat_helper_send, nat_helper_recv, nat); break; case NAT_FIREWALL: err = udp_register_helper(&nat->uh, us, LAYER_NAT, firewall_egress, firewall_ingress, nat); break; default: DEBUG_WARNING("invalid NAT type %d\n", type); err = ENOTSUP; break; } if (err) goto out; out: if (err) mem_deref(nat); else *natp = nat; return err; } ================================================ FILE: test/mock/sipsrv.c ================================================ /** * @file mock/sipsrv.c Mock SIP server * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "mock/sipsrv" #define DEBUG_LEVEL 5 #include #define LOCAL_PORT 0 #define LOCAL_SECURE_PORT 0 static bool sip_msg_handler(const struct sip_msg *msg, void *arg) { struct sip_server *srv = arg; int err; if (0 == pl_strcmp(&msg->met, "REGISTER")) { mbuf_set_pos(msg->mb, 0); if (sip_msg_decode(&srv->sip_msgs[srv->n_register_req], msg->mb)) { DEBUG_NOTICE("sip message cannot be parsed (%r)\n", &msg->met); return false; } ++srv->n_register_req; } else if (0 == pl_strcmp(&msg->met, "OPTIONS")) { ++srv->n_options_req; } else { DEBUG_NOTICE("method not handled (%r)\n", &msg->met); return false; } if (srv->terminate) err = sip_reply(srv->sip, msg, 503, "Server Error"); else { if (sip_msg_hdr_has_value(msg, SIP_HDR_SUPPORTED, "outbound")) { err = sip_replyf(srv->sip, msg, 200, "OK", "Contact: <%r>\r\n" "Content-Length: 0\r\n" "Require: outbound\r\n" "Flow-Timer: 1\r\n" "\r\n" , &msg->to.auri); } else { err = sip_reply(srv->sip, msg, 200, "OK"); } } if (err) { DEBUG_WARNING("could not reply: %m\n", err); } #if 0 if (srv->terminate) re_cancel(); #endif return true; } static void destructor(void *arg) { struct sip_server *srv = arg; srv->terminate = true; sip_close(srv->sip, false); for (unsigned i=0; isip_msgs); i++) mem_deref(srv->sip_msgs[i]); mem_deref(srv->sip); } int sip_server_alloc(struct sip_server **srvp) { struct sip_server *srv; struct sa laddr, laddrs; struct tls *tls = NULL; int err; if (!srvp) return EINVAL; srv = mem_zalloc(sizeof *srv, destructor); if (!srv) return ENOMEM; err = sa_set_str(&laddr, "127.0.0.1", LOCAL_PORT); err |= sa_set_str(&laddrs, "127.0.0.1", LOCAL_SECURE_PORT); if (err) goto out; err = sip_alloc(&srv->sip, NULL, 16, 16, 16, "mock SIP server", NULL, NULL); if (err) goto out; err |= sip_transp_add(srv->sip, SIP_TRANSP_UDP, &laddr); err |= sip_transp_add(srv->sip, SIP_TRANSP_TCP, &laddr); if (err) goto out; #ifdef USE_TLS err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; err = tls_set_certificate(tls, test_certificate_ecdsa, strlen(test_certificate_ecdsa)); if (err) goto out; err |= sip_transp_add(srv->sip, SIP_TRANSP_TLS, &laddrs, tls); #endif if (err) goto out; err = sip_listen(&srv->lsnr, srv->sip, true, sip_msg_handler, srv); if (err) goto out; out: mem_deref(tls); if (err) mem_deref(srv); else *srvp = srv; return err; } int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, enum sip_transp tp) { struct sa laddr; int err; if (!srv || !uri || !sz) return EINVAL; err = sip_transp_laddr(srv->sip, &laddr, tp, NULL); if (err) return err; if (re_snprintf(uri, sz, "sip:%J%s", &laddr, sip_transp_param(tp)) < 0) return ENOMEM; return 0; } ================================================ FILE: test/mock/stunsrv.c ================================================ /** * @file mock/stunsrv.c Mock STUN server * * Copyright (C) 2010 Creytiv.com */ #include #include "test.h" #define DEBUG_MODULE "mock/stunsrv" #define DEBUG_LEVEL 5 #include enum { TCP_MAX_LENGTH = 2048, }; static void process_msg(struct stunserver *stun, int proto, void *sock, const struct sa *src, const struct sa *dst, struct mbuf *mb) { struct stun_msg *msg; bool fp = false; int err; (void)dst; stun->nrecv++; err = stun_msg_decode(&msg, mb, NULL); if (err) return; #if 0 stun_msg_dump(msg); #endif TEST_EQUALS(0x0001, stun_msg_type(msg)); TEST_EQUALS(STUN_CLASS_REQUEST, stun_msg_class(msg)); TEST_EQUALS(STUN_METHOD_BINDING, stun_msg_method(msg)); /* mirror FINGERPRINT attribute back in response */ fp = NULL != stun_msg_attr(msg, STUN_ATTR_FINGERPRINT); if (fp) { TEST_EQUALS(0, stun_msg_chk_fingerprint(msg)); } err = stun_reply(proto, sock, src, 0, msg, NULL, 0, fp, 2, STUN_ATTR_MAPPED_ADDR, src, STUN_ATTR_XOR_MAPPED_ADDR, src); out: if (err) { (void)stun_ereply(proto, sock, src, 0, msg, 400, "Bad Request", NULL, 0, fp, 0); } mem_deref(msg); } static void stunserver_udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct stunserver *stun = arg; process_msg(stun, IPPROTO_UDP, stun->us, src, &stun->laddr, mb); } static void tcp_recv(struct mbuf *mb, void *arg) { struct stunserver *stun = arg; int err = 0; if (stun->mb) { size_t pos; pos = stun->mb->pos; stun->mb->pos = stun->mb->end; err = mbuf_write_mem(stun->mb, mbuf_buf(mb),mbuf_get_left(mb)); if (err) { goto out; } stun->mb->pos = pos; } else { stun->mb = mem_ref(mb); } for (;;) { size_t len, pos, end; uint16_t typ; if (mbuf_get_left(stun->mb) < 4) break; typ = ntohs(mbuf_read_u16(stun->mb)); len = ntohs(mbuf_read_u16(stun->mb)); if (len > TCP_MAX_LENGTH) { DEBUG_WARNING("tcp: bad length: %zu\n", len); err = EBADMSG; goto out; } if (typ < 0x4000) len += STUN_HEADER_SIZE; else if (typ < 0x8000) len += 4; else { DEBUG_WARNING("tcp: bad type: 0x%04x\n", typ); err = EBADMSG; goto out; } stun->mb->pos -= 4; if (mbuf_get_left(stun->mb) < len) break; pos = stun->mb->pos; end = stun->mb->end; stun->mb->end = pos + len; process_msg(stun, IPPROTO_TCP, stun->tc, &stun->paddr, &stun->laddr_tcp, stun->mb); /* 4 byte alignment */ while (len & 0x03) ++len; stun->mb->pos = pos + len; stun->mb->end = end; if (stun->mb->pos >= stun->mb->end) { stun->mb = mem_deref(stun->mb); break; } } out: if (err) { stun->mb = mem_deref(stun->mb); } } static void tcp_close(int err, void *arg) { struct stunserver *stun = arg; (void)err; stun->tc = mem_deref(stun->tc); } static void tcp_conn_handler(const struct sa *peer, void *arg) { struct stunserver *stun = arg; int err; /* max 1 TCP connection */ TEST_ASSERT(stun->tc == NULL); err = tcp_accept(&stun->tc, stun->ts, NULL, tcp_recv, tcp_close, stun); if (err) goto out; stun->paddr = *peer; out: if (err) { /* save the error code */ stun->err = err; tcp_reject(stun->ts); } } static void stunserver_destructor(void *arg) { struct stunserver *stun = arg; mem_deref(stun->us); mem_deref(stun->mb); mem_deref(stun->tc); mem_deref(stun->ts); } /* Both UDP- and TCP-transport enabled by default */ int stunserver_alloc(struct stunserver **stunp, const char *laddr_str) { struct stunserver *stun; struct sa laddr; int err; if (!stunp) return EINVAL; stun = mem_zalloc(sizeof(*stun), stunserver_destructor); if (!stun) return ENOMEM; sa_set_str(&laddr, laddr_str, 0); err = udp_listen(&stun->us, &laddr, stunserver_udp_recv, stun); if (err) goto out; err = udp_local_get(stun->us, &stun->laddr); if (err) goto out; err = tcp_listen(&stun->ts, &laddr, tcp_conn_handler, stun); if (err) goto out; err = tcp_local_get(stun->ts, &stun->laddr_tcp); if (err) goto out; #if 0 DEBUG_NOTICE("stunserver: udp=%J, tcp=%J\n", &stun->laddr, &stun->laddr_tcp); #endif out: if (err) mem_deref(stun); else *stunp = stun; return err; } const struct sa *stunserver_addr(const struct stunserver *stun, int proto) { if (!stun) return NULL; switch (proto) { case IPPROTO_UDP: return &stun->laddr; case IPPROTO_TCP: return &stun->laddr_tcp; default: return NULL; } return NULL; } ================================================ FILE: test/mock/turnsrv.c ================================================ /** * @file mock/turnsrv.c Mock TURN server * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "mock/turnsrv" #define DEBUG_LEVEL 5 #include enum { TCP_MAX_LENGTH = 2048, NONCE_EXPIRY = 3600, /* seconds */ NONCE_MAX_SIZE = 48, NONCE_MIN_SIZE = 33, }; struct auth_context { uint8_t key[MD5_SIZE]; size_t keylen; }; static struct channel *find_channel_numb(struct turnserver *tt, uint16_t nr) { size_t i; if (!tt) return NULL; for (i=0; ichanc; i++) { if (tt->chanv[i].nr == nr) return &tt->chanv[i]; } return NULL; } static struct channel *find_channel_peer(struct turnserver *tt, const struct sa *peer) { size_t i; if (!tt) return NULL; for (i=0; ichanc; i++) { if (sa_cmp(&tt->chanv[i].peer, peer, SA_ALL)) return &tt->chanv[i]; } return NULL; } static int add_permission(struct turnserver *tt, const struct sa *peer) { int err = 0; TEST_ASSERT(tt->permc < RE_ARRAY_SIZE(tt->permv)); tt->permv[tt->permc] = *peer; ++tt->permc; out: return err; } static struct sa *find_permission(struct turnserver *tt, const struct sa *peer) { size_t i; if (!tt) return NULL; for (i=0; ipermc; i++) { if (sa_cmp(&tt->permv[i], peer, SA_ADDR)) return &tt->permv[i]; } return NULL; } /* Receive packet on the "relayed" address -- relay to the client */ static void relay_udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct turnserver *turn = arg; struct channel *chan; int err = 0; ++turn->n_recv; chan = find_channel_peer(turn, src); if (chan) { uint16_t len = (uint16_t)mbuf_get_left(mb); size_t start; if (mb->pos < 4) { DEBUG_WARNING("relay_udp_recv: mb pos < 4\n"); return; } mb->pos -= 4; start = mb->pos; (void)mbuf_write_u16(mb, htons(chan->nr)); (void)mbuf_write_u16(mb, htons(len)); mb->pos = start; if (turn->tc) err = tcp_send(turn->tc, mb); else err = udp_send(turn->us, &turn->cli, mb); } else { int proto = turn->tc ? IPPROTO_TCP : IPPROTO_UDP; void *sock = turn->tc ? (void *)turn->tc : (void *)turn->us; err = stun_indication(proto, sock, &turn->cli, 0, STUN_METHOD_DATA, NULL, 0, false, 2, STUN_ATTR_XOR_PEER_ADDR, src, STUN_ATTR_DATA, mb); } if (err) { DEBUG_WARNING("relay_udp_recv: error %m\n", err); } } static const char *mknonce(struct turnserver *turn, char *nonce, time_t now, const struct sa *src) { uint8_t key[MD5_SIZE]; uint64_t nv[3]; nv[0] = now; nv[1] = turn->auth_secret; nv[2] = sa_hash(src, SA_ADDR); md5((uint8_t *)nv, sizeof(nv), key); (void)re_snprintf(nonce, NONCE_MAX_SIZE + 1, "%w%llx", key, sizeof(key), nv[0]); return nonce; } static bool nonce_validate(struct turnserver *turn, const char *nonce, time_t now, const struct sa *src) { uint8_t nkey[MD5_SIZE], ckey[MD5_SIZE]; uint64_t nv[3]; struct pl pl; pl.p = nonce; pl.l = str_len(nonce); if (pl.l < NONCE_MIN_SIZE || pl.l > NONCE_MAX_SIZE) { DEBUG_NOTICE("auth: bad nonce length (%zu)\n", pl.l); return false; } for (size_t i=0; iauth_secret; nv[2] = sa_hash(src, SA_ADDR); md5((uint8_t *)nv, sizeof(nv), ckey); if (memcmp(nkey, ckey, MD5_SIZE)) { DEBUG_NOTICE("auth: invalid nonce (%j)\n", src); return false; } int64_t age = now - nv[0]; if (age < 0 || age > NONCE_EXPIRY) { DEBUG_NOTICE("auth: nonce expired, age: %lli secs\n", age); return false; } return true; } static int auth_get_ha1(struct turnserver *turn, const char *username, uint8_t *ha1) { static const char auth_password[] = "password"; return md5_printf(ha1, "%s:%s:%s", username, turn->auth_realm, auth_password); } static bool auth_request(struct turnserver *turn, struct auth_context *ctx, int proto, void *sock, const struct sa *src, const struct stun_msg *msg) { struct stun_attr *mi, *user, *realm, *nonce; const time_t now = time(NULL); char nstr[NONCE_MAX_SIZE + 1]; int err; mi = stun_msg_attr(msg, STUN_ATTR_MSG_INTEGRITY); user = stun_msg_attr(msg, STUN_ATTR_USERNAME); realm = stun_msg_attr(msg, STUN_ATTR_REALM); nonce = stun_msg_attr(msg, STUN_ATTR_NONCE); if (!mi) { err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, false, 2, STUN_ATTR_REALM, turn->auth_realm, STUN_ATTR_NONCE, mknonce(turn, nstr, now, src)); goto unauth; } if (turn->error_scode) { err = stun_ereply(proto, sock, src, 0, msg, turn->error_scode, "Error", NULL, 0, false, 2, STUN_ATTR_REALM, turn->auth_realm, STUN_ATTR_NONCE, mknonce(turn, nstr, now, src)); turn->error_scode = 0; goto unauth; } if (!user || !realm || !nonce) { err = stun_ereply(proto, sock, src, 0, msg, 400, "Bad Request", NULL, 0, false, 0); goto unauth; } if (!nonce_validate(turn, nonce->v.nonce, now, src)) { err = stun_ereply(proto, sock, src, 0, msg, 438, "Stale Nonce", NULL, 0, false, 2, STUN_ATTR_REALM, turn->auth_realm, STUN_ATTR_NONCE, mknonce(turn, nstr, now, src)); goto unauth; } ctx->keylen = MD5_SIZE; if (auth_get_ha1(turn, user->v.username, ctx->key)) { DEBUG_NOTICE("auth: unknown user '%s' (%j)\n", user->v.username, src); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, false, 2, STUN_ATTR_REALM, turn->auth_realm, STUN_ATTR_NONCE, mknonce(turn, nstr, now, src)); goto unauth; } if (stun_msg_chk_mi(msg, ctx->key, ctx->keylen)) { DEBUG_NOTICE("auth: bad password for user '%s' (%j)\n", user->v.username, src); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, false, 2, STUN_ATTR_REALM, turn->auth_realm, STUN_ATTR_NONCE, mknonce(turn, nstr, now, src)); goto unauth; } return false; unauth: if (err) { DEBUG_WARNING("auth reply error: %m\n", err); } return true; } static void process_msg(struct turnserver *turn, int proto, void *sock, const struct sa *src, struct mbuf *mb) { struct auth_context ctx = {0}; struct stun_msg *msg = NULL; struct sa laddr; int err = 0; const uint32_t alloc_lifetime = TURN_DEFAULT_LIFETIME; if (stun_msg_decode(&msg, mb, NULL)) { uint16_t numb, len; struct channel *chan; if (!turn->us_relay) return; ++turn->n_raw; numb = ntohs(mbuf_read_u16(mb)); len = ntohs(mbuf_read_u16(mb)); if (mbuf_get_left(mb) < len) { DEBUG_WARNING("short length: %zu < %u\n", mbuf_get_left(mb), len); } chan = find_channel_numb(turn, numb); if (!chan) { DEBUG_WARNING("channel not found: numb=%u\n", numb); return; } /* relay data from channel to peer */ (void)udp_send(turn->us_relay, &chan->peer, mb); return; } #if 0 re_printf("process: %s:%p:%J %s\n", net_proto2name(proto), sock, src, stun_method_name(stun_msg_method(msg))); #endif if (stun_msg_class(msg) == STUN_CLASS_REQUEST) { bool unauth = auth_request(turn, &ctx, proto, sock, src, msg); if (unauth) goto out; } switch (stun_msg_method(msg)) { case STUN_METHOD_ALLOCATE: /* Max 1 allocation for now */ ++turn->n_allocate; if (turn->us_relay) { err = EALREADY; goto out; } turn->cli = *src; err = sa_set_str(&laddr, turn->addr, 0); if (err) goto out; err = udp_listen(&turn->us_relay, &laddr, relay_udp_recv, turn); if (err) goto out; err = udp_local_get(turn->us_relay, &turn->relay); if (err) goto out; udp_rxbuf_presz_set(turn->us_relay, 4); err = stun_reply(proto, sock, src, 0, msg, ctx.key, ctx.keylen, false, 3, STUN_ATTR_XOR_MAPPED_ADDR, src, STUN_ATTR_XOR_RELAY_ADDR, &turn->relay, STUN_ATTR_LIFETIME, &alloc_lifetime); break; case STUN_METHOD_CREATEPERM: { struct stun_attr *peer; ++turn->n_createperm; peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR); TEST_ASSERT(peer != NULL); add_permission(turn, &peer->v.xor_peer_addr); /* todo: install permissions and check them */ err = stun_reply(proto, sock, src, 0, msg, ctx.key, ctx.keylen, false, 0); } break; case STUN_METHOD_CHANBIND: { struct stun_attr *chnr, *peer; ++turn->n_chanbind; TEST_ASSERT(turn->us_relay != NULL); chnr = stun_msg_attr(msg, STUN_ATTR_CHANNEL_NUMBER); peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR); if (!chnr || !peer) { DEBUG_WARNING("CHANBIND: missing chnr/peer attrib\n"); goto out; } TEST_ASSERT(turn->chanc < RE_ARRAY_SIZE(turn->chanv)); turn->chanv[turn->chanc].nr = chnr->v.channel_number; turn->chanv[turn->chanc].peer = peer->v.xor_peer_addr; ++turn->chanc; err = stun_reply(proto, sock, src, 0, msg, ctx.key, ctx.keylen, false, 0); } break; case STUN_METHOD_SEND: { struct stun_attr *peer, *data; ++turn->n_send; TEST_ASSERT(turn->us_relay != NULL); peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR); data = stun_msg_attr(msg, STUN_ATTR_DATA); if (!peer || !data) { DEBUG_WARNING("SEND: missing peer/data attrib\n"); goto out; } /* check for valid Permission */ if (!find_permission(turn, &peer->v.xor_peer_addr)) { DEBUG_NOTICE("no permission to peer %j\n", &peer->v.xor_peer_addr); goto out; } err = udp_send(turn->us_relay, &peer->v.xor_peer_addr, &data->v.data); } break; case STUN_METHOD_REFRESH: { uint32_t lifetime = 1; /* short test lifetime */ err = stun_reply(proto, sock, src, 0, msg, NULL, 0, false, 1, STUN_ATTR_LIFETIME, &lifetime); } break; default: DEBUG_WARNING("unknown STUN method: %s\n", stun_method_name(stun_msg_method(msg))); err = EPROTO; break; } if (err) goto out; out: if (err && stun_msg_class(msg) == STUN_CLASS_REQUEST) { (void)stun_ereply(proto, sock, src, 0, msg, 500, "Server Error", NULL, 0, false, 0); } mem_deref(msg); } /* Simulated TURN server */ static void srv_udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct turnserver *turn = arg; process_msg(turn, IPPROTO_UDP, turn->us, src, mb); } static void tcp_estab_handler(void *arg) { struct turnserver *turn = arg; (void)turn; } static void tcp_recv_handler(struct mbuf *mb, void *arg) { struct turnserver *conn = arg; int err = 0; if (conn->mb) { size_t pos; pos = conn->mb->pos; conn->mb->pos = conn->mb->end; err = mbuf_write_mem(conn->mb, mbuf_buf(mb),mbuf_get_left(mb)); if (err) { DEBUG_WARNING("tcp: buffer write error: %m\n", err); goto out; } conn->mb->pos = pos; } else { conn->mb = mem_ref(mb); } for (;;) { size_t len, pos, end; uint16_t typ; if (mbuf_get_left(conn->mb) < 4) break; typ = ntohs(mbuf_read_u16(conn->mb)); len = ntohs(mbuf_read_u16(conn->mb)); if (len > TCP_MAX_LENGTH) { re_printf("tcp: bad length: %zu\n", len); err = EBADMSG; goto out; } if (typ < 0x4000) len += STUN_HEADER_SIZE; else if (typ < 0x8000) len += 4; else { re_printf("tcp: bad type: 0x%04x\n", typ); err = EBADMSG; goto out; } conn->mb->pos -= 4; if (mbuf_get_left(conn->mb) < len) break; pos = conn->mb->pos; end = conn->mb->end; conn->mb->end = pos + len; process_msg(conn, IPPROTO_TCP, conn->tc, &conn->paddr, conn->mb); /* 4 byte alignment */ while (len & 0x03) ++len; conn->mb->pos = pos + len; conn->mb->end = end; if (conn->mb->pos >= conn->mb->end) { conn->mb = mem_deref(conn->mb); break; } } out: if (err) { conn->mb = mem_deref(conn->mb); } } static void tcp_close_handler(int err, void *arg) { struct turnserver *turn = arg; (void)err; turn->tc = mem_deref(turn->tc); } static void tcp_conn_handler(const struct sa *peer, void *arg) { struct turnserver *turn = arg; int err = 0; if (turn->tc) { tcp_reject(turn->ts); } else { err = tcp_accept(&turn->tc, turn->ts, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, turn); if (err) tcp_reject(turn->ts); turn->paddr = *peer; } } static void destructor(void *arg) { struct turnserver *turn = arg; mem_deref(turn->us); mem_deref(turn->us_relay); mem_deref(turn->tc); mem_deref(turn->ts); mem_deref(turn->mb); } int turnserver_alloc(struct turnserver **turnp, const char *addr) { struct turnserver *turn; struct sa laddr; int err = 0; if (!turnp) return EINVAL; turn = mem_zalloc(sizeof(*turn), destructor); if (!turn) return ENOMEM; str_ncpy(turn->addr, addr, sizeof(turn->addr)); err = sa_set_str(&laddr, addr, 0); if (err) goto out; err = udp_listen(&turn->us, &laddr, srv_udp_recv, turn); if (err) goto out; err = udp_local_get(turn->us, &turn->laddr); if (err) goto out; err = tcp_listen(&turn->ts, &laddr, tcp_conn_handler, turn); if (err) goto out; err = tcp_sock_local_get(turn->ts, &turn->laddr_tcp); if (err) goto out; turn->auth_realm = "RETEST-REALM"; turn->auth_secret = 1234; out: if (err) mem_deref(turn); else *turnp = turn; return err; } void turnserver_force_error(struct turnserver *turn, uint16_t scode) { if (!turn) return; turn->error_scode = scode; } ================================================ FILE: test/mqueue.c ================================================ /** * @file mqueue.c Message queue testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "mqueue_test" #define DEBUG_LEVEL 5 #include #define NUM_EVENTS 3 struct test { int idv[NUM_EVENTS]; void *datav[NUM_EVENTS]; unsigned idc; }; static void mqueue_handler(int id, void *data, void *arg) { struct test *test = arg; test->idv [test->idc] = id; test->datav[test->idc] = data; test->idc++; if (test->idc >= NUM_EVENTS) re_cancel(); } int test_mqueue(void) { struct mqueue *mq; struct test test; int i; int err; memset(&test, 0, sizeof(test)); err = mqueue_alloc(&mq, mqueue_handler, &test); if (err) return err; for (i=0; i #include "test.h" #define DEBUG_MODULE "test_net" #define DEBUG_LEVEL 5 #include static bool ipv6_handler(const char *ifname, const struct sa *sa, void *arg) { bool *supp = arg; (void)ifname; if (AF_INET6 == sa_af(sa)) { *supp = true; return true; } return false; } bool test_ipv6_supported(void) { bool supp = false; net_if_apply(ipv6_handler, &supp); return supp; } int test_net_dst_source_addr_get(void) { struct sa dst; struct sa ip; int err; sa_init(&dst, AF_INET); sa_init(&ip, AF_UNSPEC); sa_set_str(&dst, "127.0.0.1", 53); err = net_dst_source_addr_get(&dst, &ip); if (err) return err; TEST_ASSERT(sa_is_loopback(&ip)); if (test_ipv6_supported()) { sa_init(&dst, AF_INET6); sa_init(&ip, AF_UNSPEC); sa_set_str(&dst, "::1", 53); err = net_dst_source_addr_get(&dst, &ip); if (err) return err; TEST_ASSERT(sa_is_loopback(&ip)); } else { DEBUG_NOTICE("ipv6 disabled\n"); } out: return err; } int test_net_if(void) { struct sa ip; int err; char ifname[255]; sa_set_str(&ip, "127.0.0.1", 0); err = net_if_getname(ifname, sizeof(ifname), AF_INET, &ip); TEST_ERR(err); TEST_ASSERT(str_isset(ifname)); out: return err; } ================================================ FILE: test/odict.c ================================================ /** * @file odict.c Testcode for Ordered Dictionary * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "odict" #define DEBUG_LEVEL 5 #include struct dtest { const char *key; enum odict_type type; union { int64_t i; double d; char *s; bool b; } u; }; static int compare(const struct dtest *test, const struct odict_entry *entry) { int err = 0; TEST_ASSERT(entry != NULL); TEST_STRCMP(test->key, str_len(test->key), odict_entry_key(entry), strlen(odict_entry_key(entry))); TEST_EQUALS(test->type, odict_entry_type(entry)); switch (test->type) { case ODICT_INT: TEST_EQUALS(test->u.i, odict_entry_int(entry)); break; case ODICT_BOOL: TEST_EQUALS(test->u.b, odict_entry_boolean(entry)); break; case ODICT_DOUBLE: TEST_EQUALS(test->u.d, odict_entry_dbl(entry)); break; case ODICT_STRING: TEST_STRCMP(test->u.s, str_len(test->u.s), odict_entry_str(entry), str_len(odict_entry_str(entry))); break; default: DEBUG_WARNING("cannot compare type %d\n", test->type); err = EINVAL; goto out; } out: return err; } int test_odict(void) { static const struct dtest testv[] = { {"a", ODICT_INT, {.i = 42 } }, {"b", ODICT_DOUBLE, {.d = 3.14} }, {"c", ODICT_STRING, {.s = "rattarei"} }, {"d", ODICT_BOOL, {.b = true } }, {"e", ODICT_BOOL, {.b = false } }, {"ha73ofhjausdhkjquhriasjdhkaksjdkjqkjhwkjhqweaaaaaa", ODICT_INT, {.i = 617284773 } }, {"mkkpnaidhs747fusyudyaisdiayiakakakaakajdjiwi123456", ODICT_INT, {.i = 12345678 } }, {"lllalsidjjlalksjdlaksjdlkjasd", ODICT_DOUBLE, {.d = 123456.123456} }, {"8adhw6fjshdkhjaskjdhakdhaskhdkjh", ODICT_DOUBLE, {.d = -0.007} }, {"hafgsdudue6dhcgfjgieiwuehsdkjhsdjkhasdkjasdkjhasd", ODICT_STRING, {.s = "kasjdsuher7yw783897njxdvkhjskhdkhsdkhjsdkhjasdasd"} }, {"liajsdoiausdoaudoaisudaoisdjalsijdalsidjalidjaslidj", ODICT_STRING, {.s = "bdjkhdiuasd7y78yw4y78ryuseyugfasygdasygdh"} }, }; struct odict *dict = NULL; char *debug_str = NULL; size_t i; int err; TEST_ASSERT(odict_type_iscontainer(ODICT_OBJECT)); TEST_ASSERT(odict_type_iscontainer(ODICT_ARRAY)); TEST_ASSERT(!odict_type_iscontainer(ODICT_INT)); TEST_ASSERT(odict_type_isreal(ODICT_INT)); TEST_ASSERT(odict_type_isreal(ODICT_DOUBLE)); TEST_ASSERT(odict_type_isreal(ODICT_STRING)); TEST_ASSERT(odict_type_isreal(ODICT_BOOL)); TEST_ASSERT(odict_type_isreal(ODICT_NULL)); TEST_ASSERT(!odict_type_isreal(ODICT_OBJECT)); err = odict_alloc(&dict, 64); if (err) goto out; for (i=0; ikey)); switch (test->type) { case ODICT_INT: err = odict_entry_add(dict, test->key, test->type, test->u.i); break; case ODICT_BOOL: err = odict_entry_add(dict, test->key, test->type, (int)test->u.b); break; case ODICT_DOUBLE: err = odict_entry_add(dict, test->key, test->type, test->u.d); break; case ODICT_STRING: err = odict_entry_add(dict, test->key, test->type, test->u.s); break; default: err = EINVAL; goto out; break; } if (err) goto out; /* verify that entry exist after adding */ entry = odict_lookup(dict, test->key); TEST_ASSERT(entry != NULL); err = compare(test, entry); TEST_ERR(err); } /* verify size of dictionary, after adding all entries */ TEST_EQUALS(RE_ARRAY_SIZE(testv), odict_count(dict, false)); /* compare dictionary with itself */ TEST_ASSERT(odict_compare(dict, dict, false)); err = re_sdprintf(&debug_str, "%H", odict_debug, dict); TEST_ERR(err); ASSERT_TRUE(str_isset(debug_str)); /* remove all entries */ for (i=0; ikey); /* entry should not exist anymore */ e = (struct odict_entry *)odict_lookup(dict, test->key); TEST_ASSERT(e == NULL); } /* verify size of dictionary, after removing all entries */ TEST_EQUALS(0, odict_count(dict, false)); out: mem_deref(debug_str); mem_deref(dict); return err; } int test_odict_pl(void) { struct odict *od = NULL; const struct odict_entry *e; int err; static struct pl pl=PL("liajsdoiausdoaudoaisudaoisdjal"); err = odict_alloc(&od, 64); if (err) goto out; /* add pl */ err = odict_pl_add(od, "pl1", &pl); TEST_ERR(err); e = odict_lookup(od, "pl1"); TEST_ASSERT(e != NULL); TEST_STRCMP(pl.p, pl.l, odict_entry_str(e), str_len(odict_entry_str(e))); mem_deref((struct odict_entry *) e); /* entry should not exist anymore */ e = (struct odict_entry *)odict_lookup(od, "pl1"); TEST_ASSERT(e == NULL); out: mem_deref(od); return err; } int test_odict_array(void) { struct odict *arr = NULL; const struct odict_entry *e; int err; err = odict_alloc(&arr, 64); if (err) goto out; /* test an empty array */ TEST_EQUALS(0, odict_count(arr, false)); TEST_ASSERT(NULL == odict_lookup(arr, "0")); TEST_ASSERT(NULL == odict_lookup(arr, "255")); /* add some elements of variying types */ err = odict_entry_add(arr, "0", ODICT_STRING, "hei"); if (err) goto out; TEST_EQUALS(1, odict_count(arr, false)); err = odict_entry_add(arr, "1", ODICT_INT, 1LL); err |= odict_entry_add(arr, "2", ODICT_INT, 2LL); err |= odict_entry_add(arr, "3", ODICT_INT, 3LL); if (err) goto out; TEST_EQUALS(4, odict_count(arr, false)); /* verify that elements are correct */ e = odict_lookup(arr, "0"); TEST_ASSERT(e != NULL); TEST_EQUALS(ODICT_STRING, odict_entry_type(e)); TEST_STRCMP("hei", (size_t)3, odict_entry_str(e), str_len(odict_entry_str(e))); e = odict_lookup(arr, "3"); TEST_ASSERT(e != NULL); TEST_EQUALS(ODICT_INT, odict_entry_type(e)); TEST_EQUALS(3LL, odict_entry_int(e)); uint64_t num = 0; bool ret = odict_get_number(arr, &num, "1"); ASSERT_TRUE(ret); ASSERT_EQ(1, num); ret = odict_get_number(arr, &num, "hei"); ASSERT_TRUE(!ret); #if 0 re_printf("%H\n", odict_debug, arr); #endif out: mem_deref(arr); return err; } ================================================ FILE: test/pcp.c ================================================ /** * @file pcp.c Port Control Protocol (PCP) Testcode * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include "test.h" #define DEBUG_MODULE "pcptest" #define DEBUG_LEVEL 5 #include static int test_pcp_request_loop(size_t offset) { struct mbuf *mb; struct pcp_msg *msg = NULL; static const uint8_t nonce[12] = { 0xc0, 0xff, 0xee, 0x00, 0xc0, 0xff, 0xee, 0x00, 0xc0, 0xff, 0xee, 0x00, }; size_t i; int err = 0; const uint16_t int_port = 2000; static const struct { enum pcp_opcode opcode; uint32_t lifetime; const char *ipaddr; } testreqv[] = { {PCP_MAP, 600, "10.0.0.20" }, {PCP_MAP, 3600, "2a02:fe0:cf12:91:226:8ff:fee1:cdf3" }, {PCP_PEER, 600, "46.123.65.9" }, {PCP_PEER, 999999, "2a02:fe0:cf12:91:226:8ff:fee1:cdf3" }, }; mb = mbuf_alloc(offset + 512); if (!mb) return ENOMEM; for (i=0; ipos = mb->end = offset; err = pcp_msg_req_encode(mb, testreqv[i].opcode, testreqv[i].lifetime, &sa, &peer, 0); if (err) break; TEST_ASSERT(mb->pos != offset); TEST_ASSERT(mb->end != offset); mb->pos = offset; err = pcp_msg_decode(&msg, mb); if (err) break; TEST_EQUALS(PCP_VERSION, msg->hdr.version); TEST_EQUALS(false, msg->hdr.resp); TEST_EQUALS(testreqv[i].opcode, msg->hdr.opcode); TEST_EQUALS(testreqv[i].lifetime, msg->hdr.lifetime); TEST_SACMP(&sa, &msg->hdr.cli_addr, SA_ADDR); switch (testreqv[i].opcode) { case PCP_MAP: case PCP_PEER: TEST_MEMCMP(nonce, sizeof(nonce), msg->pld.map.nonce, sizeof(msg->pld.map.nonce)); TEST_EQUALS(IPPROTO_UDP, msg->pld.map.proto); TEST_EQUALS(int_port, msg->pld.map.int_port); break; default: break; } TEST_EQUALS(0, list_count(&msg->optionl)); msg = mem_deref(msg); } out: mem_deref(msg); mem_deref(mb); return err; } static const uint8_t peer_request[] = { 0x02, 0x02, 0x00, 0x00, /* version | opcode */ 0x00, 0x00, 0x02, 0x58, /* lifetime */ 0x2a, 0x00, 0x14, 0x50, /* . */ 0x40, 0x0f, 0x08, 0x03, /* |client IP-address */ 0x00, 0x00, 0x00, 0x00, /* | */ 0x00, 0x00, 0x10, 0x00, /* ' */ 0xa9, 0x5f, 0xc9, 0xb7, /* */ 0x12, 0x3b, 0xa9, 0x66, /* nonce */ 0x33, 0xcd, 0xe2, 0xb9, /* */ 0x11, 0x00, 0x00, 0x00, /* protocol */ 0x13, 0x8c, 0x13, 0x8d, /* internal port | external port */ 0x2a, 0x00, 0x14, 0x50, /* . */ 0x40, 0x0f, 0x08, 0x03, /* |external IP Address */ 0x00, 0x00, 0x00, 0x00, /* | */ 0x00, 0x00, 0x20, 0x00, /* ' */ 0x13, 0x8e, 0x00, 0x00, /* remote peer port */ 0x2a, 0x00, 0x14, 0x50, /* */ 0x40, 0x0f, 0x08, 0x03, /* remote peer IP-address */ 0x00, 0x00, 0x00, 0x00, /* */ 0x00, 0x00, 0x30, 0x00, /* */ 0x01, 0x00, 0x00, 0x10, /* opcode THIRD_PARTY header */ 0x2a, 0x00, 0x14, 0x50, /* . */ 0x40, 0x0f, 0x08, 0x03, /* |internal IP address */ 0x00, 0x00, 0x00, 0x00, /* | */ 0x00, 0x00, 0x40, 0x00, /* ' */ }; static int test_pcp_message(void) { enum pcp_opcode opcode = PCP_PEER; uint32_t lifetime = 600; static const uint8_t nonce[] = { 0xa9, 0x5f, 0xc9, 0xb7, 0x12, 0x3b, 0xa9, 0x66, 0x33, 0xcd, 0xe2, 0xb9, }; int proto = IPPROTO_UDP; struct pcp_peer peer; struct sa int_addr, thi_addr; struct mbuf *mb; struct pcp_msg *msg = NULL; enum {DUMMY_OFFSET = 64}; int err = 0; memcpy(peer.map.nonce, nonce, sizeof(peer.map.nonce)); peer.map.proto = IPPROTO_UDP; peer.map.int_port = 5004; err |= sa_set_str(&int_addr, "2a00:1450:400f:803::1000", 0); err |= sa_set_str(&peer.map.ext_addr, "2a00:1450:400f:803::2000", 5005); err |= sa_set_str(&peer.remote_addr, "2a00:1450:400f:803::3000", 5006); err |= sa_set_str(&thi_addr, "2a00:1450:400f:803::4000", 0); if (err) return err; mb = mbuf_alloc(DUMMY_OFFSET + 512); if (!mb) return ENOMEM; mb->pos = DUMMY_OFFSET; err = pcp_msg_req_encode(mb, opcode, lifetime, &int_addr, &peer, 1, PCP_OPTION_THIRD_PARTY, &thi_addr); if (err) goto out; TEST_ASSERT(mb->pos != DUMMY_OFFSET); TEST_ASSERT(mb->pos == mb->end); mb->pos = DUMMY_OFFSET; TEST_MEMCMP(peer_request, sizeof(peer_request), mbuf_buf(mb), mbuf_get_left(mb)); err = pcp_msg_decode(&msg, mb); if (err) goto out; TEST_EQUALS(PCP_VERSION, msg->hdr.version); TEST_EQUALS(false, msg->hdr.resp); TEST_EQUALS(opcode, msg->hdr.opcode); TEST_EQUALS(lifetime, msg->hdr.lifetime); TEST_SACMP(&int_addr, &msg->hdr.cli_addr, SA_ALL); TEST_MEMCMP(nonce, sizeof(nonce), msg->pld.peer.map.nonce, sizeof(msg->pld.peer.map.nonce)); TEST_EQUALS(proto, msg->pld.peer.map.proto); TEST_EQUALS(5004, msg->pld.peer.map.int_port); TEST_SACMP(&peer.map.ext_addr, &msg->pld.peer.map.ext_addr, SA_ALL); TEST_SACMP(&peer.remote_addr, &msg->pld.peer.remote_addr, SA_ALL); out: mem_deref(mb); mem_deref(msg); return err; } static int test_pcp_option(void) { struct mbuf *mb; struct pcp_option *opt = NULL; struct sa addr; static const uint8_t opt_pkt[] = { 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x01 }; int err; mb = mbuf_alloc(64); if (!mb) return ENOMEM; err = sa_set_str(&addr, "10.0.0.1", 0); if (err) goto out; err = pcp_option_encode(mb, PCP_OPTION_THIRD_PARTY, &addr); if (err) goto out; TEST_MEMCMP(opt_pkt, sizeof(opt_pkt), mb->buf, mb->end); mb->pos = 0; err = pcp_option_decode(&opt, mb); TEST_EQUALS(PCP_OPTION_THIRD_PARTY, opt->code); TEST_SACMP(&addr, &opt->u.third_party, SA_ADDR); out: mem_deref(opt); mem_deref(mb); return err; } int test_pcp(void) { int err; err = test_pcp_request_loop(0); TEST_ERR(err); err = test_pcp_request_loop(4); TEST_ERR(err); err = test_pcp_message(); TEST_ERR(err); err = test_pcp_option(); TEST_ERR(err); out: return err; } ================================================ FILE: test/remain.c ================================================ /** * @file remain.c Testcode for re main loop * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "remain" #define DEBUG_LEVEL 5 #include struct data { thrd_t tid; mtx_t *mutex; bool thread_started; bool thread_exited; unsigned tmr_called; int err; }; static void tmr_handler(void *arg) { struct data *data = arg; int err = 0; mtx_lock(data->mutex); /* verify that timer is called from the new thread */ TEST_ASSERT(0 != thrd_equal(data->tid, thrd_current())); ++data->tmr_called; out: if (err) data->err = err; mtx_unlock(data->mutex); /* Stop re_main loop */ re_cancel(); } static int thread_handler(void *arg) { struct data *data = arg; struct tmr tmr; int err; data->thread_started = true; tmr_init(&tmr); /* Add a worker thread for this thread */ err = re_thread_init(); if (err) { DEBUG_WARNING("re thread init: %m\n", err); data->err = err; return err; } #ifndef WIN32 err = fd_setsize(-1); TEST_ERR(err); #endif err = re_thread_init(); ASSERT_TRUE(err == 0 || err == EALREADY); tmr_start(&tmr, 1, tmr_handler, data); /* run the main loop now */ err = re_main(NULL); out: if (err) data->err = err; tmr_cancel(&tmr); tmr_debug(); /* Remove the worker thread for this thread */ re_thread_close(); data->thread_exited = true; return err; } static int test_remain_thread(void) { struct data data = { 0 }; int err; err = mutex_alloc(&data.mutex); if (err) return err; err = thread_create_name(&data.tid, "remain", thread_handler, &data); TEST_ERR(err); /* wait for timer to be called */ for (size_t i=0; i<500; i++) { mtx_lock(data.mutex); if (data.tmr_called || data.err) { mtx_unlock(data.mutex); break; } mtx_unlock(data.mutex); sys_msleep(1); } data.mutex = mem_deref(data.mutex); /* wait for thread to end */ thrd_join(data.tid, NULL); if (data.err) return data.err; TEST_ASSERT(data.thread_started); TEST_ASSERT(data.thread_exited); TEST_EQUALS(1, data.tmr_called); TEST_EQUALS(0, data.err); out: mem_deref(data.mutex); return err; } int test_remain(void) { int err = 0; err = test_remain_thread(); return err; } ================================================ FILE: test/rtcp.c ================================================ /** * @file rtcp.c Tests for RTCP * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "rtcp_test" #define DEBUG_LEVEL 5 #include /* * .------. RTP * | RTP | <--------------- [ Packet Generator ] * | RTCP | * '------' */ static const uint32_t GENERATOR_SSRC = 0x00000001; struct fixture { struct rtp_sock *rtp; struct sa rtp_addr; size_t n_recv; size_t num_packets; }; static int send_rtp_packet(struct udp_sock *us, const struct sa *dst, uint16_t seq, uint32_t ssrc) { struct rtp_header hdr; struct mbuf *mb = mbuf_alloc(256); int err; if (!mb) return ENOMEM; memset(&hdr, 0, sizeof(hdr)); hdr.ver = RTP_VERSION; hdr.seq = seq; hdr.ts = 0; hdr.ssrc = ssrc; err = rtp_hdr_encode(mb, &hdr); if (err) goto out; mbuf_fill(mb, 160, 0x00); mb->pos = 0; err = udp_send(us, dst, mb); if (err) goto out; out: mem_deref(mb); return err; } static void rtp_recv(const struct sa *src, const struct rtp_header *hdr, struct mbuf *mb, void *arg) { struct fixture *f = arg; (void)src; (void)hdr; (void)mb; ++f->n_recv; if (f->n_recv >= f->num_packets) { re_cancel(); } } static int fixture_init(struct fixture *f) { int err; memset(f, 0, sizeof(*f)); err = sa_set_str(&f->rtp_addr, "127.0.0.1", 0); TEST_ERR(err); err = rtp_listen(&f->rtp, IPPROTO_UDP, &f->rtp_addr, 10000, 49152, true, rtp_recv, NULL, f); if (err) goto out; err = udp_local_get(rtp_sock(f->rtp), &f->rtp_addr); TEST_ERR(err); out: return err; } static void fixture_close(struct fixture *f) { mem_deref(f->rtp); } static int test_loss(const uint16_t *seqv, size_t seqc, int exp_lost) { struct fixture fix, *f = &fix; struct rtcp_stats stats; uint32_t ssrc = GENERATOR_SSRC; unsigned i; int err, e; err = fixture_init(f); if (err) goto out; f->num_packets = seqc; /* no sources exist yet */ e = rtcp_stats(f->rtp, ssrc, &stats); TEST_EQUALS(ENOENT, e); /* start the RTP packet generator, send X number of RTP packets * to the RTP-stack (fixture) */ for (i=0; irtp), &f->rtp_addr, seq, ssrc); if (err) goto out; } err = re_main_timeout(200); TEST_ERR(err); err = rtcp_stats(f->rtp, ssrc, &stats); if (err) { if (err == ENOENT) err = ENOMEM; TEST_ERR(err); } /* in OOM-test, detect if member/sender was not allocated */ if (stats.rx.sent == 0 && stats.rx.lost == 0 && stats.rx.jit == 0) { err = ENOMEM; TEST_ERR(err); } if (test_mode == TEST_MEMORY) goto out; /* verify expected packets sent and packet loss */ TEST_EQUALS(seqc, stats.rx.sent); TEST_EQUALS(exp_lost, stats.rx.lost); TEST_EQUALS(seqc, f->n_recv); out: fixture_close(f); return err; } int test_rtcp_packetloss(void) { static const uint16_t seqv1[] = {0, 1, 2}; static const uint16_t seqv2[] = {0,1,3,2,5,4}; static const uint16_t seqv3[] = {65534, 65535, 0, 1}; static const uint16_t seqv4[] = {65534, 0, 1}; static const uint16_t seqv5[] = {65534, 1, 2}; static const uint16_t seqv6[] = {65534, 1, 2, 65535}; static const uint16_t seqv7[] = {1,2,8,9,10}; int err = 0; err = test_loss(seqv1, RE_ARRAY_SIZE(seqv1), 0); TEST_ERR(err); err = test_loss(seqv2, RE_ARRAY_SIZE(seqv2), 0); TEST_ERR(err); err = test_loss(seqv3, RE_ARRAY_SIZE(seqv3), 0); TEST_ERR(err); err = test_loss(seqv4, RE_ARRAY_SIZE(seqv4), 1); TEST_ERR(err); err = test_loss(seqv5, RE_ARRAY_SIZE(seqv5), 2); TEST_ERR(err); err = test_loss(seqv6, RE_ARRAY_SIZE(seqv6), 1); TEST_ERR(err); err = test_loss(seqv7, RE_ARRAY_SIZE(seqv7), 5); TEST_ERR(err); out: return err; } struct agent { struct agent *peer; struct rtp_sock *rtp_sock; struct sa laddr_rtp; struct sa laddr_rtcp; unsigned step; unsigned rtp_count; unsigned psfb_count; unsigned rtpfb_count; unsigned gnack_count; unsigned app_count; int err; }; static bool agents_are_complete(const struct agent *ag) { return ag->app_count && ag->peer->app_count; } static int agent_send_rtcp_packet(struct agent *ag) { struct mbuf *mb_chunks = NULL; struct mbuf *mb_deltas = NULL; const uint8_t fir_seqn = 22; int err = 0; switch (ag->step) { case 0: err = rtcp_send_fir_rfc5104(ag->rtp_sock, rtp_sess_ssrc(ag->peer->rtp_sock), fir_seqn); break; case 1: err = rtcp_send_gnack(ag->rtp_sock, rtp_sess_ssrc(ag->peer->rtp_sock), 42, 0); break; case 2: err = rtcp_send_pli(ag->rtp_sock, rtp_sess_ssrc(ag->peer->rtp_sock)); break; case 3: { mb_chunks = mbuf_alloc(32); mb_deltas = mbuf_alloc(32); if (!mb_chunks || !mb_deltas) { err = ENOMEM; goto out; } static const uint8_t chunks[] = { 0xad, 0xe0 }; static const uint8_t deltas[] = { 0x14, 0x18, 0x18, 0x38, 0x00, 0x00, 0x00 }; mbuf_write_mem(mb_chunks, chunks, sizeof(chunks)); mbuf_write_mem(mb_deltas, deltas, sizeof(deltas)); mbuf_set_pos(mb_chunks, 0); mbuf_set_pos(mb_deltas, 0); struct twcc twcc = { .seq = 11, .count = 22, .reftime = 33, .fbcount = 44, .chunks = mb_chunks, .deltas = mb_deltas, }; err = rtcp_send_twcc(ag->rtp_sock, rtp_sess_ssrc(ag->peer->rtp_sock), &twcc); } break; case 4: /* NOTE: must be last */ err = rtcp_send_app(ag->rtp_sock, "PING", (void *)"PONG", 4); break; default: DEBUG_NOTICE("agent_send_rtcp_packet: invalid step (%u)\n", ag->step); break; } ++ag->step; out: mem_deref(mb_chunks); mem_deref(mb_deltas); return err; } static void rtp_recv_handler(const struct sa *src, const struct rtp_header *hdr, struct mbuf *mb, void *arg) { struct agent *ag = arg; (void)src; (void)hdr; (void)mb; ++ag->rtp_count; if (ag->step == 0) { int err = agent_send_rtcp_packet(ag); if (err) { ag->err = err; re_cancel(); } } } static void rtcp_recv_handler(const struct sa *src, struct rtcp_msg *msg, void *arg) { struct agent *ag = arg; int err = 0; (void)src; switch (msg->hdr.pt) { case RTCP_RTPFB: if (msg->r.fb.fci.gnackv->pid == 42) ++ag->gnack_count; ++ag->rtpfb_count; err = agent_send_rtcp_packet(ag); break; case RTCP_PSFB: ++ag->psfb_count; err = agent_send_rtcp_packet(ag); break; case RTCP_APP: ++ag->app_count; break; case RTCP_SR: case RTCP_SDES: /* ignore */ break; default: DEBUG_WARNING("unexpected RTCP message: %H\n", rtcp_msg_print, msg); err = EPROTO; break; } if (agents_are_complete(ag)) { re_cancel(); return; } if (err) { ag->err = err; re_cancel(); } } static int agent_init(struct agent *ag, bool mux, const char *laddr_str) { struct sa laddr; sa_set_str(&laddr, laddr_str, 0); int err = rtp_listen(&ag->rtp_sock, IPPROTO_UDP, &laddr, 1024, 65535, true, rtp_recv_handler, rtcp_recv_handler, ag); if (err) return err; rtcp_enable_mux(ag->rtp_sock, mux); udp_local_get(rtp_sock(ag->rtp_sock), &ag->laddr_rtp); udp_local_get(rtcp_sock(ag->rtp_sock), &ag->laddr_rtcp); return 0; } static int test_rtcp_loop_param(bool mux, const char *laddr) { struct agent a = {0}, b = {0}; struct mbuf *mb = NULL; int err; err = agent_init(&a, mux, laddr); TEST_ERR(err); err = agent_init(&b, mux, laddr); TEST_ERR(err); a.peer = &b; b.peer = &a; rtcp_start(a.rtp_sock, "cname", &b.laddr_rtcp); rtcp_start(b.rtp_sock, "cname", &a.laddr_rtcp); mb = mbuf_alloc(RTP_HEADER_SIZE + 1); if (!mb) { err = ENOMEM; goto out; } mbuf_fill(mb, 0x00, RTP_HEADER_SIZE + 1); /* Send some RTP-packets to enable RTCP-SR */ for (unsigned i=0; i<4; i++) { uint64_t jfs = tmr_jiffies_rt_usec(); mb->pos = RTP_HEADER_SIZE; err = rtp_send(a.rtp_sock, &b.laddr_rtp, false, false, 0, 160, jfs, mb); TEST_ERR(err); mb->pos = RTP_HEADER_SIZE; err = rtp_send(b.rtp_sock, &a.laddr_rtp, false, false, 0, 160, jfs, mb); TEST_ERR(err); } err = re_main_timeout(1000); TEST_ERR(err); err = a.err; TEST_ERR(err); err = b.err; TEST_ERR(err); ASSERT_TRUE(a.rtp_count >= 1); ASSERT_EQ(2, a.psfb_count); ASSERT_EQ(1, a.app_count); ASSERT_TRUE(b.rtp_count >= 1); ASSERT_EQ(2, b.psfb_count); ASSERT_EQ(2, b.rtpfb_count); ASSERT_EQ(1, b.gnack_count); ASSERT_EQ(1, b.app_count); out: mem_deref(b.rtp_sock); mem_deref(a.rtp_sock); mem_deref(mb); return err; } int test_rtcp_loop(void) { int err; err = test_rtcp_loop_param(false, "127.0.0.1"); TEST_ERR(err); err = test_rtcp_loop_param(true, "127.0.0.1"); TEST_ERR(err); if (test_ipv6_supported()) { err = test_rtcp_loop_param(false, "::1"); TEST_ERR(err); err = test_rtcp_loop_param(true, "::1"); TEST_ERR(err); } out: return err; } static int rrtr_encode_handler(struct mbuf *mb, void *arg) { int err = 0; (void)arg; err |= mbuf_write_u8(mb, RTCP_XR_RRTR); err |= mbuf_write_u8(mb, 0); /* reserved */ err |= mbuf_write_u16(mb, htons(2)); err |= mbuf_write_u32(mb, htonl(0x01020304)); err |= mbuf_write_u32(mb, htonl(0x05060708)); return err; } static int test_rtcp_xr_rrtr(void) { const uint8_t packet[] = { /* header */ 0x80, 0xcf, 0x00, 0x04, /* RTCP-XR */ 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; struct mbuf *buf = mbuf_alloc(sizeof(packet)); if (!buf) return ENOMEM; struct rtcp_msg *msg = NULL; const uint32_t ssrc = 1; int err = 0; err = rtcp_encode(buf, RTCP_XR, 0, ssrc, rrtr_encode_handler, NULL); TEST_ERR(err); mbuf_set_pos(buf, 0); TEST_MEMCMP(packet, sizeof(packet), mbuf_buf(buf), mbuf_get_left(buf)); err = rtcp_decode(&msg, buf); TEST_ERR(err); ASSERT_EQ(RTCP_XR, msg->hdr.pt); ASSERT_EQ(4, msg->hdr.length); ASSERT_EQ(ssrc, msg->r.xr.ssrc); ASSERT_EQ(RTCP_XR_RRTR, msg->r.xr.bt); ASSERT_EQ(2, msg->r.xr.block_len); ASSERT_EQ(0x01020304, msg->r.xr.rb.rrtrb.ntp_msw); ASSERT_EQ(0x05060708, msg->r.xr.rb.rrtrb.ntp_lsw); out: mem_deref(buf); mem_deref(msg); return err; } static int dlrr_encode_handler(struct mbuf *mb, void *arg) { const uint16_t block_length = 3; const uint32_t ssrc = 0xa534b254; const uint32_t lrr = 0x03040506; const uint32_t dlrr = 0x00008376; int err; (void)arg; err = mbuf_write_u8(mb, RTCP_XR_DLRR); err |= mbuf_write_u8(mb, 0); /* reserved */ err |= mbuf_write_u16(mb, htons(block_length)); err |= mbuf_write_u32(mb, htonl(ssrc)); err |= mbuf_write_u32(mb, htonl(lrr)); err |= mbuf_write_u32(mb, htonl(dlrr)); return err; } static int test_rtcp_xr_dlrr(void) { /* RTCP-XR packet from Chrome 126 */ static const uint8_t packet[] = { /* header */ 0x80, 0xcf, 0x00, 0x05, /* RTCP-XR */ 0x62, 0x8e, 0x09, 0xbd, 0x05, 0x00, 0x00, 0x03, 0xa5, 0x34, 0xb2, 0x54, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 0x83, 0x76, }; struct mbuf *mb = mbuf_alloc(sizeof(packet)); if (!mb) return ENOMEM; struct rtcp_msg *msg = NULL; const uint32_t ssrc = 0x628e09bd; int err = rtcp_encode(mb, RTCP_XR, 0, ssrc, dlrr_encode_handler, NULL); TEST_ERR(err); mbuf_set_pos(mb, 0); TEST_MEMCMP(packet, sizeof(packet), mbuf_buf(mb), mbuf_get_left(mb)); err = rtcp_decode(&msg, mb); TEST_ERR(err); ASSERT_EQ(RTCP_XR, msg->hdr.pt); ASSERT_EQ(ssrc, msg->r.xr.ssrc); ASSERT_EQ(RTCP_XR_DLRR, msg->r.xr.bt); ASSERT_EQ(3, msg->r.xr.block_len); ASSERT_EQ(0xa534b254, msg->r.xr.rb.dlrrb.ssrc); ASSERT_EQ(0x03040506, msg->r.xr.rb.dlrrb.lrr); ASSERT_EQ(0x00008376, msg->r.xr.rb.dlrrb.dlrr); out: mem_deref(msg); mem_deref(mb); return err; } int test_rtcp_xr(void) { int err; err = test_rtcp_xr_dlrr(); TEST_ERR(err); err = test_rtcp_xr_rrtr(); TEST_ERR(err); out: return err; } ================================================ FILE: test/rtmp.c ================================================ /** * @file rtmp.c RTMP Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "rtmp" #define DEBUG_LEVEL 5 #include #define NUM_MEDIA_PACKETS 4 /* Force testing of Extended Timestamp */ #define TS_OFFSET 0x00fffffe #define DUMMY_STREAM_ID 42 #define CHUNK_SIZE 4096 enum mode { MODE_PLAY, MODE_PUBLISH, }; struct rtmp_endpoint { struct rtmp_endpoint *other; struct rtmp_conn *conn; struct rtmp_stream *stream; struct tcp_sock *ts; /* server only */ struct tls *tls; const char *tag; enum mode mode; uint32_t stream_id; bool is_client; unsigned n_estab; unsigned n_cmd; unsigned n_stream_cmd; unsigned n_close; unsigned n_ready; unsigned n_play; unsigned n_publish; unsigned n_publish_start; unsigned n_deletestream; unsigned n_audio; unsigned n_video; unsigned n_data; int err; }; static const uint8_t fake_audio_packet[CHUNK_SIZE + 6] = { 0x5b, 0xb2, 0xfb, 0x11, 0x46, 0xe9 }; static const uint8_t fake_video_packet[CHUNK_SIZE + 8] = { 0xcb, 0x9c, 0xb5, 0x60, 0x7f, 0xe9, 0xbd, 0xe1 }; static const char *fake_stream_name = "sample.mp4"; static const char *fake_app_inst = "vod"; static void endpoint_terminate(struct rtmp_endpoint *ep, int err) { if (err) { DEBUG_INFO("[ %s ] terminate: %m\n", ep->tag, err); } ep->err = err; re_cancel(); } /* criteria for test to be finished */ static bool client_is_finished(const struct rtmp_endpoint *ep) { switch (ep->mode) { case MODE_PLAY: return ep->n_ready > 0 && ep->n_audio >= NUM_MEDIA_PACKETS && ep->n_video >= NUM_MEDIA_PACKETS; case MODE_PUBLISH: return ep->n_ready > 0 && ep->n_publish_start > 0; } return false; } static bool server_is_finished(const struct rtmp_endpoint *ep) { switch (ep->mode) { case MODE_PLAY: return ep->n_play > 0; case MODE_PUBLISH: return ep->n_publish > 0 && ep->n_audio >= NUM_MEDIA_PACKETS && ep->n_video >= NUM_MEDIA_PACKETS; } return false; } static bool endpoints_are_finished(const struct rtmp_endpoint *ep) { if (ep->is_client) { return client_is_finished(ep) && server_is_finished(ep->other); } else { return client_is_finished(ep->other) && server_is_finished(ep); } } static int send_media(struct rtmp_endpoint *ep_cli) { unsigned i; int err = 0; /* Send some dummy media packets to server */ for (i=0; istream, i, fake_audio_packet, sizeof(fake_audio_packet)); if (err) return err; err = rtmp_send_video(ep_cli->stream, TS_OFFSET + i, fake_video_packet, sizeof(fake_video_packet)); if (err) return err; } return 0; } static void stream_command_handler(const struct odict *msg, void *arg) { struct rtmp_endpoint *ep = arg; const char *name; int err = 0; name = odict_string(msg, "0"); DEBUG_INFO("[%s] stream command: %s\n", ep->tag, name); TEST_EQUALS(DUMMY_STREAM_ID, ep->stream_id); ++ep->n_stream_cmd; if (0 == str_casecmp(name, "play")) { const char *stream_name; uint64_t tid; uint32_t i; ++ep->n_play; if (!odict_get_number(msg, &tid, "1")) { err = EPROTO; goto out; } TEST_EQUALS(0, tid); stream_name = odict_string(msg, "3"); TEST_STRCMP(fake_stream_name, strlen(fake_stream_name), stream_name, str_len(stream_name)); err = rtmp_amf_data(ep->conn, DUMMY_STREAM_ID, "|RtmpSampleAccess", 2, RTMP_AMF_TYPE_BOOLEAN, false, RTMP_AMF_TYPE_BOOLEAN, false); if (err) goto out; /* Send some dummy media packets to client */ for (i=0; istream, i, fake_audio_packet, sizeof(fake_audio_packet)); if (err) goto out; err = rtmp_send_video(ep->stream, TS_OFFSET + i, fake_video_packet, sizeof(fake_video_packet)); if (err) goto out; } } else if (0 == str_casecmp(name, "publish")) { const char *stream_name; const char *code = "NetStream.Publish.Start"; uint64_t tid; ++ep->n_publish; if (!odict_get_number(msg, &tid, "1")) { err = EPROTO; goto out; } TEST_EQUALS(0, tid); stream_name = odict_string(msg, "3"); TEST_STRCMP(fake_stream_name, strlen(fake_stream_name), stream_name, str_len(stream_name)); err = rtmp_amf_command(ep->conn, ep->stream_id, "onStatus", 3, RTMP_AMF_TYPE_NUMBER, (double)0, RTMP_AMF_TYPE_NULL, RTMP_AMF_TYPE_OBJECT, 2, RTMP_AMF_TYPE_STRING, "level", "status", RTMP_AMF_TYPE_STRING, "code", code); if (err) goto out; } else if (0 == str_casecmp(name, "onStatus")) { struct odict *obj; const char *level; obj = odict_get_object(msg, "3"); level = odict_string(obj, "level"); if (0 == str_casecmp(level, "status")) { const char *code = odict_string(obj, "code"); const char *exp_code = "NetStream.Publish.Start"; ++ep->n_publish_start; TEST_STRCMP(exp_code, str_len(exp_code), code, str_len(code)); err = rtmp_meta(ep->stream); if (err) goto out; err = send_media(ep); if (err) goto out; } else { DEBUG_WARNING("unsupported level %s\n", level); } } else { DEBUG_NOTICE("[ %s ] stream: command not handled (%s)\n", ep->tag, name); err = ENOTSUP; goto out; } out: if (err) endpoint_terminate(ep, err); } static void test_done(struct rtmp_endpoint *ep) { struct rtmp_endpoint *client; if (ep->is_client) client = ep; else client = ep->other; /* Force destruction here to test robustness */ client->stream = mem_deref(client->stream); } static void stream_control_handler(enum rtmp_event_type event, struct mbuf *mb, void *arg) { struct rtmp_endpoint *ep = arg; int err = 0; (void)mb; TEST_EQUALS(DUMMY_STREAM_ID, ep->stream_id); DEBUG_INFO("[ %s ] got control event: event=%d (%s)\n", ep->tag, event, rtmp_event_name(event)); switch (event) { default: break; } out: if (err) endpoint_terminate(ep, err); } static void audio_handler(uint32_t timestamp, const uint8_t *pld, size_t len, void *arg) { struct rtmp_endpoint *ep = arg; int err = 0; TEST_EQUALS(DUMMY_STREAM_ID, ep->stream_id); TEST_EQUALS(ep->n_audio, timestamp); ++ep->n_audio; TEST_MEMCMP(fake_audio_packet, sizeof(fake_audio_packet), pld, len); /* Test complete ? */ if (endpoints_are_finished(ep)) { test_done(ep); return; } out: if (err) endpoint_terminate(ep, err); } static void video_handler(uint32_t timestamp, const uint8_t *pld, size_t len, void *arg) { struct rtmp_endpoint *ep = arg; int err = 0; TEST_EQUALS(DUMMY_STREAM_ID, ep->stream_id); TEST_EQUALS(TS_OFFSET + ep->n_video, timestamp); ++ep->n_video; TEST_MEMCMP(fake_video_packet, sizeof(fake_video_packet), pld, len); /* Test complete ? */ if (endpoints_are_finished(ep)) { test_done(ep); return; } out: if (err) endpoint_terminate(ep, err); } static void stream_data_handler(const struct odict *msg, void *arg) { struct rtmp_endpoint *ep = arg; const char *command; bool ret; bool value; int err = 0; TEST_EQUALS(DUMMY_STREAM_ID, ep->stream_id); ++ep->n_data; command = odict_string(msg, "0"); if (ep->is_client) { TEST_STRCMP("|RtmpSampleAccess", 17, command, str_len(command)); ret = odict_get_boolean(msg, &value, "1"); TEST_ASSERT(ret); TEST_ASSERT(!value); ret = odict_get_boolean(msg, &value, "2"); TEST_ASSERT(ret); TEST_ASSERT(!value); } else { const struct odict_entry *e; uint64_t num; TEST_STRCMP("@setDataFrame", 13, command, str_len(command)); e = odict_get_type(msg, ODICT_OBJECT, "2"); TEST_ASSERT(e != NULL); ret = odict_get_number(odict_entry_object(e), &num, "audiocodecid"); TEST_ASSERT(ret); TEST_EQUALS(10ULL, num); ret = odict_get_number(odict_entry_object(e), &num, "videocodecid"); TEST_ASSERT(ret); TEST_EQUALS(7ULL, num); } out: if (err) endpoint_terminate(ep, err); } static void stream_create_resp_handler(bool success, const struct odict *msg, void *arg) { struct rtmp_endpoint *ep = arg; uint64_t stream_id; int err; TEST_ASSERT(success); ++ep->n_ready; /* the stream-id was assigned by the server */ if (!odict_get_number(msg, &stream_id, "3")) { err = EPROTO; goto out; } ep->stream_id = (uint32_t)stream_id; switch (ep->mode) { case MODE_PLAY: err = rtmp_play(ep->stream, fake_stream_name); if (err) goto out; break; case MODE_PUBLISH: err = rtmp_publish(ep->stream, fake_stream_name); if (err) goto out; break; default: err = EPROTO; goto out; } out: if (err) endpoint_terminate(ep, err); } static void estab_handler(void *arg) { struct rtmp_endpoint *ep = arg; int err = 0; DEBUG_INFO("[%s] Established\n", ep->tag); ++ep->n_estab; if (ep->is_client) { TEST_ASSERT(ep->stream == NULL); err = rtmp_stream_create(&ep->stream, ep->conn, stream_create_resp_handler, stream_command_handler, stream_control_handler, audio_handler, video_handler, stream_data_handler, ep); if (err) goto out; } out: if (err) endpoint_terminate(ep, err); } /* Server */ static int server_send_reply(struct rtmp_conn *conn, const struct odict *req) { const char *code = "NetConnection.Connect.Success"; const char *descr = "Connection succeeded."; int err; err = rtmp_amf_reply(conn, 0, true, req, 2, RTMP_AMF_TYPE_OBJECT, 3, RTMP_AMF_TYPE_STRING, "fmsVer", "FMS/3,5,7,7009", RTMP_AMF_TYPE_NUMBER, "capabilities", 31.0, RTMP_AMF_TYPE_NUMBER, "mode", 1.0, RTMP_AMF_TYPE_OBJECT, 6, RTMP_AMF_TYPE_STRING, "level", "status", RTMP_AMF_TYPE_STRING, "code", code, RTMP_AMF_TYPE_STRING, "description", descr, RTMP_AMF_TYPE_ECMA_ARRAY, "data", 1, RTMP_AMF_TYPE_STRING, "version", "3,5,7,7009", RTMP_AMF_TYPE_NUMBER, "clientid", 734806661.0, RTMP_AMF_TYPE_NUMBER, "objectEncoding", 0.0); return err; } static void command_handler(const struct odict *msg, void *arg) { struct rtmp_endpoint *ep = arg; const char *name; int err = 0; TEST_ASSERT(!ep->is_client); name = odict_string(msg, "0"); ++ep->n_cmd; if (0 == str_casecmp(name, "connect")) { const struct odict_entry *entry; uint32_t window_ack_size = 32000; const char *app; entry = odict_lookup(msg, "2"); TEST_ASSERT(entry != NULL); app = odict_string(odict_entry_object(entry), "app"); TEST_STRCMP(fake_app_inst, strlen(fake_app_inst), app, str_len(app)); err = rtmp_control(ep->conn, RTMP_TYPE_WINDOW_ACK_SIZE, (uint32_t)window_ack_size); if (err) goto out; err = server_send_reply(ep->conn, msg); if (err) { re_printf("rtmp: reply failed (%m)\n", err); goto out; } } else if (0 == str_casecmp(name, "createStream")) { uint32_t stream_id = DUMMY_STREAM_ID; TEST_ASSERT(ep->stream == NULL); ep->stream_id = stream_id; err = rtmp_stream_alloc(&ep->stream, ep->conn, stream_id, stream_command_handler, stream_control_handler, audio_handler, video_handler, stream_data_handler, ep); if (err) { goto out; } err = rtmp_amf_reply(ep->conn, 0, true, msg, 2, RTMP_AMF_TYPE_NULL, RTMP_AMF_TYPE_NUMBER, (double)stream_id); if (err) { re_printf("rtmp: reply failed (%m)\n", err); goto out; } } else if (0 == str_casecmp(name, "deleteStream")) { uint64_t stream_id; ++ep->n_deletestream; if (!odict_get_number(msg, &stream_id, "3")) { err = EPROTO; goto out; } TEST_EQUALS(DUMMY_STREAM_ID, stream_id); if (stream_id == ep->stream_id) ep->stream = mem_deref(ep->stream); /* re_main will be stopped when the * TCP connection is closed */ endpoint_terminate(ep, 0); } else { DEBUG_NOTICE("[ %s ] command not handled (%s)\n", ep->tag, name); err = ENOTSUP; goto out; } out: if (err) endpoint_terminate(ep, err); } static void close_handler(int err, void *arg) { struct rtmp_endpoint *ep = arg; if (err) { DEBUG_INFO("[ %s ] rtmp connection closed (%m)\n", ep->tag, err); } ++ep->n_close; endpoint_terminate(ep, err); } static void endpoint_destructor(void *data) { struct rtmp_endpoint *ep = data; mem_deref(ep->stream); mem_deref(ep->conn); mem_deref(ep->tls); mem_deref(ep->ts); } static struct rtmp_endpoint *rtmp_endpoint_alloc(enum mode mode, bool is_client, bool secure) { struct rtmp_endpoint *ep; int err = 0; ep = mem_zalloc(sizeof(*ep), endpoint_destructor); if (!ep) return NULL; ep->is_client = is_client; ep->mode = mode; if (secure) { #ifdef USE_TLS char path[256]; re_snprintf(path, sizeof(path), "%s/server-ecdsa.pem", test_datapath()); err = tls_alloc(&ep->tls, TLS_METHOD_SSLV23, is_client ? NULL : path, NULL); if (err) goto out; /* Client: Add the server's certificate as a CA cert. * This is required for authentication to work. */ if (is_client) { err = tls_add_ca(ep->tls, path); if (err) goto out; } #else err = ENOSYS; goto out; #endif } ep->tag = is_client ? "Client" : "Server"; out: if (err) return mem_deref(ep); return ep; } static void tcp_conn_handler(const struct sa *peer, void *arg) { struct rtmp_endpoint *ep = arg; int err; (void)peer; err = rtmp_accept(&ep->conn, ep->ts, ep->tls, command_handler, close_handler, ep); if (err) goto out; out: if (err) endpoint_terminate(ep, err); } static int test_rtmp_client_server_conn(enum mode mode, bool secure) { struct rtmp_endpoint *cli, *srv; struct sa srv_addr; char uri[256]; int err = 0; cli = rtmp_endpoint_alloc(mode, true, secure); srv = rtmp_endpoint_alloc(mode, false, secure); if (!cli || !srv) { err = ENOMEM; goto out; } cli->other = srv; srv->other = cli; err = sa_set_str(&srv_addr, "127.0.0.1", 0); TEST_ERR(err); err = tcp_listen(&srv->ts, &srv_addr, tcp_conn_handler, srv); if (err) goto out; err = tcp_local_get(srv->ts, &srv_addr); TEST_ERR(err); re_snprintf(uri, sizeof(uri), "rtmp%s://%J/%s/foo", secure ? "s" : "", &srv_addr, fake_app_inst); err = rtmp_connect(&cli->conn, NULL, uri, cli->tls, estab_handler, command_handler, close_handler, cli); if (err) goto out; err = re_main_timeout(1000); if (err) goto out; if (cli->err) { err = cli->err; goto out; } if (srv->err) { err = srv->err; goto out; } TEST_EQUALS(1, cli->n_estab); TEST_EQUALS(0, srv->n_estab); TEST_EQUALS(0, cli->n_cmd); TEST_EQUALS(3, srv->n_cmd); TEST_EQUALS(1, srv->n_stream_cmd); TEST_EQUALS(0, cli->n_close); TEST_EQUALS(1, cli->n_ready); TEST_EQUALS(0, srv->n_ready); TEST_EQUALS(0, cli->n_deletestream); TEST_EQUALS(1, srv->n_deletestream); switch (mode) { case MODE_PLAY: TEST_EQUALS(1, srv->n_play); TEST_EQUALS(0, srv->n_publish); TEST_EQUALS(NUM_MEDIA_PACKETS, cli->n_audio); TEST_EQUALS(NUM_MEDIA_PACKETS, cli->n_video); TEST_EQUALS(1, cli->n_data); TEST_EQUALS(0, srv->n_audio); TEST_EQUALS(0, srv->n_video); TEST_EQUALS(0, srv->n_data); break; case MODE_PUBLISH: TEST_EQUALS(0, srv->n_play); TEST_EQUALS(1, srv->n_publish); TEST_EQUALS(0, cli->n_audio); TEST_EQUALS(0, cli->n_video); TEST_EQUALS(0, cli->n_data); TEST_EQUALS(NUM_MEDIA_PACKETS, srv->n_audio); TEST_EQUALS(NUM_MEDIA_PACKETS, srv->n_video); TEST_EQUALS(1, srv->n_data); break; } out: mem_deref(srv); mem_deref(cli); return err; } int test_rtmp_play(void) { int err = 0; err = test_rtmp_client_server_conn(MODE_PLAY, false); TEST_ERR(err); out: return err; } int test_rtmp_publish(void) { int err = 0; err = test_rtmp_client_server_conn(MODE_PUBLISH, false); TEST_ERR(err); out: return err; } #ifdef USE_TLS int test_rtmps_publish(void) { int err = 0; err = test_rtmp_client_server_conn(MODE_PUBLISH, true); TEST_ERR(err); out: return err; } #endif ================================================ FILE: test/rtp.c ================================================ /** * @file rtp.c RTP/RTCP Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "rtptest" #define DEBUG_LEVEL 5 #include enum {PAYLOAD_SIZE = 160}; int test_rtp(void) { struct rtp_sock *rtp = NULL; struct mbuf *mb = NULL; static const uint8_t payload[PAYLOAD_SIZE]; int j; int err; mb = mbuf_alloc(RTP_HEADER_SIZE); if (!mb) return ENOMEM; err = rtp_alloc(&rtp); if (err) goto out; for (j=0; j<100; j++) { struct rtp_header hdr, hdr2; memset(&hdr, 0, sizeof(hdr)); hdr.m = j & 0x01; hdr.pt = j & 0x7f; hdr.ts = 160 + j; mb->pos = mb->end = RTP_HEADER_SIZE; err = mbuf_write_mem(mb, payload, sizeof(payload)); if (err) break; mb->pos = 0; err = rtp_encode(rtp, false, hdr.m, hdr.pt, hdr.ts, mb); if (err) break; mb->pos = 0; err = rtp_decode(rtp, mb, &hdr2); if (err) break; if (hdr.m != hdr2.m) { DEBUG_WARNING("marker bit mismatch (%d != %d)\n", hdr.m, hdr2.m); err = EBADMSG; break; } if (hdr.pt != hdr2.pt) { DEBUG_WARNING("payload type mismatch (%u != %u)\n", hdr.pt, hdr2.pt); err = EBADMSG; break; } if (hdr.ts != hdr2.ts) { DEBUG_WARNING("timestamp mismatch (%lu != %lu)\n", hdr.ts, hdr2.ts); err = EBADMSG; break; } if (hdr2.pad) { DEBUG_WARNING("unexpected padding bit\n"); err = EBADMSG; break; } if (hdr2.ext) { DEBUG_WARNING("unexpected extension bit\n"); err = EBADMSG; break; } if (RTP_HEADER_SIZE != mb->pos || (RTP_HEADER_SIZE + PAYLOAD_SIZE) != mb->end) { DEBUG_WARNING("invalid mbuf size (pos=%u end=%u)\n", mb->pos, mb->end); err = EBADMSG; break; } if (0 != memcmp(mbuf_buf(mb), payload, sizeof(payload))) { DEBUG_WARNING("RTP payload mismatch\n"); err = EBADMSG; break; } } out: mem_deref(rtp); mem_deref(mb); return err; } static const uint8_t rtcp_msg[] = /* SR */ "\x81\xc8\x00\x0c" "\x12\x34\x56\x78" "\x00\x11\x22\x33" "\x44\x55\x66\x77" "\x22\x33\x22\x33" "\x00\x00\x11\x11" "\x00\x00\x22\x22" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" /* RR */ "\x81\xc9\x00\x07" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" "\x12\x34\x56\x78" /* SDES */ "\x81\xca\x00\x03" "\xde\xad\xbe\xef" "\x01\x03\x61\x62" "\x63\x00\x00\x00" /* BYE */ "\x82\xcb\x00\x04" "\x12\x34\x56\x78" "\x00\xab\xcd\xef" "\x04\x63\x69\x61" "\x6f\x00\x00\x00" /* APP */ "\x80\xcc\x00\x03" "\x12\x34\x56\x78" "\x6e\x61\x6d\x65" "\x64\x61\x74\x61" /* FIR */ "\x80\xc0\x00\x01" "\x12\x34\x56\x78" /* NACK */ "\x80\xc1\x00\x02" "\x12\x34\x56\x78" "\x89\xab\xcd\xef" /* RTPFB */ "\x81\xcd\x00\x05" "\x12\x34\x56\x78" "\xfe\xdc\xba\x98" "\x01\x23\x04\x56" "\x01\x23\x04\x56" "\x01\x23\x04\x56" /* PSFB - PLI */ "\x81\xce\x00\x02" "\x12\x34\x56\x78" "\xfe\xdc\xba\x98" /* PSFB - SLI */ "\x82\xce\x00\x04" "\x12\x34\x56\x78" "\xfe\xdc\xba\x98" "\xca\xfe\xca\xfe" "\xca\xfe\xca\xfe" ""; static int encode_handler(struct mbuf *mb, void *arg) { int err = 0; size_t i; (void)arg; for (i=0; i<6 && !err; i++) err = mbuf_write_u32(mb, htonl(0x12345678)); return err; } static int sdes_encode_handler(struct mbuf *mb, void *arg) { (void)arg; return rtcp_sdes_encode(mb, 0xdeadbeef, 1, RTCP_SDES_CNAME, "abc"); } static int gnack_encode(struct mbuf *mb, void *arg) { int err = 0, n=3; (void)arg; while (n--) { err |= mbuf_write_u16(mb, htons(0x0123)); err |= mbuf_write_u16(mb, htons(0x0456)); } return err; } static int sli_encode(struct mbuf *mb, void *arg) { int err = 0, n=2; (void)arg; while (n--) { err |= mbuf_write_u32(mb, htonl(0xcafecafe)); } return err; } int test_rtcp_encode(void) { struct mbuf *mb; const size_t sz = sizeof(rtcp_msg) - 1; const uint32_t srcv[2] = {0x12345678, 0x00abcdef}; char debug_buf[512]; int err = 0; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err |= rtcp_encode(mb, RTCP_SR, 1, 0x12345678, 0x00112233, 0x44556677, 0x22332233, 0x00001111, 0x00002222, encode_handler, 0); err |= rtcp_encode(mb, RTCP_RR, 1, 0x12345678, encode_handler, 0); err |= rtcp_encode(mb, RTCP_SDES, 1, sdes_encode_handler, 0); err |= rtcp_encode(mb, RTCP_BYE, 2, srcv, "ciao"); err |= rtcp_encode(mb, RTCP_APP, 0, 0x12345678, "name", "data", (size_t)4); err |= rtcp_encode(mb, RTCP_FIR, 0, 0x12345678); err |= rtcp_encode(mb, RTCP_NACK, 0, 0x12345678, 0x89ab, 0xcdef); err |= rtcp_encode(mb, RTCP_RTPFB, RTCP_RTPFB_GNACK, 0x12345678, 0xfedcba98, gnack_encode, 0); err |= rtcp_encode(mb, RTCP_PSFB, RTCP_PSFB_PLI, 0x12345678, 0xfedcba98, NULL, 0); err |= rtcp_encode(mb, RTCP_PSFB, RTCP_PSFB_SLI, 0x12345678, 0xfedcba98, sli_encode, 0); if (err) goto out; if (mb->end != sz) { err = EPROTO; } if (0 != memcmp(mb->buf, rtcp_msg, mb->end)) { err = EBADMSG; } if (err) { DEBUG_WARNING("encode error: %m\n", err); hexdump(stderr, mb->buf, mb->end); } mb->pos = 0; while (mbuf_get_left(mb) >= 4 && !err) { struct rtcp_msg *msg = NULL; err = rtcp_decode(&msg, mb); if (err) break; /* Check that debug print works */ debug_buf[0] = '\0'; re_snprintf(debug_buf, sizeof(debug_buf), "%H", rtcp_msg_print, msg); msg = mem_deref(msg); ASSERT_TRUE(str_isset(debug_buf)); } if (err) goto out; /* verify that rtcp_decode() read the whole buffer */ TEST_EQUALS(mb->end, mb->pos); out: mem_deref(mb); return err; } static const uint8_t rtcp_sdes[] = /* SDES */ "\x83\xca\x00\x09" "\x11\x22\x33\x44" "\x01\x02\x41\x61" /* cname */ "\x00\x00\x00\x00" "\x55\x66\x77\x88" "\x07\x02\x42\x62" /* note */ "\x02\x01\x43\x00" /* name */ "\xaa\xbb\xcc\xdd" "\x04\x05\x31\x32" /* phone */ "\x33\x34\x35\x00" /* APP */ "\x80\xcc\x00\x03" "\x12\x34\x56\x78" "\x6e\x61\x6d\x65" "\x64\x61\x74\x61" ""; int test_rtcp_decode_badmsg(void) { struct rtcp_msg *msg = NULL; uint32_t ssrc = 0xcafebabe; struct mbuf *mb = mbuf_alloc(128); if (!mb) return ENOMEM; int err = rtcp_encode(mb, RTCP_PSFB, RTCP_PSFB_SLI, ssrc, ssrc, NULL, NULL); if (err) goto out; /* simulate a corrupt RTCP packet */ mb->pos = 2; (void)mbuf_write_u16(mb, htons(0)); mb->pos = 0; int ret = rtcp_decode(&msg, mb); if (EBADMSG != ret && ret != ENOMEM) { err = EBADMSG; goto out; } out: mem_deref(msg); mem_deref(mb); return err; } int test_rtcp_decode(void) { struct rtcp_msg *msg = NULL; struct mbuf *mb; int err = 0; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err |= mbuf_write_u8(mb, 0x55); /* overhead -- test padding */ err |= mbuf_write_mem(mb, rtcp_sdes, sizeof(rtcp_sdes)); err |= mbuf_write_u8(mb, 0xaa); /* junk */ TEST_ERR(err); mb->pos = 1; /* SDES */ err = rtcp_decode(&msg, mb); TEST_ERR(err); ASSERT_EQ( 2, msg->hdr.version); ASSERT_EQ( 0, msg->hdr.p); ASSERT_EQ( 3, msg->hdr.count); ASSERT_EQ(202, msg->hdr.pt); ASSERT_EQ( 9, msg->hdr.length); const struct rtcp_sdes *sdes = &msg->r.sdesv[0]; ASSERT_EQ(0x11223344, sdes->src); ASSERT_EQ(1, sdes->n); const struct rtcp_sdes_item *item = &sdes->itemv[0]; ASSERT_EQ(1, item->type); ASSERT_EQ(2, item->length); TEST_STRCMP("Aa", 2, item->data, item->length); msg = mem_deref(msg); /* APP */ err = rtcp_decode(&msg, mb); TEST_ERR(err); ASSERT_EQ( 2, msg->hdr.version); ASSERT_EQ( 0, msg->hdr.p); ASSERT_EQ( 0, msg->hdr.count); ASSERT_EQ(204, msg->hdr.pt); ASSERT_EQ( 3, msg->hdr.length); ASSERT_EQ( 0x12345678, msg->r.app.src); TEST_STRCMP("name", 4, msg->r.app.name, 4); TEST_STRCMP("data", 4, msg->r.app.data, msg->r.app.data_len); if (err) goto out; out: mem_deref(msg); mem_deref(mb); return err; } static int afb_encode_handler(struct mbuf *mb, void *arg) { return mbuf_write_str(mb, arg); } int test_rtcp_encode_afb(void) { uint32_t ssrc_packet_sender, ssrc_media_source; const char *afb_payload = "AFB tull"; struct rtcp_msg *msg = NULL; struct mbuf *mb; int err = 0; mb = mbuf_alloc(512); if (!mb) return ENOMEM; ssrc_packet_sender = 0xbad00bad; ssrc_media_source = 0; /* always 0 */ err = rtcp_encode(mb, RTCP_PSFB, RTCP_PSFB_AFB, ssrc_packet_sender, ssrc_media_source, afb_encode_handler, afb_payload); if (err) goto out; mb->pos = 0; err = rtcp_decode(&msg, mb); if (err) goto out; if (msg->hdr.count != RTCP_PSFB_AFB) { DEBUG_WARNING("expected AFB, got fmt=%u\n", msg->hdr.count); err = EPROTO; goto out; } if (msg->r.fb.ssrc_packet != ssrc_packet_sender || msg->r.fb.ssrc_media != ssrc_media_source) { DEBUG_WARNING("error in SSRC encoding\n"); err = EBADMSG; goto out; } if (!msg->r.fb.fci.afb || mbuf_get_left(msg->r.fb.fci.afb) != strlen(afb_payload)) { DEBUG_WARNING("error in AFB mbuf (left=%u, size=%u)\n", mbuf_get_left(msg->r.fb.fci.afb), strlen(afb_payload)); err = EBADMSG; goto out; } if (0 != memcmp(mbuf_buf(msg->r.fb.fci.afb), afb_payload, strlen(afb_payload))) { DEBUG_WARNING("error in AFB mbuf content\n"); err = EBADMSG; goto out; } /* verify that rtcp_decode() read the whole buffer */ TEST_EQUALS(mb->end, mb->pos); out: mem_deref(mb); mem_deref(msg); return err; } struct rtp_test { struct rtp_sock *rtp; struct mbuf *mb; uint32_t n; uint32_t f; }; static void rtp_recv_handler(const struct sa *src, const struct rtp_header *hdr, struct mbuf *mb, void *arg) { struct rtp_test *test = arg; char bufs[5]; char bufr[5]; (void) src; (void) hdr; mbuf_read_str(test->mb, bufs, sizeof(bufs)); mbuf_read_str(mb, bufr, sizeof(bufr)); if (!strncmp(bufr, bufs, sizeof(bufs))) test->n++; else test->f++; if (test->n + test->f == 2) re_cancel(); else mbuf_advance(test->mb, RTP_HEADER_SIZE); } static int test_rtp_listen_priv(bool clear, bool single) { struct rtp_test test; struct sa sa; size_t pos; int err; sa_init(&sa, AF_INET); memset(&test, 0, sizeof(test)); if (single) { for (int i=0; i<10; i++) { err = rtp_listen_single(&test.rtp, &sa, 49152 + i, rtp_recv_handler, &test); if (!err || err == ENOMEM) break; } } else { err = rtp_listen(&test.rtp, IPPROTO_UDP, &sa, 1024, 49152, false, rtp_recv_handler, NULL, &test); } TEST_ERR(err); test.mb = mbuf_alloc(2 * (RTP_HEADER_SIZE + 5)); if (!test.mb) { err = ENOMEM; goto out; } pos = RTP_HEADER_SIZE; test.mb->pos = test.mb->end = pos; mbuf_write_str(test.mb, "abcd"); mbuf_write_u8(test.mb, 0); test.mb->pos = pos; sa_set_str(&sa, "127.0.0.1", sa_port(rtp_local(test.rtp))); err = rtp_send(test.rtp, &sa, false, true, 0, 160, tmr_jiffies_rt_usec(), test.mb); TEST_ERR(err); pos = test.mb->end + RTP_HEADER_SIZE; test.mb->pos = test.mb->end = pos; mbuf_write_str(test.mb, "bcde"); mbuf_write_u8(test.mb, 0); test.mb->pos = pos; err = rtp_send(test.rtp, &sa, false, false, 0, 320, tmr_jiffies_rt_usec(), test.mb); TEST_ERR(err); if (clear) { err = rtp_clear(test.rtp); TEST_ERR(err); } test.mb->pos = RTP_HEADER_SIZE; if (!clear) { err = re_main_timeout(100); TEST_ERR(err); } TEST_EQUALS(clear ? 0 : 2, test.n); TEST_EQUALS(0, test.f); out: mem_deref(test.rtp); mem_deref(test.mb); return err; } int test_rtp_listen(void) { int err; err = test_rtp_listen_priv(false, false); TEST_ERR(err); err = test_rtp_listen_priv(true, false); TEST_ERR(err); err = test_rtp_listen_priv(false, true); TEST_ERR(err); err = test_rtp_listen_priv(true, true); TEST_ERR(err); out: return err; } int test_rtcp_twcc(void) { /* A RTCP transport-cc parser test. Done as part of WebRtcTransport which uses it. TWCC rtcp packets have been extracted from what Chrome sends using Wireshark and concatenated to form a single compound packet. */ uint8_t packets[] = { /* First packet */ 0xaf, 0xcd, 0x00, 0x07, 0xfa, 0x17, 0xfa, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x09, 0x00, 0x42, 0x6d, 0x00, 0xad, 0xe0, 0x14, 0x18, 0x18, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, /* Second packet */ 0xaf, 0xcd, 0x00, 0x0c, 0xfa, 0x17, 0xfa, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x1e, 0x00, 0x42, 0x6d, 0x01, 0x96, 0xf7, 0xbb, 0xf3, 0x20, 0x02, 0xb0, 0x14, 0x08, 0x58, 0x18, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, /* Third packet */ 0xaf, 0xcd, 0x00, 0x07, 0xfa, 0x17, 0xfa, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x2a, 0x00, 0x0b, 0x00, 0x42, 0x6e, 0x02, 0x9b, 0xf8, 0xb8, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, /* Chrome 114 */ 0x8f, 0xcd, 0x00, 0x08, 0xfa, 0x17, 0xfa, 0x17, 0x4f, 0x10, 0x01, 0x6f, 0x00, 0x08, 0x00, 0x0e, 0x25, 0x27, 0x0c, 0x02, 0x20, 0x0e, 0xb9, 0x0b, 0x27, 0x00, 0x15, 0x0b, 0x0a, 0x00, 0x00, 0x10, 0x0b, 0x10, 0x10, 0x1d }; struct mbuf *buf = mbuf_alloc(sizeof(packets)); if (!buf) return ENOMEM; struct rtcp_msg *msg = NULL; int err = 0; mbuf_write_mem(buf, packets, sizeof(packets)); mbuf_set_pos(buf, 0); /* TWCC n=5 base=3 count=9 reftime=17005 fbcount=0 chunks=2 deltas=7 (with padding 00 00 03) */ err = rtcp_decode(&msg, buf); TEST_ERR(err); ASSERT_EQ(err, 0); ASSERT_EQ(msg->hdr.count, RTCP_RTPFB_TWCC); ASSERT_EQ(msg->r.fb.n, 5); ASSERT_TRUE(msg->r.fb.fci.twccv != NULL); ASSERT_EQ(msg->r.fb.fci.twccv->seq, 3); ASSERT_EQ(msg->r.fb.fci.twccv->count, 9); ASSERT_EQ(msg->r.fb.fci.twccv->reftime, 17005); ASSERT_EQ(msg->r.fb.fci.twccv->fbcount, 0); ASSERT_EQ(mbuf_get_left(msg->r.fb.fci.twccv->chunks), 2); ASSERT_EQ(mbuf_get_left(msg->r.fb.fci.twccv->deltas), 7); msg = mem_deref(msg); /* TWCC n=10 base=12 count=30 reftime=17005 fbcount=1 chunks=6 deltas=23 (with padding 00 00 03) */ err = rtcp_decode(&msg, buf); TEST_ERR(err); ASSERT_EQ(err, 0); ASSERT_EQ(msg->hdr.count, RTCP_RTPFB_TWCC); ASSERT_EQ(msg->r.fb.n, 10); ASSERT_EQ(msg->r.fb.fci.twccv->seq, 12); ASSERT_EQ(msg->r.fb.fci.twccv->count, 30); ASSERT_EQ(msg->r.fb.fci.twccv->reftime, 17005); ASSERT_EQ(msg->r.fb.fci.twccv->fbcount, 1); ASSERT_EQ(mbuf_get_left(msg->r.fb.fci.twccv->chunks), 6); ASSERT_EQ(mbuf_get_left(msg->r.fb.fci.twccv->deltas), 23); msg = mem_deref(msg); /* TWCC n=5 base=42 count=11 reftime=17006 fbcount=2 chunks=2 deltas=9 (with padding 01) */ err = rtcp_decode(&msg, buf); TEST_ERR(err); ASSERT_EQ(err, 0); ASSERT_EQ(msg->hdr.count, RTCP_RTPFB_TWCC); ASSERT_EQ(msg->r.fb.n, 5); ASSERT_EQ(msg->r.fb.fci.twccv->seq, 42); ASSERT_EQ(msg->r.fb.fci.twccv->count, 11); ASSERT_EQ(msg->r.fb.fci.twccv->reftime, 17006); ASSERT_EQ(msg->r.fb.fci.twccv->fbcount, 2); ASSERT_EQ(mbuf_get_left(msg->r.fb.fci.twccv->chunks), 2); ASSERT_EQ(mbuf_get_left(msg->r.fb.fci.twccv->deltas), 9); msg = mem_deref(msg); /* Chrome 114 */ err = rtcp_decode(&msg, buf); TEST_ERR(err); ASSERT_TRUE(!msg->hdr.p); ASSERT_EQ(RTCP_RTPFB_TWCC, msg->hdr.count); ASSERT_EQ(205, msg->hdr.pt); ASSERT_EQ(8, msg->hdr.length); ASSERT_EQ(0xfa17fa17, msg->r.fb.ssrc_packet); ASSERT_EQ(0x4f10016f, msg->r.fb.ssrc_media); ASSERT_EQ(6, msg->r.fb.n); const struct twcc *twcc = msg->r.fb.fci.twccv; ASSERT_TRUE(twcc != NULL); ASSERT_EQ(8, twcc->seq); ASSERT_EQ(14, twcc->count); ASSERT_EQ(2434828, twcc->reftime); ASSERT_EQ(2, twcc->fbcount); ASSERT_EQ(2, mbuf_get_left(twcc->chunks)); ASSERT_EQ(14, mbuf_get_left(twcc->deltas)); msg = mem_deref(msg); /* Assert we have processed everything. */ ASSERT_EQ(mbuf_get_left(buf), 0); out: mem_deref(buf); mem_deref(msg); return err; } ================================================ FILE: test/rtpext.c ================================================ /** * @file rtpext.c RTP Header Extensions * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include "test.h" #define DEBUG_MODULE "rtpext" #define DEBUG_LEVEL 5 #include static const uint8_t packet_bytes[] = { /* Header */ 0xbe, 0xde, 0x00, 0x01, /* First Extension */ 0x40, 0x30, /* Second Extension */ 0x10, 0xe0 }; struct rtpext_header { uint16_t type; uint16_t num_bytes; }; /* Common for One-Byte and Two-Byte headers */ static int rtpext_hdr_decode(struct rtpext_header *hdr, struct mbuf *mb) { if (mbuf_get_left(mb) < RTPEXT_HDR_SIZE) return EBADMSG; hdr->type = ntohs(mbuf_read_u16(mb)); hdr->num_bytes = ntohs(mbuf_read_u16(mb)) * 4; if (mbuf_get_left(mb) < hdr->num_bytes) return EBADMSG; return 0; } static int test_rtpext_long(void) { static const uint8_t TEST_EXTENSION_ID_TWOBYTE = 0xf0; #define TEST_DATA_LENGTH 3 #define NUM_BYTES_LONG 8 static const uint8_t packet[RTPEXT_HDR_SIZE + NUM_BYTES_LONG] = { 0x10, 0x00, 0x00, 0x02, 0xf0, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00 }; static const uint8_t data[TEST_DATA_LENGTH] = { 0x01, 0x02, 0x03 }; struct mbuf *mb = mbuf_alloc(1024); if (!mb) return ENOMEM; struct rtpext_header hdr; struct rtpext ext; /* Encode packet */ int err = rtpext_hdr_encode_long(mb, NUM_BYTES_LONG); ASSERT_EQ(0, err); err = rtpext_encode_long(mb, TEST_EXTENSION_ID_TWOBYTE, TEST_DATA_LENGTH, data); ASSERT_EQ(0, err); /* padding */ mbuf_fill(mb, 0x00, 3); TEST_MEMCMP(packet, sizeof(packet), mb->buf, mb->end); mb->pos = 0; /* Decode packet */ err = rtpext_hdr_decode(&hdr, mb); ASSERT_EQ(0, err); ASSERT_EQ(RTPEXT_TYPE_MAGIC_LONG, hdr.type); ASSERT_EQ(NUM_BYTES_LONG, hdr.num_bytes); err = rtpext_decode_long(&ext, mb); ASSERT_EQ(0, err); ASSERT_EQ(TEST_EXTENSION_ID_TWOBYTE, ext.id); ASSERT_EQ(TEST_DATA_LENGTH, ext.len); TEST_MEMCMP(data, sizeof(data), ext.data, ext.len); out: mem_deref(mb); return err; } /* 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 0x10 | 0x00 | length=3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ID | L=0 | ID | L=1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | 0 (pad) | ID | L=4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ static int test_rtpext_long_rfc(void) { #define NUM_BYTES_RFC (12) struct mbuf *mb = mbuf_alloc(1024); if (!mb) return ENOMEM; int err = rtpext_hdr_encode_long(mb, NUM_BYTES_RFC); ASSERT_EQ(0, err); /* Encode */ err = rtpext_encode_long(mb, 0x80, 0, NULL); ASSERT_EQ(0, err); err = rtpext_encode_long(mb, 0x81, 1, (uint8_t *)"A" ); ASSERT_EQ(0, err); /* padding */ mbuf_write_u8(mb, 0x00); err = rtpext_encode_long(mb, 0x82, 4, (uint8_t *)"ABCD" ); ASSERT_EQ(0, err); static const uint8_t packet[RTPEXT_HDR_SIZE + NUM_BYTES_RFC] = { 0x10, 0x00, 0x00, 0x03, 0x80, 0x00, 0x81, 0x01, 0x41, 0x00, 0x82, 0x04, 0x41, 0x42, 0x43, 0x44, }; TEST_MEMCMP(packet, sizeof(packet), mb->buf, mb->end); mb->pos = 0; /* Decode */ struct rtpext_header hdr; err = rtpext_hdr_decode(&hdr, mb); ASSERT_EQ(0, err); ASSERT_EQ(RTPEXT_TYPE_MAGIC_LONG, hdr.type); ASSERT_EQ(NUM_BYTES_RFC, hdr.num_bytes); struct rtpext ext; err = rtpext_decode_long(&ext, mb); ASSERT_EQ(0, err); ASSERT_EQ(0x80, ext.id); ASSERT_EQ(0, ext.len); err = rtpext_decode_long(&ext, mb); ASSERT_EQ(0, err); ASSERT_EQ(0x81, ext.id); ASSERT_EQ(1, ext.len); ASSERT_EQ(0x41, ext.data[0]); err = rtpext_decode_long(&ext, mb); ASSERT_EQ(0, err); ASSERT_EQ(0x82, ext.id); ASSERT_EQ(4, ext.len); TEST_MEMCMP(&packet[12], 4, ext.data, ext.len); out: mem_deref(mb); return err; } int test_rtpext(void) { struct mbuf *mb = mbuf_alloc(sizeof(packet_bytes)); if (!mb) return ENOMEM; int err = mbuf_write_mem(mb, packet_bytes, sizeof(packet_bytes)); ASSERT_EQ(0, err); mb->pos = 0; /* decode header */ uint16_t type = ntohs(mbuf_read_u16(mb)); uint16_t words = ntohs(mbuf_read_u16(mb)); ASSERT_EQ(RTPEXT_TYPE_MAGIC, type); ASSERT_EQ(1, words); size_t num_bytes = words * sizeof(uint32_t); ASSERT_EQ(num_bytes, mbuf_get_left(mb)); struct rtpext ext; /* First extension */ err = rtpext_decode(&ext, mb); ASSERT_EQ(0, err); ASSERT_EQ(4, ext.id); ASSERT_EQ(1, ext.len); ASSERT_EQ(0x30, ext.data[0]); /* Second extension */ err = rtpext_decode(&ext, mb); ASSERT_EQ(0, err); ASSERT_EQ(1, ext.id); ASSERT_EQ(1, ext.len); ASSERT_EQ(0xe0, ext.data[0]); err = test_rtpext_long(); if (err) goto out; err = test_rtpext_long_rfc(); if (err) goto out; out: mem_deref(mb); return err; } ================================================ FILE: test/sa.c ================================================ /** * @file sa.c Socket address Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_sa" #define DEBUG_LEVEL 5 #include int test_sa_cmp(void) { const struct { const char *host1; uint16_t port1; const char *host2; uint16_t port2; bool eq; } testv[] = { #if HAVE_UNIXSOCK { "unix:/test.sock", 0, "unix:/test.sock", 0, true }, #endif { "1.2.3.4", 12345, "1.2.3.4", 12345, true }, { "1.2.3.4", 12345, "1.2.3.5", 12345, false }, { "1.2.3.4", 12345, "1.2.3.4", 12344, false }, { "0:0:0:0:0:0:0:0", 123, "::", 123, true }, { "0:0:0:0:0:0:0:1", 123, "::1", 123, true }, { "0:0:0:0:0:0:1:1", 123, "::1", 123, false }, { "2001:0:53aa:64c:0:fbff:ab2e:1eac", 2001, "2001:0000:53aa:064c:0000:fbff:ab2e:1eac", 2001, true }, { "3001:0:53aa:64c:0:fbff:ab2e:1eac", 2001, "2001:0000:53aa:064c:0000:fbff:ab2e:1eac", 2001, false }, { "192.168.1.1", 123, "2001:0000:53aa:064c:0000:fbff:ab2e:1eac", 123, false }, { /* IPv6-mapped IPv4-address */ "::ffff:208.68.208.201", 3478, "208.68.208.201", 3478, true }, { "fe80::215:58ff:fe2d:90ab", 3333, "fe80:0000:0000:0000:0215:58ff:fe2d:90ab", 3333, true }, { "fe80::215:58ff:fe2d:90ab", 3333, "fe80:0000:0000:0000:1215:58ff:fe2d:90ab", 3333, false }, }; size_t i; int err = 0; for (i=0; i #include #include "test.h" #define DEBUG_MODULE "test" #define DEBUG_LEVEL 5 #include #define TEST_STRCMP_LEN(exp, actual) \ TEST_STRCMP((exp), str_len((exp)), (actual), str_len((actual))) static const char ref_host[] = "1.2.3.4"; static const uint16_t ref_port = 5004; static const char ref_pt[] = "0"; static const char *ref_cname = "PCMU"; static const char *cname_speex = "speex"; static const uint32_t ref_srate = 8000; static const char ref_msg[] = "v=0\r\n" "o=- 1234 5678 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 5004 RTP/AVP 0 110\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:110 speex/16000/2\r\n" "a=sendrecv\r\n"; static const char *msgs[] = { /* Counterpath */ "v=0\n" "o=- 1 2 IN IP4 84.209.220.122\r\n" "s=\r\n" "c=IN IP4 84.209.220.122\r\n" "t=0 0\r\n" "m=audio 24484 RTP/AVP 107 119 0 98 8 3 101\r\n" "a=alt:1 2 : 8KiUNmDF 2AKrU/iZ 192.168.1.100 24484\r\n" "a=alt:2 1 : uEmq9erD rG6uFpsK 84.209.220.122 24484\r\n" "a=fmtp:101 0-15\r\n" "a=rtpmap:107 BV32/16000\r\n" "a=rtpmap:119 BV32-FEC/16000\r\n" "a=rtpmap:98 iLBC/8000\r\n" "a=rtpmap:101 telephone-event/8000\r\n" "a=sendrecv\r\n" "a=x-rtp-session-id:EE42E5DC96034E1A95B8843DA28640E4\r\n" "m=video 5040 RTP/AVP 115 34\r\n" "a=alt:1 2 : 6zn66DoK 0TsJT2lQ 192.168.1.100 5040\r\n" "a=alt:2 1 : JTVNzu+a 8pvqo0dE 84.209.220.122 5040\r\n" "a=fmtp:115 QCIF=2 MAXBR=2180\r\n" "a=fmtp:34 QCIF=2 MAXBR=2180\r\n" "a=rtpmap:115 H263-1998/90000\r\n" "a=rtpmap:34 H263/90000\r\n" "a=sendrecv\r\n" "a=x-rtp-session-id:E6856C1F08904D6B88B129266C82D351\r\n", /** Freeswitch 1.0rc1 */ "v=0\r\n" "o=FreeSWITCH 531003883936 28814208941 IN IP4 1.2.3.4\r\n" "s=FreeSWITCH\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "a=sendrecv\r\n" "m=audio 16610 RTP/AVP 8 99 13\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:99 telephone-event/8000\r\n" "a=fmtp:99 0-16\r\n" "a=rtpmap:13 CN/8000\r\n" "a=ptime:20\r\n" "a=nortpproxy:yes\r\n", /* newline termination */ "v=0\n" "o=- 531003883936 28814208941 IN IP4 1.2.3.4\n" "s=-\n" "t=0 0\n" "m=audio 16610 RTP/AVP 8\n" "c=IN IP4 1.2.3.4\n" "a=rtpmap:8 PCMA/8000\n", /* Polycom */ "v=0\r\n" "o=- 1197975037 1197975037 IN IP4 192.168.9.74\r\n" "s=Polycom IP Phone\r\n" "c=IN IP4 192.168.9.74\r\n" "t=0 0\r\n" "m=audio 49200 RTP/AVP 8 0 9 18 96\r\n" "a=sendrecv\r\n" "a=crypto:1 AES_CM_128_HMAC_SHA1_80" " inline:tMuyik1Aiiq9p4DQVHhAASSWDEP7K7wo0cICOn39\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:9 G722/8000\r\n" "a=rtpmap:18 G729/8000\r\n" "a=rtpmap:96 telephone-event/8000\r\n", /* Ekiga 3.0 */ "v=0\r\n" "o=- 1235562135 1235562135 IN IP4 192.168.1.55\r\n" "s=Opal SIP Session\r\n" "c=IN IP4 192.168.1.55\r\n" "t=0 0\r\n" "m=audio 5062 RTP/AVP 106 105 9 117 8 0 104 103 102 120 3 116 101\r\n" "a=sendrecv\r\n" "a=rtpmap:106 CELT/48000/1\r\n" "a=rtpmap:105 CELT/32000/1\r\n" "a=rtpmap:9 G722/8000/1\r\n" "a=rtpmap:117 Speex/16000/1\r\n" "a=fmtp:117 sr=16000,mode=any\r\n" "a=rtpmap:8 PCMA/8000/1\r\n" "a=rtpmap:0 PCMU/8000/1\r\n" "a=rtpmap:104 G726-16/8000/1\r\n" "a=rtpmap:103 G726-24/8000/1\r\n" "a=rtpmap:102 G726-32/8000/1\r\n" "a=rtpmap:120 G726-40/8000/1\r\n" "a=rtpmap:3 gsm/8000/1\r\n" "a=rtpmap:116 Speex/8000/1\r\n" "a=fmtp:116 sr=8000,mode=any\r\n" "a=rtpmap:101 telephone-event/8000\r\n" "a=fmtp:101 0-16,32,36\r\n" /* LG */ "v=0\r\n" "o=LGN_IP_PHONE 29386 29386 IN IP4 192.168.1.97\r\n" "s=SIP Call\r\n" "c=IN IP4 85.112.135.82\r\n" "t=0 0\r\n" "m=audio 17412 RTP/AVP 9 8 0 18 101\r\n" "c=IN IP4 85.112.135.82\r\n" "b=AS:82\r\n" "a=rtpmap:9 G722/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:18 G729/8000\r\n" "a=fmtp:18 annexb=no\r\n" "a=rtpmap:101 telephone-event/8000\r\n" "a=fmtp:101 0-15\r\n" "a=ptime:20\r\n" "a=sendrecv\r\n" "m=video 16512 RTP/AVP 98 102 34 105\r\n" "c=IN IP4 85.112.135.82\r\n" "b=TIAS:329000\r\n" "b=AS:366\r\n" "a=rtpmap:98 H264/90000\r\n" "a=rtpmap:102 H264/90000\r\n" "a=fmtp:98 profile-level-id=42800C; packetization-mode=0\r\n" "a=fmtp:102 profile-level-id=42800C; packetization-mode=1\r\n" "a=rtpmap:34 H263/90000\r\n" "a=fmtp:34 CIF=1;QCIF=1\r\n" "a=rtpmap:105 MP4V-ES/90000\r\n" }; /** Compare two SDP messages line-by-line (exclude owner) */ static bool sdp_cmp(struct mbuf *mb, const char *msg) { struct pl pl; if (!mb || !msg) return false; pl.p = (char *)mb->buf; pl.l = mb->end; while (pl.l && strlen(msg)) { struct pl n1, v1, n2, v2; if (re_regex(pl.p, pl.l, "[^=]1=[^\r\n]+", &n1, &v1)) return false; if (re_regex(msg, strlen(msg), "[^=]1=[^\r\n]+", &n2, &v2)) return false; pl_advance(&pl, 2 + v1.l + 2); msg += (2 + v2.l + 2); if (0 != pl_cmp(&n1, &n2)) { DEBUG_WARNING("name mismatch: %r=%r\n", &n1, &v1); return false; } /* ignore owner */ if (n1.p[0] == 'o') continue; if (0 != pl_cmp(&v1, &v2)) { DEBUG_WARNING("value mismatch: %r=%r\n", &n1, &v1); return false; } } if (pl.l) { DEBUG_WARNING("%u bytes junk at end: %r\n", pl.l, &pl); } if (strlen(msg)) { DEBUG_WARNING("%u bytes junk at end: %s\n", strlen(msg), msg); } return !pl.l && !strlen(msg); } int test_sdp_all(void) { struct sdp_session *sess = NULL; struct sdp_media *audio = NULL; struct mbuf *desc = NULL; struct sa ref; const struct sdp_format *rc = NULL, *sc; struct sa laddr; int err; (void)sa_set_str(&laddr, ref_host, 0); err = sdp_session_alloc(&sess, &laddr); if (err) goto out; err = sdp_media_add(&audio, sess, sdp_media_audio, 5004, sdp_proto_rtpavp); if (err) goto out; err = sdp_format_add(NULL, audio, false, ref_pt, ref_cname, ref_srate, 1, NULL, NULL, NULL, false, NULL); err |= sdp_format_add(NULL, audio, false, "110", cname_speex, 16000, 2, NULL, NULL, NULL, false, NULL); if (err) goto out; /* find codec - expected */ sc = sdp_media_format(audio, true, NULL, 0, "PCMU", 8000, 1); if (!sc) { DEBUG_WARNING("codec not found\n"); err = ENOENT; goto out; } sc = sdp_media_format(audio, true, NULL, 110, "Speex", 16000, 2); if (!sc) { DEBUG_WARNING("codec not found: speex\n"); err = ENOENT; goto out; } /* find codec - not expected */ sc = sdp_media_format(audio, true, NULL, -1, "Speex", 8000, 1); if (sc) { DEBUG_WARNING("unexpected codec found\n"); err = EINVAL; goto out; } err = sdp_encode(&desc, sess, true); if (err) goto out; if (!sdp_cmp(desc, ref_msg)) { DEBUG_WARNING("ref: %s\n", ref_msg); DEBUG_WARNING("sdp: %b\n", desc->buf, desc->end); err = EBADMSG; goto out; } err = sdp_decode(sess, desc, false); if (err) goto out; rc = sdp_media_rformat(audio, NULL); if (!rc) { err = ENOENT; goto out; } err = sa_set_str(&ref, ref_host, ref_port); if (err) goto out; err = EINVAL; if (!sa_cmp(sdp_media_raddr(audio), &ref, SA_ALL)) goto out; if (0 != strcmp(rc->id, ref_pt)) goto out; if (0 != strcmp(ref_cname, rc->name)) goto out; if (rc->srate != ref_srate) goto out; err = 0; out: mem_deref(audio); mem_deref(sess); mem_deref(desc); return err; } /** * Test parsing of various SDP messages from various vendors */ int test_sdp_parse(void) { struct sdp_session *sess = NULL; struct sdp_media *audio; struct mbuf *mb; struct sa laddr; uint32_t i; int err = 0; mb = mbuf_alloc(2048); if (!mb) return ENOMEM; sa_init(&laddr, AF_INET); for (i=0; ipos = 0; err = sdp_decode(sess, mb, true); if (err) goto out; } out: mem_deref(sess); mem_deref(mb); return err; } struct oa { struct sdp_session *alice, *bob; }; static int oa_init(struct oa *oa) { struct sa laddr; int err; if (!oa->alice) { (void)sa_set_str(&laddr, "1.2.3.4", 0); err = sdp_session_alloc(&oa->alice, &laddr); if (err) return err; } if (!oa->bob) { (void)sa_set_str(&laddr, "5.6.7.8", 0); err = sdp_session_alloc(&oa->bob, &laddr); if (err) return err; } return 0; } static void oa_reset(struct oa *oa) { oa->alice = mem_deref(oa->alice); oa->bob = mem_deref(oa->bob); } static int oa_addmedia(struct oa *oa, bool local, const char *mname, uint16_t port, const char *transp, enum sdp_dir dir, uint32_t ncodec, ...) { struct sdp_media *m; va_list ap; int err; err = oa_init(oa); if (err) return err; err = sdp_media_add(&m, local ? oa->alice : oa->bob, mname, port, transp); if (err) return err; sdp_media_set_ldir(m, dir); va_start(ap, ncodec); while (ncodec--) { const char *id = va_arg(ap, char *); const char *cname = va_arg(ap, char *); int srate = va_arg(ap, int); err = sdp_format_add(NULL, m, false, id, cname, srate, 1, NULL, NULL, NULL, false, NULL); if (err) break; } va_end(ap); return err; } static int oa_offeranswer(struct oa *oa, const char *offer, const char *answer) { struct mbuf *mbo = NULL, *mba = NULL; int err = 0; /* create and send offer, compare offer */ err = sdp_encode(&mbo, oa->alice, true); if (err) goto out; if (!sdp_cmp(mbo, offer)) { DEBUG_WARNING("offer failed:\n%b", mbo->buf, mbo->end); err = EBADMSG; goto out; } /* bob decodes offer */ err = sdp_decode(oa->bob, mbo, true); if (err) goto out; /* create and send answer, compare answer */ err = sdp_encode(&mba, oa->bob, false); if (err) goto out; if (!sdp_cmp(mba, answer)) { DEBUG_WARNING("answer failed:\n%b", mba->buf, mba->end); err = EBADMSG; TEST_ERR(err); } err = sdp_decode(oa->alice, mba, false); out: oa_reset(oa); mem_deref(mbo); mem_deref(mba); return err; } /* RFC 4317 - section 2.1 */ static int rfc4317_section2_1(struct oa *oa) { int err = 0; err |= oa_addmedia(oa, 1, "audio", 49170, "RTP/AVP", SDP_SENDRECV, 3, "0", "PCMU", 8000, "8", "PCMA", 8000, "97", "iLBC", 8000); err |= oa_addmedia(oa, 1, "video", 51372, "RTP/AVP", SDP_SENDRECV, 2, "31", "H261", 90000, "32", "MPV", 90000); err |= oa_addmedia(oa, 0, "audio", 49174, "RTP/AVP", SDP_SENDRECV, 1, "0", "PCMU", 8000); err |= oa_addmedia(oa, 0, "video", 49170, "RTP/AVP", SDP_SENDRECV, 1, "32", "MPV", 90000); if (err) return err; err = oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 8 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=video 51372 RTP/AVP 31 32\r\n" "a=rtpmap:31 H261/90000\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49174 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=sendrecv\r\n" "m=video 49170 RTP/AVP 32\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n"); return err; } /* RFC 4317 - section 2.2 */ static int rfc4317_section2_2(struct oa *oa) { int err = 0; err |= oa_addmedia(oa, 1, "audio", 49170, "RTP/AVP", SDP_SENDRECV, 3, "0", "PCMU", 8000, "8", "PCMA", 8000, "97", "iLBC", 8000); err |= oa_addmedia(oa, 1, "video", 51372, "RTP/AVP", SDP_SENDRECV, 2, "31", "H261", 90000, "32", "MPV", 90000); err |= oa_addmedia(oa, 0, "audio", 49172, "RTP/AVP", SDP_SENDRECV, 2, "0", "PCMU", 8000, "8", "PCMA", 8000); if (err) return err; return oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 8 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=video 51372 RTP/AVP 31 32\r\n" "a=rtpmap:31 H261/90000\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49172 RTP/AVP 0 8\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=sendrecv\r\n" "m=video 0 RTP/AVP 0\r\n" ); } /* RFC 4317 - section 2.4 */ static int rfc4317_section2_4(struct oa *oa) { int err = 0; err |= oa_addmedia(oa, 1, "audio", 49170, "RTP/AVP", SDP_SENDRECV, 2, "0", "PCMU", 8000, "97", "iLBC", 8000); err |= oa_addmedia(oa, 1, "audio", 49172, "RTP/AVP", SDP_SENDRECV, 1, "98", "telephone-event", 8000); err |= oa_addmedia(oa, 0, "audio", 49172, "RTP/AVP", SDP_SENDRECV, 1, "97", "iLBC", 8000); err |= oa_addmedia(oa, 0, "audio", 49174, "RTP/AVP", SDP_RECVONLY, 1, "98", "telephone-event", 8000); if (err) return err; err = oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=audio 49172 RTP/AVP 98\r\n" "a=rtpmap:98 telephone-event/8000\r\n" "a=sendrecv\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49172 RTP/AVP 97\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=audio 49174 RTP/AVP 98\r\n" "a=rtpmap:98 telephone-event/8000\r\n" "a=recvonly\r\n"); return err; } /* RFC 4317 - section 5.1 */ static int rfc4317_section5_1(struct oa *oa) { int err = 0; err |= oa_init(oa); if (err) return err; err = oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n"); return err; } /** Test SDP Offer/Answer examples in RFC 4317 */ int test_sdp_oa(void) { struct oa oa; int err = 0; memset(&oa, 0, sizeof(oa)); err |= rfc4317_section2_1(&oa); err |= rfc4317_section2_2(&oa); err |= rfc4317_section2_4(&oa); err |= rfc4317_section5_1(&oa); oa_reset(&oa); return err; } /** Test BFCP in SDP -- RFC 4583 */ int test_sdp_bfcp(void) { static const char *msg_offer = "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=application 50000 TCP/BFCP *\r\n" "a=sendrecv\r\n" "a=setup:passive\r\n" "a=connection:new\r\n" "a=floorctrl:s-only\r\n" "a=confid:4321\r\n" "a=userid:1234\r\n" "a=floorid:1 m-stream:10\r\n" "a=floorid:2 m-stream:11\r\n" "m=audio 50002 RTP/AVP 0\r\n" "a=sendrecv\r\n" "a=label:10\r\n" "m=video 50004 RTP/AVP 31\r\n" "a=sendrecv\r\n" "a=label:11\r\n" ; struct sdp_session *alice = NULL, *bob = NULL; struct sdp_media *bfcp, *audio, *video; struct mbuf *mbo = NULL, *mba = NULL; struct sa laddr; int err; /* create sessions */ (void)sa_set_str(&laddr, "1.2.3.4", 0); err = sdp_session_alloc(&alice, &laddr); if (err) goto out; err = sdp_media_add(&bfcp, alice, "application", 50000, "TCP/BFCP"); if (err) goto out; err |= sdp_media_set_lattr(bfcp, true, "setup", "passive"); err |= sdp_media_set_lattr(bfcp, true, "connection", "new"); err |= sdp_media_set_lattr(bfcp, true, "floorctrl", "s-only"); err |= sdp_media_set_lattr(bfcp, true, "confid", "4321"); err |= sdp_media_set_lattr(bfcp, true, "userid", "1234"); err |= sdp_media_set_lattr(bfcp, false, "floorid", "1 m-stream:10"); sdp_media_del_lattr(bfcp, "floorid"); /* test attr delete */ err |= sdp_media_set_lattr(bfcp, false, "floorid", "1 m-stream:10"); err |= sdp_media_set_lattr(bfcp, false, "floorid", "2 m-stream:11"); if (err) goto out; err = sdp_media_add(&audio, alice, "audio", 50002, "RTP/AVP"); if (err) goto out; err = sdp_media_add(&video, alice, "video", 50004, "RTP/AVP"); if (err) goto out; err |= sdp_media_set_lattr(audio, true, "label", "10"); err |= sdp_media_set_lattr(video, true, "label", "11"); if (err) goto out; err = sdp_format_add(NULL, bfcp, false, "*", NULL, 0, 0, NULL, NULL, NULL, false, NULL); err |= sdp_format_add(NULL, audio, false, "0", NULL, 0, 0, NULL, NULL, NULL, false, NULL); err |= sdp_format_add(NULL, video, false, "31", NULL, 0, 0, NULL, NULL, NULL, false, NULL); if (err) goto out; /* create and send offer, compare offer */ err = sdp_encode(&mbo, alice, true); if (err) goto out; if (!sdp_cmp(mbo, msg_offer)) { DEBUG_WARNING("offer failed:\n%b", mbo->buf, mbo->end); err = EBADMSG; goto out; } out: mem_deref(alice); mem_deref(bob); mem_deref(mbo); mem_deref(mba); return err; } int test_sdp_extmap(void) { static const char *extmapv[3] = { "extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level", "extmap:2/sendrecv http://example.com/ext.htm#xmeta short", "extmap:4096/recvonly URI-gps-string" }; struct sdp_extmap ext; int err = 0; /* extmap 1 */ err = sdp_extmap_decode(&ext, extmapv[0]); TEST_EQUALS(0, err); TEST_STRCMP("urn:ietf:params:rtp-hdrext:ssrc-audio-level", strlen("urn:ietf:params:rtp-hdrext:ssrc-audio-level"), ext.name.p, ext.name.l); TEST_ASSERT(!pl_isset(&ext.attrs)); TEST_EQUALS(SDP_SENDRECV, ext.dir); TEST_ASSERT(!ext.dir_set); TEST_EQUALS(1, ext.id); /* extmap 2 */ err = sdp_extmap_decode(&ext, extmapv[1]); TEST_EQUALS(0, err); TEST_STRCMP("http://example.com/ext.htm#xmeta", strlen("http://example.com/ext.htm#xmeta"), ext.name.p, ext.name.l); TEST_STRCMP("short", strlen("short"), ext.attrs.p, ext.attrs.l); TEST_EQUALS(SDP_SENDRECV, ext.dir); TEST_ASSERT(ext.dir_set); TEST_EQUALS(2, ext.id); /* extmap 3 */ err = sdp_extmap_decode(&ext, extmapv[2]); TEST_EQUALS(0, err); TEST_STRCMP("URI-gps-string", strlen("URI-gps-string"), ext.name.p, ext.name.l); TEST_ASSERT(!pl_isset(&ext.attrs)); TEST_EQUALS(SDP_RECVONLY, ext.dir); TEST_ASSERT(ext.dir_set); TEST_EQUALS(4096, ext.id); out: return err; } static int disabled_local_medialine(struct oa *oa) { int err = 0; err |= oa_addmedia(oa, 1, "audio", 49170, "RTP/AVP", SDP_SENDRECV, 3, "0", "PCMU", 8000, "8", "PCMA", 8000, "97", "iLBC", 8000); err |= oa_addmedia(oa, 1, "video", 51372, "RTP/AVP", SDP_INACTIVE, 2, "31", "H261", 90000, "32", "MPV", 90000); err |= oa_addmedia(oa, 0, "audio", 49174, "RTP/AVP", SDP_SENDRECV, 1, "0", "PCMU", 8000); err |= oa_addmedia(oa, 0, "video", 49170, "RTP/AVP", SDP_SENDRECV, 1, "32", "MPV", 90000); if (err) return err; err = oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 8 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=video 51372 RTP/AVP 31 32\r\n" "a=rtpmap:31 H261/90000\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=inactive\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49174 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=sendrecv\r\n" "m=video 49170 RTP/AVP 32\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=inactive\r\n"); return err; } static int disabled_remote_medialine(struct oa *oa) { int err = 0; err |= oa_addmedia(oa, 1, "audio", 49170, "RTP/AVP", SDP_SENDRECV, 3, "0", "PCMU", 8000, "8", "PCMA", 8000, "97", "iLBC", 8000); err |= oa_addmedia(oa, 1, "video", 51372, "RTP/AVP", SDP_SENDRECV, 2, "31", "H261", 90000, "32", "MPV", 90000); err |= oa_addmedia(oa, 0, "audio", 49174, "RTP/AVP", SDP_SENDRECV, 1, "0", "PCMU", 8000); err |= oa_addmedia(oa, 0, "video", 49170, "RTP/AVP", SDP_INACTIVE, 1, "32", "MPV", 90000); if (err) return err; err = oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 8 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=video 51372 RTP/AVP 31 32\r\n" "a=rtpmap:31 H261/90000\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49174 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=sendrecv\r\n" "m=video 49170 RTP/AVP 32\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=inactive\r\n"); return err; } static int reject_video_medialine(struct oa *oa) { int err = 0; err |= oa_addmedia(oa, 1, "audio", 49170, "RTP/AVP", SDP_SENDRECV, 3, "0", "PCMU", 8000, "8", "PCMA", 8000, "97", "iLBC", 8000); err |= oa_addmedia(oa, 1, "video", 51372, "RTP/AVP", SDP_SENDRECV, 2, "31", "H261", 90000, "32", "MPV", 90000); err |= oa_addmedia(oa, 0, "audio", 49174, "RTP/AVP", SDP_SENDRECV, 1, "0", "PCMU", 8000); if (err) return err; err = oa_offeranswer(oa, "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 8 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=video 51372 RTP/AVP 31 32\r\n" "a=rtpmap:31 H261/90000\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n" , "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49174 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=sendrecv\r\n" "m=video 0 RTP/AVP 0\r\n"); return err; } int test_sdp_disabled_rejected(void) { struct oa oa; int err = 0; memset(&oa, 0, sizeof(oa)); err |= disabled_local_medialine(&oa); err |= disabled_remote_medialine(&oa); err |= reject_video_medialine(&oa); oa_reset(&oa); return err; } struct fixture { struct sdp_session *sess; struct sdp_media *audio; struct sdp_media *video; }; struct attr { const char *name; const char *value; }; struct codec { const char *id; const char *name; uint32_t srate; uint8_t ch; bool audio; }; struct args { const struct attr *attrv; size_t attrc; size_t ix; }; static int fixture_init(struct fixture *fix, const struct attr *session_attrv, size_t session_attrc, const struct attr *media_attrv, size_t media_attrc, const struct codec *codecv, size_t codecc) { struct sa laddr; int err = sa_set_str(&laddr, "127.0.0.1", 9); TEST_ERR(err); err = sdp_session_alloc(&fix->sess, &laddr); TEST_ERR(err); for (size_t i=0; isess, false, attr->name, attr->value); TEST_ERR(err); } err = sdp_media_add(&fix->audio, fix->sess, "audio", 9, "UDP/TLS/RTP/SAVPF"); TEST_ERR(err); for (size_t i=0; iaudio, false, attr->name, attr->value); TEST_ERR(err); } for (size_t i=0; iaudio) continue; err = sdp_format_add(NULL, fix->audio, false, codec->id, codec->name, codec->srate, codec->ch, NULL, NULL, NULL, false, NULL); TEST_ERR(err); } err = sdp_media_add(&fix->video, fix->sess, "video", 10, "RTP/AVP"); TEST_ERR(err); for (size_t i=0; iaudio) continue; err = sdp_format_add(NULL, fix->video, false, codec->id, codec->name, codec->srate, codec->ch, NULL, NULL, NULL, false, NULL); TEST_ERR(err); } out: return err; } static void fixture_close(struct fixture *fix) { mem_deref(fix->audio); mem_deref(fix->sess); } static bool sdp_attr_handler(const char *name, const char *value, void *arg) { struct args *args = arg; int err = 0; if (args->ix >= args->attrc) { DEBUG_WARNING("sdp_attr_handler: attr count mismatch\n"); return true; } const struct attr *attr = &args->attrv[args->ix]; TEST_STRCMP_LEN(attr->name, name); TEST_STRCMP_LEN(attr->value, value); ++args->ix; out: return err != 0; } static int test_sdp_param(const char *sdp, const struct attr *session_attrv, size_t session_attrc, const struct attr *media_attrv, size_t media_attrc, const struct codec *codecv, size_t codecc) { struct fixture fix = { 0 }; struct mbuf *offer = mbuf_alloc(str_len(sdp)); if (!offer) return ENOMEM; int err = mbuf_write_str(offer, sdp); if (err) goto out; mbuf_set_pos(offer, 0); err = fixture_init(&fix, session_attrv, session_attrc, media_attrv, media_attrc, codecv, codecc); TEST_ERR(err); err = sdp_decode(fix.sess, offer, true); TEST_ERR(err); for (size_t i=0; iname); TEST_STRCMP_LEN(attr->value, rattr); } struct args args = { .attrv = media_attrv, .attrc = media_attrc }; sdp_media_rattr_apply(fix.audio, NULL, sdp_attr_handler, &args); ASSERT_EQ(media_attrc, args.ix); TEST_STRCMP_LEN("UDP/TLS/RTP/SAVPF", sdp_media_proto(fix.audio)); const struct sdp_format *audio_fmt = sdp_media_rformat(fix.audio, NULL); TEST_STRCMP_LEN("opus", audio_fmt->name); const struct sdp_format *video_fmt = sdp_media_lformat(fix.video, 102); TEST_ASSERT(video_fmt); TEST_STRCMP_LEN("H264", video_fmt->name); out: fixture_close(&fix); mem_deref(offer); return err; } int test_sdp_interop(void) { static const char sdp_chrome[] = "v=0\r\n" "o=- 6851975412855494469 2 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "a=group:BUNDLE 0 1\r\n" "a=extmap-allow-mixed\r\n" "a=msid-semantic: WMS 2a30d377-cd13-4454-974c-0144db0118a6\r\n" "m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126\r\n" "c=IN IP4 0.0.0.0\r\n" "a=rtcp:9 IN IP4 0.0.0.0\r\n" "a=ice-ufrag:ie02\r\n" "a=ice-pwd:CWO5WLKWo2j5QmsY1396cvFe\r\n" "a=ice-options:trickle\r\n" "a=fingerprint:sha-256" " A9:1F:F1:FD:FB:90:7D:4D:F7:DF:C4:6E:F8:6A:7B:E7" ":87:1B:07:4E:22:3C:80:99:83:E6:9A:34:BD:93:F5:CE\r\n" "a=setup:actpass\r\n" "a=mid:0\r\n" "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" "a=extmap:2" " http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" "a=extmap:3 http://www.ietf.org/id/" "draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" "a=sendrecv\r\n" "a=msid:2a30d377-cd13-4454-974c-0144db0118a6" " c061a2b9-95bc-45fb-9e5d-6df08d8e1d0f\r\n" "a=rtcp-mux\r\n" "a=rtcp-rsize\r\n" "a=rtpmap:111 opus/48000/2\r\n" "a=rtcp-fb:111 transport-cc\r\n" "a=fmtp:111 minptime=10;useinbandfec=1\r\n" "a=rtpmap:63 red/48000/2\r\n" "a=fmtp:63 111/111\r\n" "a=rtpmap:9 G722/8000\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:13 CN/8000\r\n" "a=rtpmap:110 telephone-event/48000\r\n" "a=rtpmap:126 telephone-event/8000\r\n" "a=ssrc:2161565476 cname:P6e47zI3iVPviKRL\r\n" "a=ssrc:2161565476 msid:2a30d377-cd13-4454-974c-0144db0118a6" " c061a2b9-95bc-45fb-9e5d-6df08d8e1d0f\r\n" "m=video 29942 RTP/AVP 102 104 106 108 127 112 116\r\n" "b=AS:3072\r\n" "a=rtpmap:102 H264/90000\r\n" "a=fmtp:102 level-asymmetry-allowed=1;" "packetization-mode=1;profile-level-id=42001f\r\n" "a=rtpmap:104 H264/90000\r\n" "a=fmtp:104 level-asymmetry-allowed=1;" "packetization-mode=0;profile-level-id=42001f\r\n" "a=rtpmap:106 H264/90000\r\n" "a=fmtp:106 level-asymmetry-allowed=1;" "packetization-mode=1;profile-level-id=42e01f\r\n" "a=rtpmap:108 H264/90000\r\n" "a=fmtp:108 level-asymmetry-allowed=1;" "packetization-mode=0;profile-level-id=42e01f\r\n" "a=rtpmap:127 H264/90000\r\n" "a=fmtp:127 level-asymmetry-allowed=1;" "packetization-mode=1;profile-level-id=4d001f\r\n" "a=rtpmap:112 H264/90000\r\n" "a=fmtp:112 level-asymmetry-allowed=1;" "packetization-mode=1;profile-level-id=64001f\r\n" "a=rtpmap:116 H265/90000\r\n" "a=fmtp:116 level-id=93;profile-id=1;tier-flag=0;tx-mode=SRST\r\n" "a=rtcp-fb:102 ccm fir\r\n" "a=rtcp-fb:102 ccm tmmbr\r\n" "a=rtcp-fb:102 nack\r\n" "a=rtcp-fb:102 nack pli\r\n" "a=rtcp-fb:104 ccm fir\r\n" "a=rtcp-fb:104 ccm tmmbr\r\n" "a=rtcp-fb:104 nack\r\n" "a=rtcp-fb:104 nack pli\r\n" "a=rtcp-fb:106 ccm fir\r\n" "a=rtcp-fb:106 ccm tmmbr\r\n" "a=rtcp-fb:106 nack\r\n" "a=rtcp-fb:106 nack pli\r\n" "a=rtcp-fb:108 ccm fir\r\n" "a=rtcp-fb:108 ccm tmmbr\r\n" "a=rtcp-fb:108 nack\r\n" "a=rtcp-fb:108 nack pli\r\n" "a=rtcp-fb:127 ccm fir\r\n" "a=rtcp-fb:127 ccm tmmbr\r\n" "a=rtcp-fb:127 nack\r\n" "a=rtcp-fb:127 nack pli\r\n" "a=rtcp-fb:112 ccm fir\r\n" "a=rtcp-fb:112 ccm tmmbr\r\n" "a=rtcp-fb:112 nack\r\n" "a=rtcp-fb:112 nack pli\r\n" "a=rtcp-fb:116 ccm fir\r\n" "a=rtcp-fb:116 ccm tmmbr\r\n" "a=rtcp-fb:116 nack\r\n" "a=rtcp-fb:116 nack pli\r\n" ; static const struct attr session_attrv[] = { {"group", "BUNDLE 0 1"}, {"extmap-allow-mixed", ""}, {"msid-semantic", " WMS 2a30d377-cd13-4454-974c-0144db0118a6"}, }; static const struct attr audio_attrv[] = { {"ice-ufrag", "ie02"}, {"ice-pwd", "CWO5WLKWo2j5QmsY1396cvFe"}, {"ice-options", "trickle"}, {"fingerprint", "sha-256 A9:1F:F1:FD:FB:90:7D:4D:F7:DF:C4:6E:F8:6A" ":7B:E7:87:1B:07:4E:22:3C:80" ":99:83:E6:9A:34:BD:93:F5:CE"}, {"setup", "actpass"}, {"mid", "0"}, {"extmap", "1 urn:ietf:params:rtp-hdrext:ssrc-audio-level"}, {"extmap", "2 http://www.webrtc.org/experiments/" "rtp-hdrext/abs-send-time"}, {"extmap", "3 http://www.ietf.org/id/" "draft-holmer-rmcat-transport-wide-cc-extensions-01"}, {"extmap", "4 urn:ietf:params:rtp-hdrext:sdes:mid"}, {"msid", "2a30d377-cd13-4454-974c-0144db0118a6" " c061a2b9-95bc-45fb-9e5d-6df08d8e1d0f"}, {"rtcp-mux", ""}, {"rtcp-rsize", ""}, {"rtcp-fb", "111 transport-cc"}, {"ssrc", "2161565476 cname:P6e47zI3iVPviKRL"}, {"ssrc", "2161565476 msid:2a30d377-cd13-4454-974c-0144db0118a6" " c061a2b9-95bc-45fb-9e5d-6df08d8e1d0f"}, }; static const struct codec codecv[] = { { NULL, "opus", 48000, 2, true}, { "63", "red", 48000, 2, true}, { "9", "G722", 8000, 1, true}, { "0", "PCMU", 8000, 1, true}, { "8", "PCMA", 8000, 1, true}, { "13", "CN", 8000, 1, true}, { "110", "telephone-event", 48000, 1, true}, { "126", "telephone-event", 8000, 1, true}, { "150", "H264", 90000, 1, false}, }; int err; err = test_sdp_param(sdp_chrome, session_attrv, RE_ARRAY_SIZE(session_attrv), audio_attrv, RE_ARRAY_SIZE(audio_attrv), codecv, RE_ARRAY_SIZE(codecv)); TEST_ERR(err); out: return err; } ================================================ FILE: test/sha.c ================================================ /** * @file sha.c SHA Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "testsha1" #define DEBUG_LEVEL 4 #include static const char *test_data[] = { "abc", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", /* 64 bytes */ "9293haoijsdlasjd9ehr98wehrlsihdflskidjflaisjdlaisdjalsdkjasdlsda", /* 96 bytes */ "9293haoijsdlasjd9ehr98wehrlsihdflskidjflaisjdlaisdjalsdkjasdlsda" "9293haoijsdlasjd9ehr98wehrlsihdf", /* 128 bytes */ "9293haoijsdlasjd9ehr98wehrlsihdflskidjflaisjdlaisdjalsdkjasdlsda" "9293haoijsdlasjd82halsdlkajsdlkjasldkjasldjlskjd9ehr98wehrlsihdd", /* 256 bytes */ "9293haoijsdlasjd9ehr98wehrlsihdflskidjflaisjdlaisdjalsdkjasdlsda" "9293haoijsdlasjd82halsdlkajsdlkjasldkjasldjlskjd9ehr98wehrlsihdd" "9293haoijsdlasjd9ehr98wehrlsihdflskidjflaisjdlaisdjalsdkjasdlsda" "9293haoijsdlasjd82halsdlkajsdlkjasldkjasldjlskjd9ehr98wehrlsihdd", }; static const char *test_results[] = { "a9993e364706816aba3e25717850c26c9cd0d89d", "84983e441c3bd26ebaae4aa1f95129e5e54670f1", "105104a6ee22de58c0888d2f9cdd56d95c14d4e7", "9962f530d85f354304efcf35ceaa29a279a3208d", "17307171329ed5aeaccf4cd4f6d02223a69af9fb", "4f051b5c4fcd0916df00f9c9dbab8608cd3355a7"}; int test_sha1(void) { uint32_t k; uint8_t digest[20]; char output[80]; for (k = 0; k < RE_ARRAY_SIZE(test_data); k++) { sha1((uint8_t *)test_data[k], strlen(test_data[k]), digest); (void)re_snprintf(output, sizeof(output), "%02w", digest, sizeof(digest)); if (strcmp(output, test_results[k])) { DEBUG_WARNING("* hash of \"%s\" incorrect:\n", test_data[k]); DEBUG_WARNING("\t%s returned\n", output); DEBUG_WARNING("\t%s is correct\n", test_results[k]); return EINVAL; } } /* success */ return 0; } ================================================ FILE: test/sip.c ================================================ /** * @file sip.c SIP Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "siptest" #define DEBUG_LEVEL 5 #include static int sip_addr_encode(const struct sip_addr *addr, struct mbuf *mb) { bool anglebr = addr->dname.p || addr->uri.params.p; int err; if (addr->dname.p) { err = mbuf_printf(mb, "\"%r\" ", &addr->dname); if (err) return err; } if (anglebr) { err = mbuf_write_u8(mb, '<'); if (err) return err; } err = mbuf_printf(mb, "%H", uri_encode, &addr->uri); if (err) return err; if (anglebr) { err = mbuf_write_u8(mb, '>'); if (err) return err; } if (addr->params.p) { err = mbuf_write_pl(mb, &addr->params); if (err) return err; } return 0; } int test_sip_addr(void) { const char *addrv[] = { /* With Display name */ "\"Agmund Bolt\" ", "\"Agmund Bolt\" ", "\"Agmund Bolt\" ", "\"Agmund Bolt\" ", "\"Agmund Bolt\" ", "\"Agmund Bolt\" ", "", "", "", "", "", "", "", "", /* RFC 3261 */ "", "", "", "", "", "", /* With address parameters */ "\"Agmund Bolt\" " ";tag=foo123", "\"Agmund Bolt\" " ";tag=foo123;bar=9d7j3", /* RFC 5118 - SIP Torture Test Messages for IPv6 */ "\"Caller\" ", "\"Caller\" ", /* gruu */ "\"hei\" " ";expires=3845" ";gruu=\"sip:alfred1@devel.sip.su.se" ";opaque=t49sgjnwu8;gruu\"" ";+sip.instance=" "\"\"" ";reg-id=1", }; struct sip_addr addr; struct mbuf mb; int err = EINVAL; size_t i; mbuf_init(&mb); for (i=0; ivia.tp != testv[i].tp) { DEBUG_WARNING("%u: via transp: '%s' != '%s'\n", i, sip_transp_name(msg->via.tp), sip_transp_name(testv[i].tp)); goto out; } /* Numeric IP address */ if (ipaddr) { if (!sa_cmp(&msg->via.addr, &addr, SA_ALL)) { DEBUG_WARNING("%u: via addr: addr=%J\n", i, &msg->via.addr); err = EINVAL; goto out; } } else { err = pl_strcmp(&msg->via.sentby, testv[i].host); if (err) { DEBUG_WARNING("%u: via uri: sentby='%r'\n", i, &msg->via.sentby); goto out; } if (sa_port(&msg->via.addr) != testv[i].port) { DEBUG_WARNING("%u: via: port mismatch (%u)\n", i, sa_port(&msg->via.addr)); err = EINVAL; goto out; } } err = pl_strcmp(&msg->via.branch, testv[i].branch); if (err) { DEBUG_WARNING("%u: via branch: '%r' != '%s'\n", i, &msg->via.branch, testv[i].branch); goto out; } msg = mem_deref(msg); } err = 0; out: mem_deref(mb); mem_deref(msg); return err; } struct apply { uint32_t n; int err; }; static bool apply_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { const char *ref = "SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123"; struct apply *apply = arg; (void)msg; (void)arg; if (hdr->id != SIP_HDR_VIA) { apply->err = EINVAL; return true; } apply->err = pl_strcmp(&hdr->val, ref); if (apply->err) { return true; } ++apply->n; return false; } int test_sip_apply(void) { struct { uint32_t n; const char *msg; } testv[] = { {0, "Tull: tull\r\n"}, {1, "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n"}, {1, "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n"}, {2, "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n" "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n" }, {2, "Via: SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123," " SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123\r\n" }, {2, "v: SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123," " SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123\r\n" }, {2, "v: SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123" ",SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123\r\n" }, {3, "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n" "v: SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123\r\n" "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n" }, {3, "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n" "Tull: tull\r\n" "v: SIP/2.0/UDP 123.45.67.89:12345;branch=z9hG4bK123\r\n" "Via: SIP/2.0/UDP 123.45.67.89:12345" ";branch=z9hG4bK123\r\n" }, }; struct sip_msg *msg = NULL; struct mbuf *mb; int err = 0; size_t i; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } for (i=0; i\r\n" "t: Bob \r\n" "f: Alice \r\n" " ;tag=1928301774\r\n" "Call-ID : a84b4c76e66710@pc33.atlanta.com\r\n" "CSeq : 314159 INVITE\r\n" "Contact: \r\n" "Content-Type: application/sdp\r\n" "Content-Length: 142\r\n" "\r\n"; const char hdr_maxf[] = "70"; const char hdr_from[] = "sip:alice@atlanta.com"; const char hdr_to[] = "sip:bob@biloxi.com"; const char hdr_callid[] = "a84b4c76e66710@pc33.atlanta.com"; struct mbuf *mb; struct sip_msg *msg = NULL; int err = EINVAL; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } err = mbuf_write_str(mb, str_raw); if (err) goto out; mbuf_set_pos(mb, 0); err = sip_msg_decode(&msg, mb); if (err) { goto out; } /* Max-Forwards */ err = pl_strcmp(&msg->maxfwd, hdr_maxf); if (err) goto out; /* From */ err = pl_strcmp(&msg->from.auri, hdr_from); if (err) { DEBUG_WARNING("from header mismatch (%r)\n", &msg->from.auri); goto out; } /* To */ err = pl_strcmp(&msg->to.auri, hdr_to); if (err) { DEBUG_WARNING("to header mismatch\n"); goto out; } /* Call-ID */ err = pl_strcmp(&msg->callid, hdr_callid); if (err) { DEBUG_WARNING("callid header mismatch\n"); goto out; } /* CSeq */ if (314159 != msg->cseq.num) { DEBUG_WARNING("cseq number mismatch\n"); goto out; } err = pl_strcmp(&msg->cseq.met, "INVITE"); if (err) { DEBUG_WARNING("cseq method mismatch\n"); goto out; } /* Content-Type */ if (!msg_ctype_cmp(&msg->ctyp, "application", "sdp")) { DEBUG_WARNING("content type mismatch (%r/%r)\n", &msg->ctyp.type, &msg->ctyp.subtype); err = EBADMSG; goto out; } /* Content-Length */ if (142 != pl_u32(&msg->clen)) { DEBUG_WARNING("content length mismatch\n"); err = EINVAL; goto out; } err = 0; out: mem_deref(mb); mem_deref(msg); return err; } static bool count_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, void *arg) { (void)hdr; (void)msg; ++(*(uint32_t *)arg); return false; } static uint32_t xhdr_count(const struct sip_msg *msg, const char *name) { uint32_t n = 0; sip_msg_xhdr_apply(msg, true, name, count_handler, &n); return n; } int test_sip_hdr(void) { const char str[] = "REGISTER sip:telio.no SIP/2.0\r\n" "Via: SIP/2.0/UDP 85.119.136.184:5080" " ;branch=z9hG4bKe282.0c5b6835.0;i=2b505\r\n" "Via: SIP/2.0/TCP 172.17.18.219:5060;received=85.0.35.235" " ;branch=z9hG4bK6ec163d6cebbbe491e1940b91.1;rport=49505\r\n" "Call-ID: 2e60298e76751681@172.17.18.219\r\n" "CSeq: 67139 REGISTER\r\n" "Contact: \r\n" "From: ;tag=1ea582725e044bf6\r\n" "To: \r\n" "Max-Forwards: 16\r\n" "Allow: INVITE,ACK,CANCEL,BYE,UPDATE,INFO,OPTIONS\r\n" "User-Agent: TANDBERG/67 (F7.2 PAL)\r\n" "Expires: 3600\r\n" "Supported: replaces,100rel,timer\r\n" "Content-Length: 0\r\n" "\r\n"; struct mbuf *mb; struct sip_msg *msg = NULL; int err = EINVAL; mb = mbuf_alloc(1024); if (!mb) return ENOMEM; err = mbuf_write_str(mb, str); if (err) goto out; mbuf_set_pos(mb, 0); err = sip_msg_decode(&msg, mb); if (err) goto out; if (xhdr_count(msg, "Call-ID") != 1) { err = EBADMSG; goto out; } if (xhdr_count(msg, "Supported") != 3) { err = EBADMSG; goto out; } if (xhdr_count(msg, "Allow") != 7) { err = EBADMSG; goto out; } if (xhdr_count(msg, "NonExisting") != 0) { err = EBADMSG; goto out; } out: mem_deref(msg); mem_deref(mb); return err; } /** SIP Authenticated Request */ struct sip_req { struct sip_request *req; struct sip_auth *auth; struct sip_dialog *dlg; struct sip *sip; }; static int do_sip_drequestf(struct sa *laddr) { struct sip_req *sr; int err; char uri[64]; char touri[64]; sr = mem_zalloc(sizeof(*sr), NULL); if (!sr) return ENOMEM; re_snprintf(uri, sizeof(uri), "sip:%J;transport=UDP", laddr); re_snprintf(touri, sizeof(touri), "sip:test@%J", laddr); err = sip_dialog_alloc(&sr->dlg, uri, touri, NULL, touri, NULL, 0); TEST_ERR(err); err = sip_auth_alloc(&sr->auth, NULL, NULL, false); TEST_ERR(err); err = sip_alloc(&sr->sip, NULL, 32, 32, 32, "retest", NULL, NULL); TEST_ERR(err); err = sip_transp_add(sr->sip, SIP_TRANSP_UDP, laddr); TEST_ERR(err); err = sip_drequestf(&sr->req, sr->sip, true, "REGISTER", sr->dlg, 0, sr->auth, NULL, NULL, NULL, ""); TEST_ERR(err); out: mem_deref(sr->dlg); mem_deref(sr->auth); mem_deref(sr->sip); mem_deref(sr); return err; } int test_sip_drequestf(void) { int err; struct sa laddr; err = sa_set_str(&laddr, "127.0.0.1", 0); TEST_ERR(err); err = do_sip_drequestf(&laddr); TEST_ERR(err); out: return err; } int test_sip_drequestf_network(void) { struct sa laddr; int err = 0; sa_init(&laddr, AF_INET6); if (0 == net_if_getlinklocal(NULL, AF_INET6, &laddr)) { err = do_sip_drequestf(&laddr); TEST_ERR(err); } out: return err; } #ifdef USE_TLS struct sip_transp_tls { struct sip *sip; struct tls *tls; struct uri uri; const char *ccert_cn; }; int test_sip_transp_add_client_cert(void) { struct sip_transp_tls *stt; struct sa laddr; int err; char clientcert[256]; char cafile[256]; const char *user = "abcd"; const char *scheme = "sip"; const char *host = "localhost"; const uint16_t port = 5061; memset(clientcert, 0, sizeof(clientcert)); (void)re_snprintf(clientcert, sizeof(clientcert), "%s/client.pem", test_datapath()); stt = mem_zalloc(sizeof(*stt), NULL); if (!stt) return ENOMEM; pl_set_str(&stt->uri.user, user); pl_set_str(&stt->uri.scheme, scheme); pl_set_str(&stt->uri.host, host); stt->uri.port = port; err = sa_set_str(&laddr, "127.0.0.1", 0); TEST_ERR(err); err = tls_alloc(&stt->tls, TLS_METHOD_SSLV23, NULL, NULL); TEST_ERR(err); (void)re_snprintf(cafile, sizeof(cafile), "%s/server-ecdsa.pem", test_datapath()); err = tls_add_ca(stt->tls, cafile); TEST_ERR(err); err = sip_alloc(&stt->sip, NULL, 32, 32, 32, "retest", NULL, NULL); TEST_ERR(err); err = sip_transp_add(stt->sip, SIP_TRANSP_TLS, &laddr, stt->tls); TEST_ERR(err); /* actual test cases */ err = sip_transp_add_ccert(NULL, &stt->uri, clientcert); if (err == EINVAL) { err = 0; goto out; } TEST_ERR(err); err = sip_transp_add_ccert(stt->sip, NULL, clientcert); if (err == EINVAL) { err = 0; goto out; } TEST_ERR(err); err = sip_transp_add_ccert(stt->sip, &stt->uri, NULL); if (err == EINVAL) { err = 0; goto out; } TEST_ERR(err); err = sip_transp_add_ccert(stt->sip, &stt->uri, clientcert); TEST_EQUALS(0, err); out: mem_deref(stt->sip); mem_deref(stt->tls); mem_deref(stt); return err; } #endif struct sip_dns { int req1_err; int req2_err; int req3_err; bool req1_done; bool req2_done; bool req3_done; }; static void req1_handler(int err, const struct sip_msg *msg, void *arg) { struct sip_dns *sipdns = arg; (void)msg; DEBUG_INFO("req1_handler: err=%m\n", err); sipdns->req1_err = err; sipdns->req1_done = true; re_cancel(); } static void req2_handler(int err, const struct sip_msg *msg, void *arg) { struct sip_dns *sipdns = arg; (void)msg; DEBUG_INFO("req2_handler: err=%m\n", err); sipdns->req2_err = err; sipdns->req2_done = true; if (sipdns->req3_done) re_cancel(); } static void req3_handler(int err, const struct sip_msg *msg, void *arg) { struct sip_dns *sipdns = arg; (void)msg; DEBUG_INFO("req3_handler: err=%m\n", err); sipdns->req3_err = err; sipdns->req3_done = true; if (sipdns->req2_done) re_cancel(); } /* Send a SIP request to example.com with mock DNS server and mock SIP server. * After response has been received, send two SIP reqeusts to example.com * simultanesously. Both requests must work. Tests DNS and DNS caching. */ int test_sip_dns(void) { char from_uri[256]; char to_uri[256]; struct sip_server *sipsrv = NULL; struct dns_server *dnssrv = NULL; struct dnsc *dnsc = NULL; struct sip *sip = NULL; struct sip_auth *auth = NULL; struct sip_dialog *dlg = NULL; struct sip_request *req1 = NULL; struct sip_request *req2 = NULL; struct sip_request *req3 = NULL; struct sa laddr, sipsrv_addr; int err = 0; struct sip_dns sipdns; memset(&sipdns, 0, sizeof(sipdns)); /* Set up mock SIP server */ err = sip_server_alloc(&sipsrv); TEST_ERR(err); /* Get SIP server address */ err = sip_transp_laddr(sipsrv->sip, &sipsrv_addr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); DEBUG_INFO("SIP server listening on %J\n", &sipsrv_addr); /* Set up mock DNS server */ err = dns_server_alloc(&dnssrv, "127.0.0.1"); TEST_ERR(err); /* Add A record pointing to SIP server */ err = dns_server_add_a(dnssrv, "example.com", sa_in(&sipsrv_addr), 1); TEST_ERR(err); DEBUG_INFO("DNS resolves example.com to %J\n", &sipsrv_addr); /* Create DNS client */ err = dnsc_alloc(&dnsc, NULL, &dnssrv->addr, 1); TEST_ERR(err); /* Set up SIP stack with both IPv4 and IPv6 support */ err = sip_alloc(&sip, dnsc, 32, 32, 32, "retest", NULL, NULL); TEST_ERR(err); /* Create SIP auth object */ err = sip_auth_alloc(&auth, NULL, NULL, false); TEST_ERR(err); /* Add IPv4 transport */ err = sa_set_str(&laddr, "127.0.0.1", 0); TEST_ERR(err); err = sip_transp_add(sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); /* Add IPv6 transport */ err = sa_set_str(&laddr, "::1", 0); TEST_ERR(err); err = sip_transp_add(sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); /* Build From and To URIs */ re_snprintf(from_uri, sizeof(from_uri), "sip:test@127.0.0.1"); re_snprintf(to_uri, sizeof(to_uri), "sip:user@example.com:%u", sa_port(&sipsrv_addr)); err = sip_dialog_alloc(&dlg, to_uri, to_uri, NULL, from_uri, NULL, 0); TEST_ERR(err); err = sip_drequestf(&req1, sip, true, "OPTIONS", dlg, 0, auth, NULL, req1_handler, &sipdns, "Content-Length: 0\r\n\r\n"); TEST_ERR(err); err = re_main_timeout(500); TEST_ERR(err); ASSERT_TRUE(sipdns.req1_done); err = sip_drequestf(&req2, sip, true, "OPTIONS", dlg, 0, auth, NULL, req2_handler, &sipdns, "Content-Length: 0\r\n\r\n"); TEST_ERR(err); /* Send third request immediately */ err = sip_drequestf(&req3, sip, true, "OPTIONS", dlg, 0, auth, NULL, req3_handler, &sipdns, "Content-Length: 0\r\n\r\n"); TEST_ERR(err); err = re_main_timeout(500); TEST_ERR(err); /* Verify handlers were called */ ASSERT_TRUE(sipdns.req2_done); ASSERT_TRUE(sipdns.req3_done); /* Verify requests reached the transport layer */ ASSERT_EQ(0, sipdns.req2_err); ASSERT_EQ(0, sipdns.req3_err); /* Verify all three OPTIONS were received */ ASSERT_EQ(3, sipsrv->n_options_req); out: mem_deref(req3); mem_deref(req2); mem_deref(req1); mem_deref(dlg); mem_deref(auth); if (sip) { sip_close(sip, false); mem_deref(sip); } mem_deref(dnsc); mem_deref(dnssrv); mem_deref(sipsrv); return err; } ================================================ FILE: test/sipauth.c ================================================ /** * @file sipauth.c SIP Auth testcode */ #include #include "test.h" #define DEBUG_MODULE "test_sipauth" #define DEBUG_LEVEL 5 #include static const char *testv[] = { /* without algorithm - default MD5 */ "SIP/2.0 401 Unauthorized\r\n" "Via: SIP/2.0/TLS " "10.0.0.1:37589;branch=z9hG4bK5625ce6f310a0fc8;rport=13718;" "received=10.0.0.2\r\n" "WWW-Authenticate: Digest realm=\"example.net\", " "nonce=\"YZlVk2GZVGegVBZVKaMHpnxmUA+QyoSl\"\r\n" "Content-Length: 0\r\n\r\n", /* explicit MD5 */ "SIP/2.0 401 Unauthorized\r\n" "Via: SIP/2.0/TLS " "10.0.0.1:37589;branch=z9hG4bK5625ce6f310a0fc8;rport=13718;" "received=10.0.0.2\r\n" "WWW-Authenticate: Digest realm=\"example.net\", " "algorithm=\"MD5\", " "nonce=\"YZlVk2GZVGegVBZVKaMHpnxmUA+QyoSl\"\r\n" "Content-Length: 0\r\n\r\n", /* explicit SHA-256 */ "SIP/2.0 401 Unauthorized\r\n" "Via: SIP/2.0/TLS " "10.0.0.1:37589;branch=z9hG4bK5625ce6f310a0fc8;rport=13718;" "received=10.0.0.2\r\n" "WWW-Authenticate: Digest realm=\"example.net\", " "algorithm=\"SHA-256\", " "nonce=\"YZlVk2GZVGegVBZVKaMHpnxmUA+QyoSl\"\r\n" "Content-Length: 0\r\n\r\n", /* explicit SHA-256 qop */ "SIP/2.0 401 Unauthorized\r\n" "Via: SIP/2.0/TLS " "10.0.0.1:37589;branch=z9hG4bK5625ce6f310a0fc8;rport=13718;" "received=10.0.0.2\r\n" "WWW-Authenticate: Digest realm=\"example.net\", " "algorithm=\"SHA-256\", " "qop=\"auth\", " "nonce=\"YZlVk2GZVGegVBZVKaMHpnxmUA+QyoS\"\r\n" "Content-Length: 0\r\n\r\n" }; static const char *testr[] = { "algorithm=MD5", "algorithm=MD5", "algorithm=SHA-256", "algorithm=SHA-256" }; static int auth_handler(char **user, char **pass, const char *rlm, void *arg) { (void)user; (void)pass; (void)rlm; (void)arg; return 0; } static int test_sip_auth_encode(void) { int err = 0; struct mbuf *mb, *mb_enc; struct sip_auth *auth = NULL; char buf[1024] = {0}; struct sip_msg *msg = NULL; const char met[] = "REGISTER"; const char uri[] = ""; mb = mbuf_alloc(2048); if (!mb) return ENOMEM; mb_enc = mbuf_alloc(2048); if (!mb_enc) { mem_deref(mb); return ENOMEM; } for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { mbuf_rewind(mb); mbuf_rewind(mb_enc); err = sip_auth_alloc(&auth, auth_handler, NULL, false); TEST_ERR(err); err = mbuf_write_str(mb, testv[i]); TEST_ERR(err); mbuf_set_pos(mb, 0); err = sip_msg_decode(&msg, mb); TEST_ERR(err); err = sip_auth_authenticate(auth, msg); TEST_ERR(err); err = sip_auth_encode(mb_enc, auth, met, uri); TEST_ERR(err); mbuf_set_pos(mb_enc, 0); mbuf_read_str(mb_enc, buf, mbuf_get_left(mb_enc)); err = re_regex(buf, str_len(buf), testr[i]); TEST_ERR(err); mem_deref(msg); mem_deref(auth); } out: mem_deref(mb); mem_deref(mb_enc); if (err) { mem_deref(msg); mem_deref(auth); } return err; } int test_sip_auth(void) { int err; err = test_sip_auth_encode(); TEST_ERR(err); out: return err; } ================================================ FILE: test/sipevent.c ================================================ /** * @file sipevent.c SIP Event regression testcode * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_sipevent" #define DEBUG_LEVEL 5 #include /* * A is the subscriber to event changes from B * * .-----. .-----. * | A | | B | * '-----' '-----' * (Subscriber) (Notifier) * * ----- SUBSCRIBE -----> * * <----- 200 OK -------- * * * * * <------- X somekind of event happened * <----- NOTIFY -------- * X <-- * ------- 200 OK ------> * */ struct agent { struct agent *peer; struct sip *sip; struct sipevent_sock *sock; struct sipsub *sub; struct sipnot *not; char name[32]; bool exited; char uri[256]; unsigned subc; unsigned notc; unsigned closec; int err; }; static const char *test_event = "my-event"; static void complete(struct agent *ag, int err) { ag->err = err; re_cancel(); } static int send_notify(struct agent *ag) { const char *aor = "tull"; struct mbuf *mb; int err; mb = mbuf_alloc(1024); if (!mb) return ENOMEM; err = mbuf_printf(mb, "\r\n" "\r\n" " \r\n" " \r\n" " \r\n" " %s\r\n" " \r\n" " %s\r\n" " \r\n" "\r\n" ,aor, "open", aor); if (err) goto out; mb->pos = 0; err = sipevent_notify(ag->not, mb, SIPEVENT_ACTIVE, 0, 0); if (err) { DEBUG_WARNING("presence: notify to %s failed (%m)\n", aor, err); } out: mem_deref(mb); return err; } static void sipnot_close_handler(int err, const struct sip_msg *msg, void *arg) { struct agent *ag = arg; (void)msg; DEBUG_WARNING("[ %s ] sip notification closed (%m)\n", ag->name, err); ++ag->closec; complete(ag, err); } /* * Agent `B' -- the Notifier * * Handle incoming SUBSCRIBE message from A SUBSCRIBE sip:b@127.0.0.1:20000 SIP/2.0. Via: SIP/2.0/UDP 127.0.0.1:10000;branch=z9hG4bKf7f2e9e48bbaea6b;rport. Contact: . Max-Forwards: 70. To: . From: "a" ;tag=d3d00d9fb5ee45d5. Call-ID: 26ac32ea11a58011. CSeq: 37745 SUBSCRIBE. User-Agent: a. Event: my-event. Expires: 600. Content-Length: 0. */ static bool subscribe_handler(const struct sip_msg *msg, void *arg) { struct agent *ag = arg; struct agent *peer = ag->peer; const struct sip_hdr *hdr; struct sipevent_event se; int err = 0; DEBUG_INFO("[ %s ] recv SIP msg (%r)\n", ag->name, &msg->met); ++ag->subc; TEST_ASSERT(msg != NULL); TEST_STRCMP("SUBSCRIBE", 9U, msg->met.p, msg->met.l); hdr = sip_msg_hdr(msg, SIP_HDR_CONTACT); TEST_ASSERT(hdr != NULL); TEST_STRCMP(ag->uri, strlen(ag->uri), msg->to.auri.p, msg->to.auri.l); TEST_STRCMP(peer->uri, strlen(peer->uri), msg->from.auri.p, msg->from.auri.l); TEST_STRCMP("SUBSCRIBE", 9U, msg->cseq.met.p, msg->cseq.met.l); TEST_ASSERT(pl_u32(&msg->expires) > 0); hdr = sip_msg_hdr(msg, SIP_HDR_EVENT); if (!hdr) { err = EPROTO; goto out; } err = sipevent_event_decode(&se, &hdr->val); if (err) goto out; if (pl_strcasecmp(&se.event, test_event)) { DEBUG_WARNING("presence: unexpected event '%r'\n", &se.event); err = EPROTO; goto out; } err = sipevent_accept(&ag->not, ag->sock, msg, NULL, &se, 200, "OK", 600, 600, 600, ag->name, "application/pidf", NULL, NULL, false, sipnot_close_handler, ag, NULL); if (err) goto out; err = send_notify(ag); if (err) goto out; out: if (err) { complete(ag, err); } return true; } /* * Agent `A' -- the Subscriber * * handle incoming NOTIFY messages from B * NOTIFY sip:a@127.0.0.1:10000 SIP/2.0. Via: SIP/2.0/UDP 127.0.0.1:20000;branch=z9hG4bK056ce9e0dabdea16;rport. Contact: . Max-Forwards: 70. To: "a" ;tag=b8c2a38adecb1bea. From: ;tag=426f09aed9f32053. Call-ID: f3b22da3f4223935. CSeq: 7364 NOTIFY. User-Agent: b. Event: my-event. Subscription-State: active;expires=600. Content-Type: application/pidf. Content-Length: 417. */ static void sipsub_notify_handler(struct sip *sip, const struct sip_msg *msg, void *arg) { struct agent *ag = arg; struct agent *peer = ag->peer; const struct sip_hdr *hdr; struct sipevent_substate substate; int err = 0; (void)sip; DEBUG_INFO("[ %s ] subscriber -- incoming notify\n", ag->name); TEST_ASSERT(NULL != ag->sub); ++ag->notc; /* verify the SIP message */ TEST_ASSERT(msg != NULL); TEST_STRCMP("NOTIFY", 6U, msg->met.p, msg->met.l); hdr = sip_msg_hdr(msg, SIP_HDR_CONTACT); TEST_ASSERT(hdr != NULL); TEST_STRCMP(ag->uri, strlen(ag->uri), msg->to.auri.p, msg->to.auri.l); TEST_STRCMP(peer->uri, strlen(peer->uri), msg->from.auri.p, msg->from.auri.l); TEST_STRCMP("NOTIFY", 6U, msg->cseq.met.p, msg->cseq.met.l); hdr = sip_msg_hdr(msg, SIP_HDR_EVENT); TEST_ASSERT(hdr != NULL); TEST_STRCMP(test_event, str_len(test_event), hdr->val.p, hdr->val.l); hdr = sip_msg_hdr(msg, SIP_HDR_SUBSCRIPTION_STATE); TEST_ASSERT(hdr != NULL); err = sipevent_substate_decode(&substate, &hdr->val); TEST_ERR(err); /* verify that state is active */ TEST_EQUALS(SIPEVENT_ACTIVE, substate.state); TEST_ASSERT(pl_u32(&substate.expires) > 0); sip_treply(NULL, sip, msg, 200, "OK"); complete(ag, 0); return; out: if (err) complete(ag, err); } static void sipsub_close_handler(int err, const struct sip_msg *msg, const struct sipevent_substate *substate, void *arg) { struct agent *ag = arg; (void)msg; (void)substate; DEBUG_WARNING("[ %s ] subscriber -- closed (%m)\n", ag->name, err); ++ag->closec; complete(ag, err); } static void exit_handler(void *arg) { struct agent *ag = arg; ag->exited = true; if (ag->peer->exited) re_cancel(); } static void destructor(void *data) { struct agent *ag = data; mem_deref(ag->sub); mem_deref(ag->not); mem_deref(ag->sock); sip_close(ag->sip, true); mem_deref(ag->sip); } static int agent_alloc(struct agent **agp, const char *name, const struct sa *laddr) { struct sa sa; struct agent *ag; int err; ag = mem_zalloc(sizeof(*ag), destructor); if (!ag) return ENOMEM; str_ncpy(ag->name, name, sizeof(ag->name)); err = sip_alloc(&ag->sip, NULL, 32, 32, 32, name, exit_handler, ag); if (err) goto out; err = sip_transp_add(ag->sip, SIP_TRANSP_UDP, laddr); if (err) goto out; err = sip_transp_laddr(ag->sip, &sa, SIP_TRANSP_UDP, NULL); if (err) goto out; err = sipevent_listen(&ag->sock, ag->sip, 32, 32, subscribe_handler, ag); if (err) goto out; re_snprintf(ag->uri, sizeof(ag->uri), "sip:%s@%J", name, &sa); #if 0 re_printf("agent %s (%s)\n", name, ag->uri); #endif out: if (err) mem_deref(ag); else *agp = ag; return err; } static int agent_subscribe(struct agent *ag, struct agent *peer) { if (!ag || !peer) return EINVAL; return sipevent_subscribe(&ag->sub, ag->sock, peer->uri, ag->name, ag->uri, test_event, NULL, 600, ag->name, NULL, 0, NULL, NULL, false, NULL, sipsub_notify_handler, sipsub_close_handler, ag, NULL); } static int do_sipevent(struct sa *laddr) { struct agent *a = NULL, *b = NULL; int err = 0; err = agent_alloc(&a, "a", laddr); if (err) goto out; err = agent_alloc(&b, "b", laddr); if (err) goto out; a->peer = b; b->peer = a; err = agent_subscribe(a, b); if (err) goto out; err = re_main_timeout(500); if (err) goto out; TEST_ERR(a->err); TEST_ERR(b->err); TEST_EQUALS(0, a->subc); TEST_EQUALS(1, a->notc); TEST_EQUALS(0, a->closec); TEST_EQUALS(1, b->subc); TEST_EQUALS(0, b->notc); TEST_EQUALS(0, b->closec); out: mem_deref(b); mem_deref(a); return err; } int test_sipevent(void) { int err; struct sa laddr; err = sa_set_str(&laddr, "127.0.0.1", 0); TEST_ERR(err); err = do_sipevent(&laddr); out: return err; } int test_sipevent_network(void) { struct sa laddr; int err = 0; sa_init(&laddr, AF_INET6); if (0 == net_if_getlinklocal(NULL, AF_INET6, &laddr)) { err = do_sipevent(&laddr); } return err; } ================================================ FILE: test/sipreg.c ================================================ /** * @file sipreg.c SIP Register client regression testcode * * Copyright (C) 2010 - 2015 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_sipreg" #define DEBUG_LEVEL 5 #include #define LOCAL_PORT 0 #define LOCAL_SECURE_PORT 0 struct test { struct tmr tmr; enum sip_transp tp; unsigned n_resp; uint16_t srcport; int err; }; static void exit_handler(void *arg) { (void)arg; re_cancel(); } static void tmr_handler(void *arg) { struct test *test = arg; (void)test; re_cancel(); } static int sipstack_fixture(struct sip **sipp) { struct sa laddr, laddrs; struct sip *sip = NULL; struct tls *tls = NULL; #ifdef USE_TLS char cafile[256]; #endif int err; (void)sa_set_str(&laddr, "127.0.0.1", LOCAL_PORT); (void)sa_set_str(&laddrs, "127.0.0.1", LOCAL_SECURE_PORT); err = sip_alloc(&sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); if (err) goto out; err |= sip_transp_add(sip, SIP_TRANSP_UDP, &laddr); err |= sip_transp_add(sip, SIP_TRANSP_TCP, &laddr); if (err) goto out; #ifdef USE_TLS /* TLS-context for client -- no certificate needed */ err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; re_snprintf(cafile, sizeof(cafile), "%s/server-ecdsa.pem", test_datapath()); err = tls_add_ca(tls, cafile); if (err) goto out; err |= sip_transp_add(sip, SIP_TRANSP_TLS, &laddrs, tls); if (err) goto out; #endif out: mem_deref(tls); if (err) mem_deref(sip); else *sipp = sip; return err; } static void sip_resp_handler(int err, const struct sip_msg *msg, void *arg) { struct test *test = arg; if (err) { test->err = err; re_cancel(); return; } ++test->n_resp; /* verify the SIP response message */ TEST_ASSERT(msg != NULL); TEST_EQUALS(200, msg->scode); TEST_STRCMP("REGISTER", 8, msg->cseq.met.p, msg->cseq.met.l); TEST_EQUALS(test->tp, msg->tp); if (test->srcport) TEST_EQUALS(test->srcport, sa_port(&msg->dst)); out: if (err) test->err = err; /* done */ if (test->tp == SIP_TRANSP_UDP) tmr_start(&test->tmr, 50, tmr_handler, test); else re_cancel(); } #define CPARAMS "some-param=test;other-param=123;" static int reg_test(enum sip_transp tp, uint16_t srcport) { struct test test; struct sip_server *srv = NULL; struct sipreg *reg = NULL; struct sip *sip = NULL; const struct sip_hdr *contact_hdr = NULL; char reg_uri[256]; int err; memset(&test, 0, sizeof(test)); test.tp = tp; test.srcport = srcport; err = sip_server_alloc(&srv); TEST_ERR(err); err = sipstack_fixture(&sip); TEST_ERR(err); err = sip_server_uri(srv, reg_uri, sizeof(reg_uri), tp); TEST_ERR(err); const int regid = 1; err = sipreg_alloc(®, sip, reg_uri, "sip:x@test", NULL, "sip:x@test", 3600, "x", NULL, 0, regid, NULL, NULL, false, sip_resp_handler, &test, NULL, "X-Custom: 1234\r\n"); TEST_ERR(err); err = sipreg_set_contact_params(reg, CPARAMS); TEST_ERR(err); if (srcport) sipreg_set_srcport(reg, srcport); err = sipreg_send(reg); TEST_ERR(err); err = re_main_timeout(1000); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } TEST_ASSERT(srv->n_register_req > 0); TEST_ASSERT(test.n_resp > 0); contact_hdr = sip_msg_hdr( srv->sip_msgs[srv->n_register_req - 1], SIP_HDR_CONTACT); err = re_regex(contact_hdr->val.p, contact_hdr->val.l, ";" CPARAMS ">;expires=", NULL); TEST_ERR(err); ASSERT_TRUE(sipreg_registered(reg)); ASSERT_TRUE(!sipreg_failed(reg)); out: tmr_cancel(&test.tmr); sipreg_unregister(reg); mem_deref(reg); sip_close(sip, true); mem_deref(sip); mem_deref(srv); return err; } int test_sipreg_udp(void) { return reg_test(SIP_TRANSP_UDP, 0); } int test_sipreg_tcp(void) { return reg_test(SIP_TRANSP_TCP, 0); } #ifdef USE_TLS int test_sipreg_tls(void) { return reg_test(SIP_TRANSP_TLS, 0); } #endif ================================================ FILE: test/sipsess.c ================================================ /** * @file sipsess.c SIP Session regression testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_sipsess" #define DEBUG_LEVEL 5 #include typedef void (prack_func)(void *arg); enum neg_state { INITIAL = 0, OFFER_RECEIVED, ANSWER_RECEIVED, EARLY_CONFIRMED }; enum rel100_state { REL100_NONE = 0, REL100_SUPPORTED = 1, REL100_REQUIRE = 2 }; enum connect_action { CONN_PROGRESS = 1, CONN_PROGR_ANS = 2, CONN_PROGR_UPD = 4, CONN_ANSWER = 8, CONN_BUSY = 16 }; enum answer_action { ANSW_NONE = 0, ANSW_CANCEL = 1, }; enum offer_action { OFFER_NONE = 0, OFFER_ANSW = 1, }; struct test { struct sip *sip; struct sipsess_sock *sock; struct sipsess *a; struct sipsess *b; struct tmr ans_tmr; struct tmr ack_tmr; bool estab_a; bool estab_b; bool answr_a; bool answr_b; bool progr_a; bool progr_b; bool offer_a; bool offer_b; bool ack_a; bool ack_b; enum rel100_mode rel100_a; enum rel100_mode rel100_b; enum neg_state sdp_state; enum rel100_state rel100_state_a; enum rel100_state rel100_state_b; enum connect_action conn_action; enum offer_action offer_action; enum answer_action answ_action; prack_func *prack_action; int progr_ret_code; int answ_ret_code; int ack_cnt; bool upd_a; bool upd_b; struct mbuf *desc; bool blind_transfer; uint16_t altaddr_port; int err; }; const char sdp_a[] = "v=0\r\n" "o=alice 2890844526 2890844526 IN IP4 1.2.3.4\r\n" "s=-\r\n" "c=IN IP4 1.2.3.4\r\n" "t=0 0\r\n" "m=audio 49170 RTP/AVP 0 8 97\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:97 iLBC/8000\r\n" "a=sendrecv\r\n" "m=video 51372 RTP/AVP 31 32\r\n" "a=rtpmap:31 H261/90000\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n"; const char sdp_b[] = "v=0\r\n" "o=bob 2808844564 2808844564 IN IP4 5.6.7.8\r\n" "s=-\r\n" "c=IN IP4 5.6.7.8\r\n" "t=0 0\r\n" "m=audio 49174 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=sendrecv\r\n" "m=video 49170 RTP/AVP 32\r\n" "a=rtpmap:32 MPV/90000\r\n" "a=sendrecv\r\n"; static void stop_test(void) { re_cancel(); } static void abort_test(struct test *test, int err) { test->err = err; re_cancel(); } static void exit_handler(void *arg) { (void)arg; re_cancel(); } static void check_ack(void *arg) { struct test *test = arg; bool ack_a = sipsess_ack_pending(test->a); bool ack_b = sipsess_ack_pending(test->b); test->ack_a |= ack_a; test->ack_b |= ack_b; if (ack_a || ack_b) tmr_start(&test->ack_tmr, 1, check_ack, test); else stop_test(); } static void wait_for_ack(struct test *test) { test->ack_a = sipsess_ack_pending(test->a); test->ack_b = sipsess_ack_pending(test->b); if (!test->ack_a && !test->ack_b) return; tmr_start(&test->ack_tmr, 1, check_ack, test); } static void send_answer_b(void *arg) { struct test *test = arg; int err; err = sipsess_answer(test->b, 200, "Answering", NULL, NULL); if (err) { abort_test(test, err); } } static int make_sdp(struct mbuf **mbp, const char *sdp) { struct mbuf *desc; int err; desc = mbuf_alloc(strlen(sdp) + 1); if (!desc) return ENOMEM; err = mbuf_write_str(desc, sdp); TEST_ERR(err); mbuf_set_pos(desc, 0); out: if (err) mem_deref(desc); else *mbp = desc; return err; } static void send_update_a(void *arg) { struct test *test = arg; struct mbuf *desc = NULL; int err; err = make_sdp(&desc, sdp_a); TEST_ERR(err); err = sipsess_modify(test->a, desc); TEST_ERR(err); out: mem_deref(desc); if (err) abort_test(test, err); } static void send_update_b(void *arg) { struct test *test = arg; struct mbuf *desc = NULL; int err; err = make_sdp(&desc, sdp_b); TEST_ERR(err); err = sipsess_modify(test->b, desc); TEST_ERR(err); out: mem_deref(desc); if (err) abort_test(test, err); } static int desc_handler(struct mbuf **descp, const struct sa *src, const struct sa *dst, void *arg) { struct test *test = arg; (void)src; (void)dst; test->desc = mbuf_alloc(1); if (!test->desc) return ENOMEM; *descp = test->desc; return 0; } static int desc_handler_a(struct mbuf **descp, const struct sa *src, const struct sa *dst, void *arg) { struct mbuf *desc; int err = 0; (void)src; (void)dst; (void)arg; err = make_sdp(&desc, sdp_a); TEST_ERR(err); *descp = desc; out: return err; } static int offer_handler_a(struct mbuf **descp, const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)descp; (void)msg; if (test->sdp_state == INITIAL || test->sdp_state == EARLY_CONFIRMED) test->sdp_state = OFFER_RECEIVED; if (!pl_strcmp(&msg->met, "UPDATE")) test->upd_a = true; test->offer_a = true; return 0; } static int offer_handler_b(struct mbuf **descp, const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)msg; int err = 0; if (test->sdp_state == INITIAL || test->sdp_state == EARLY_CONFIRMED) test->sdp_state = OFFER_RECEIVED; if (!pl_strcmp(&msg->met, "UPDATE")) test->upd_b = true; test->offer_b = true; if (test->offer_action == OFFER_ANSW) { err = make_sdp(descp, sdp_b); TEST_ERR(err); } out: return err; } static int answer_handler_a(const struct sip_msg *msg, void *arg) { struct test *test = arg; test->answr_a = true; if (mbuf_get_left(msg->mb)) test->sdp_state = ANSWER_RECEIVED; if (sip_msg_hdr_has_value(msg, SIP_HDR_SUPPORTED, "100rel")) test->rel100_a |= (enum rel100_mode)REL100_SUPPORTED; if (sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "100rel")) test->rel100_a |= (enum rel100_mode)REL100_REQUIRE; if (!pl_strcmp(&msg->cseq.met, "UPDATE")) { if (msg->scode < 200 || msg->scode > 299) { abort_test(test, msg->scode); return msg->scode; } tmr_start(&test->ans_tmr, 0, send_answer_b, test); } if (test->answ_action == ANSW_CANCEL) wait_for_ack(test); return 0; } static int answer_handler_b(const struct sip_msg *msg, void *arg) { struct test *test = arg; test->answr_b = true; if (mbuf_get_left(msg->mb)) test->sdp_state = ANSWER_RECEIVED; if (sip_msg_hdr_has_value(msg, SIP_HDR_SUPPORTED, "100rel")) test->rel100_state_b |= REL100_SUPPORTED; if (sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "100rel")) test->rel100_state_b |= REL100_REQUIRE; if (!pl_strcmp(&msg->cseq.met, "UPDATE")) { if (msg->scode < 200 || msg->scode > 299) { abort_test(test, msg->scode); return msg->scode; } tmr_start(&test->ans_tmr, 0, send_answer_b, test); } return 0; } static void progr_handler_a(const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)msg; test->progr_a = true; } static void prack_handler(const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)msg; if (test->sdp_state == ANSWER_RECEIVED) test->sdp_state = EARLY_CONFIRMED; if (test->prack_action) tmr_start(&test->ans_tmr, 0, test->prack_action, test); } static void estab_handler_a(const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)msg; test->estab_a = true; if (test->estab_b) stop_test(); } static void estab_handler_b(const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)msg; test->estab_b = true; if (test->estab_a) stop_test(); } static void close_handler(int err, const struct sip_msg *msg, void *arg) { struct test *test = arg; (void)msg; if (!err && test->conn_action == CONN_BUSY) err = EBUSY; abort_test(test, err ? err : ENOMEM); } static void conn_handler(const struct sip_msg *msg, void *arg) { struct test *test = arg; struct mbuf *desc = NULL; int err; char *hdrs = test->rel100_b == REL100_REQUIRED ? "Require: 100rel\r\n" : ""; if (mbuf_get_left(msg->mb)) { test->sdp_state = OFFER_RECEIVED; test->offer_b = true; } if (sip_msg_hdr_has_value(msg, SIP_HDR_SUPPORTED, "100rel")) test->rel100_state_b |= REL100_SUPPORTED; if (sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "100rel")) test->rel100_state_b |= REL100_REQUIRE; err = make_sdp(&desc, sdp_b); TEST_ERR(err); test->desc = desc; if (test->conn_action & CONN_PROGRESS || test->conn_action & CONN_PROGR_ANS || test->conn_action & CONN_PROGR_UPD) { err = sipsess_accept(&test->b, test->sock, msg, 183, "Progress", test->rel100_b, "b", "application/sdp", desc, NULL, NULL, false, offer_handler_b, answer_handler_b, estab_handler_b, NULL, NULL, close_handler, test, hdrs); if (err != test->progr_ret_code) { test->progr_ret_code = err; goto out; } if (err) mem_deref(desc); err = sipsess_set_prack_handler(test->b, prack_handler); if (err) abort_test(test, err); } if (test->conn_action & CONN_PROGR_ANS) { err = sipsess_answer(test->b, 200, "Answering", NULL, NULL); if (err != test->answ_ret_code) { test->answ_ret_code = err; goto out; } } else if (test->conn_action & CONN_PROGR_UPD) { mem_deref(desc); desc = mbuf_alloc(0); if (!desc) { err = ENOMEM; goto out; } mbuf_set_pos(desc, 0); test->desc = desc; err = sipsess_modify(test->b, desc); TEST_ERR(err); } else if (test->conn_action & CONN_ANSWER) { err = sipsess_accept(&test->b, test->sock, msg, 200, "OK", test->rel100_b, "b", "application/sdp", desc, NULL, NULL, false, offer_handler_b, answer_handler_b, estab_handler_b, NULL, NULL, close_handler, test, hdrs); if (err != test->answ_ret_code) { test->answ_ret_code = err; goto out; } } else if (test->conn_action & CONN_BUSY) { err = sipsess_accept(&test->b, test->sock, msg, 180, "Ringing", test->rel100_b, "b", "application/sdp", NULL, NULL, NULL, false, offer_handler_b, answer_handler_b, estab_handler_b, NULL, NULL, close_handler, test, hdrs); if (err != test->answ_ret_code) { test->answ_ret_code = err; goto out; } err |= sipsess_reject(test->b, 486, "Busy Here", NULL); if (err != test->answ_ret_code) { test->answ_ret_code = err; goto out; } } if (test->conn_action & (CONN_ANSWER | CONN_PROGR_ANS | CONN_BUSY)) mem_deref(desc); return; out: mem_deref(desc); abort_test(test, err); } static void conn_transfer_handler(const struct sip_msg *msg, void *arg) { struct test *test = arg; int err = 0; if (test->blind_transfer) { conn_handler(msg, arg); } else { err = sip_replyf(test->sip, msg, 302, "Moved Temporarily", "Contact: \"alt retest\" " "\r\n\r\n", test->altaddr_port); if (err) { abort_test(test, err); } } return; } int test_sipsess(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_ENABLED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_ANSWER; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); if (err) goto out; (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); if (err) goto out; err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); if (err) goto out; port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); if (err) goto out; err = str_x64dup(&callid, rand_u64()); if (err) goto out; /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, NULL, estab_handler_a, NULL, NULL, close_handler, &test, NULL); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; goto out; } /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.desc); ASSERT_TRUE(test.answr_a); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(!test.offer_a); ASSERT_TRUE(!test.answr_b); /* test re-invite with wait for ACK */ test.sdp_state = INITIAL; test.answ_action = ANSW_CANCEL; test.offer_action = OFFER_ANSW; err = make_sdp(&test.desc, sdp_a); TEST_ERR(err); err = sipsess_modify(test.a, test.desc); TEST_ERR(err); test.desc = mem_deref(test.desc); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; goto out; } ASSERT_TRUE(test.ack_b); ASSERT_TRUE(!sipsess_ack_pending(test.a)); ASSERT_TRUE(!sipsess_ack_pending(test.b)); ASSERT_TRUE(test.sdp_state == ANSWER_RECEIVED); out: test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); return err; } int test_sipsess_reject(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_DISABLED; test.rel100_b = REL100_DISABLED; test.conn_action = CONN_BUSY; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler, offer_handler_a, answer_handler_a, NULL, estab_handler_a, NULL, NULL, close_handler, &test, NULL); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); /* okay here -- verify */ ASSERT_TRUE(test.err == EBUSY); ASSERT_TRUE(!test.estab_a); ASSERT_TRUE(!test.estab_b); ASSERT_TRUE(test.desc); ASSERT_TRUE(!test.answr_a); ASSERT_TRUE(!test.offer_b); out: test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); return err; } int test_sipsess_blind_transfer(void) { struct test test; struct sa laddr, altaddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_ENABLED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_ANSWER; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_transfer_handler, &test); TEST_ERR(err); (void)sa_set_str(&altaddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &altaddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &altaddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); test.altaddr_port = sa_port(&altaddr); err = str_x64dup(&callid, rand_u64()); if (err) goto out; /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, NULL, estab_handler_a, NULL, NULL, close_handler, &test, NULL); mem_deref(callid); TEST_ERR(err); test.blind_transfer = true; err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } /* okay here -- verify */ ASSERT_TRUE(test.blind_transfer); ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.desc); ASSERT_TRUE(test.answr_a); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(!test.offer_a); ASSERT_TRUE(!test.answr_b); out: test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); return err; } int test_sipsess_100rel_caller_require(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_REQUIRED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_PROGRESS; test.prack_action = send_answer_b; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, progr_handler_a, estab_handler_a, NULL, NULL, close_handler, &test, "Require: 100rel\r\n"); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.desc); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(!test.answr_b); ASSERT_TRUE(test.progr_a); ASSERT_TRUE(test.rel100_state_b & REL100_REQUIRE); ASSERT_TRUE((test.rel100_state_b & REL100_SUPPORTED) == 0); ASSERT_TRUE(test.sdp_state == EARLY_CONFIRMED); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); mem_deref(test.desc); return err; } int test_sipsess_100rel_supported(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_ENABLED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_PROGRESS; test.prack_action = send_answer_b; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, progr_handler_a, estab_handler_a, NULL, NULL, close_handler, &test, "Supported: 100rel\r\n"); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.desc); ASSERT_TRUE(test.answr_a); ASSERT_TRUE(!test.offer_a); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(!test.answr_b); ASSERT_TRUE(test.progr_a); ASSERT_TRUE(test.rel100_state_b & REL100_SUPPORTED); ASSERT_TRUE((test.rel100_state_b & REL100_REQUIRE) == 0); ASSERT_TRUE(test.sdp_state == EARLY_CONFIRMED); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); mem_deref(test.desc); return err; } int test_sipsess_100rel_answer_not_allowed(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_ENABLED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_PROGR_ANS; test.answ_ret_code = EAGAIN; test.prack_action = send_answer_b; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, progr_handler_a, estab_handler_a, NULL, NULL, close_handler, &test, "Supported: 100rel\r\n"); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } TEST_ERR(test.progr_ret_code); ASSERT_TRUE(test.answ_ret_code == EAGAIN); /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.progr_a); ASSERT_TRUE(test.rel100_state_b & REL100_SUPPORTED); ASSERT_TRUE((test.rel100_state_b & REL100_REQUIRE) == 0); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); return err; } int test_sipsess_100rel_420(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_REQUIRED; test.rel100_b = REL100_DISABLED; test.conn_action = CONN_PROGRESS; test.progr_ret_code = -1; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler, offer_handler_a, answer_handler_a, NULL, estab_handler_a, NULL, NULL, close_handler, &test, "Require: 100rel\r\n"); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); ASSERT_TRUE(test.err == EPROTO); /* okay here -- verify */ ASSERT_TRUE(!test.b); ASSERT_TRUE(!test.estab_a); ASSERT_TRUE(!test.estab_b); ASSERT_TRUE(test.desc); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); return err; } int test_sipsess_100rel_421(void) { struct test test; struct sa laddr; char to_uri[256]; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_DISABLED; test.rel100_b = REL100_REQUIRED; test.conn_action = CONN_PROGRESS; test.progr_ret_code = -1; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler, offer_handler_a, answer_handler_a, NULL, estab_handler_a, NULL, NULL, close_handler, &test, NULL); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); ASSERT_TRUE(test.err == EPROTO); /* okay here -- verify */ ASSERT_TRUE(!test.b); ASSERT_TRUE(!test.estab_a); ASSERT_TRUE(!test.estab_b); ASSERT_TRUE(test.desc); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); return err; } int test_sipsess_update_uac(void) { struct test test; struct sa laddr; char to_uri[256]; struct mbuf *desc_a = NULL; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_ENABLED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_PROGRESS; test.prack_action = send_update_a; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, progr_handler_a, estab_handler_a, NULL, NULL, close_handler, &test, "Supported: 100rel\r\n"); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.answr_a); ASSERT_TRUE(!test.answr_b); ASSERT_TRUE(!test.offer_a); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(test.progr_a); ASSERT_TRUE(test.upd_b); ASSERT_TRUE(!test.upd_a); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); mem_deref(desc_a); mem_deref(test.desc); return err; } int test_sipsess_update_uas(void) { struct test test; struct sa laddr; char to_uri[256]; struct mbuf *desc_a = NULL; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_ENABLED; test.rel100_b = REL100_ENABLED; test.conn_action = CONN_PROGRESS; test.prack_action = send_update_b; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, progr_handler_a, estab_handler_a, NULL, NULL, close_handler, &test, "Supported: 100rel\r\n"); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.answr_a); ASSERT_TRUE(test.answr_b); ASSERT_TRUE(test.offer_a); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(test.progr_a); ASSERT_TRUE(test.upd_a); ASSERT_TRUE(!test.upd_b); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); mem_deref(desc_a); mem_deref(test.desc); return err; } int test_sipsess_update_no_sdp(void) { struct test test; struct sa laddr; char to_uri[256]; struct mbuf *desc_a = NULL; int err; uint16_t port; char *callid; memset(&test, 0, sizeof(test)); test.rel100_a = REL100_DISABLED; test.rel100_b = REL100_DISABLED; test.conn_action = CONN_PROGR_UPD; err = sip_alloc(&test.sip, NULL, 32, 32, 32, "retest", exit_handler, NULL); TEST_ERR(err); (void)sa_set_str(&laddr, "127.0.0.1", 0); err = sip_transp_add(test.sip, SIP_TRANSP_UDP, &laddr); TEST_ERR(err); err = sip_transp_laddr(test.sip, &laddr, SIP_TRANSP_UDP, NULL); TEST_ERR(err); port = sa_port(&laddr); err = sipsess_listen(&test.sock, test.sip, 32, conn_handler, &test); TEST_ERR(err); err = str_x64dup(&callid, rand_u64()); TEST_ERR(err); /* Connect to "b" */ (void)re_snprintf(to_uri, sizeof(to_uri), "sip:b@127.0.0.1:%u", port); err = sipsess_connect(&test.a, test.sock, to_uri, NULL, "sip:a@127.0.0.1", "a", NULL, 0, "application/sdp", NULL, NULL, false, callid, desc_handler_a, offer_handler_a, answer_handler_a, progr_handler_a, estab_handler_a, NULL, NULL, close_handler, &test, NULL); mem_deref(callid); TEST_ERR(err); err = re_main_timeout(200); TEST_ERR(err); if (test.err) { err = test.err; TEST_ERR(err); } /* okay here -- verify */ ASSERT_TRUE(test.estab_a); ASSERT_TRUE(test.estab_b); ASSERT_TRUE(test.answr_a); ASSERT_TRUE(test.answr_b); ASSERT_TRUE(!test.offer_a); ASSERT_TRUE(test.offer_b); ASSERT_TRUE(test.progr_a); out: tmr_cancel(&test.ans_tmr); test.a = mem_deref(test.a); test.b = mem_deref(test.b); sipsess_close_all(test.sock); test.sock = mem_deref(test.sock); sip_close(test.sip, false); test.sip = mem_deref(test.sip); mem_deref(desc_a); mem_deref(test.desc); return err; } ================================================ FILE: test/srtp.c ================================================ /** * @file srtp.c SRTP Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "srtptest" #define DEBUG_LEVEL 5 #include #define SSRC 0x31323334 enum { SALT_LEN_CTR = 14 }; static const uint8_t fixed_payload[20] = { 0x55, 0x55, 0x55, 0x55, 0x11, 0x11, 0x11, 0x11, 0xee, 0xee, 0xee, 0xee, 0x11, 0x11, 0x11, 0x11, 0x55, 0x55, 0x55, 0x55, }; static size_t get_keylen(enum srtp_suite suite) { switch (suite) { case SRTP_AES_CM_128_HMAC_SHA1_32: return 16; case SRTP_AES_CM_128_HMAC_SHA1_80: return 16; case SRTP_AES_256_CM_HMAC_SHA1_32: return 32; case SRTP_AES_256_CM_HMAC_SHA1_80: return 32; case SRTP_AES_128_GCM: return 16; case SRTP_AES_256_GCM: return 32; default: return 0; } } static size_t get_saltlen(enum srtp_suite suite) { switch (suite) { case SRTP_AES_CM_128_HMAC_SHA1_32: return 14; case SRTP_AES_CM_128_HMAC_SHA1_80: return 14; case SRTP_AES_256_CM_HMAC_SHA1_32: return 14; case SRTP_AES_256_CM_HMAC_SHA1_80: return 14; case SRTP_AES_128_GCM: return 12; case SRTP_AES_256_GCM: return 12; default: return 0; } } static size_t get_taglen(enum srtp_suite suite) { switch (suite) { case SRTP_AES_CM_128_HMAC_SHA1_32: return 4; case SRTP_AES_CM_128_HMAC_SHA1_80: return 10; case SRTP_AES_256_CM_HMAC_SHA1_32: return 4; case SRTP_AES_256_CM_HMAC_SHA1_80: return 10; case SRTP_AES_128_GCM: return 16; case SRTP_AES_256_GCM: return 16; default: return 0; } } /* * RFC 3711 B.2. AES-CM Test Vectors */ static int test_srtp_aescm128(void) { uint8_t k_e[16], iv[16]; struct aes *aes = NULL; uint8_t keystream[16], nulldata[16]; size_t i; int err = 0; static const struct { const char *counter; const char *keystream; } testv[] = { {"F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000", "E03EAD0935C95E80E166B16DD92B4EB4"}, {"F0F1F2F3F4F5F6F7F8F9FAFBFCFD0001", "D23513162B02D0F72A43A2FE4A5F97AB"}, {"F0F1F2F3F4F5F6F7F8F9FAFBFCFD0002", "41E95B3BB0A2E8DD477901E4FCA894C0"}, }; memset(nulldata, 0, sizeof(nulldata)); err |= str_hex(k_e, sizeof(k_e), "2B7E151628AED2A6ABF7158809CF4F3C"); err |= str_hex(iv, sizeof(iv), "F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000"); if (err) return err; err = aes_alloc(&aes, AES_MODE_CTR, k_e, 128, iv); if (err) return err; for (i=0; ipos = mb->end = offset; memset(&hdr, 0, sizeof(hdr)); hdr.ver = RTP_VERSION; hdr.seq = seq++; hdr.ssrc = SSRC; err = rtp_hdr_encode(mb, &hdr); if (err) break; memcpy(hdrbuf, &mb->buf[mb->pos-12], 12); err = mbuf_write_mem(mb, fixed_payload, sizeof(fixed_payload)); if (err) break; end = mb->end; /* tx */ mb->pos = offset; err = srtp_encrypt(ctx_tx, mb); if (err) break; TEST_EQUALS(offset, mb->pos); TEST_EQUALS(end + tag_len, mb->end); /* verify that srtp_encrypt() did not tamper with RTP header */ TEST_MEMCMP(hdrbuf, sizeof(hdrbuf), &mb->buf[offset], 12); /* rx */ mb->pos = offset; err = srtp_decrypt(ctx_rx, mb); if (err) { DEBUG_WARNING("srtp_decrypt: %m\n", err); break; } TEST_EQUALS(offset, mb->pos); TEST_EQUALS(end, mb->end); mb->pos = offset + RTP_HEADER_SIZE; TEST_MEMCMP(fixed_payload, sizeof(fixed_payload), mbuf_buf(mb), mbuf_get_left(mb)); } out: mem_deref(ctx_tx); mem_deref(ctx_rx); mem_deref(mb); return err; } static int test_srtcp_loop(size_t offset, enum srtp_suite suite, enum rtcp_type type) { struct srtp *ctx_tx = NULL, *ctx_rx = NULL; struct mbuf *mb1 = NULL, *mb2 = NULL; const size_t key_len = get_keylen(suite); const size_t salt_len = get_saltlen(suite); const size_t tag_len = get_taglen(suite); unsigned i; int err = 0; static const uint8_t master_key[16+16+14] = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, }; mb1 = mbuf_alloc(1024); mb2 = mbuf_alloc(1024); if (!mb1 || !mb2) { err = ENOMEM; goto out; } err = srtp_alloc(&ctx_tx, suite, master_key, key_len + salt_len, 0); err |= srtp_alloc(&ctx_rx, suite, master_key, key_len + salt_len, 0); if (err) goto out; for (i=0; i<10; i++) { const uint32_t srcv[2] = {0x12345678, 0x00abcdef}; size_t end; mb1->pos = mb1->end = offset; mb2->pos = mb2->end = offset; if (type == RTCP_BYE) { err = rtcp_encode(mb1, RTCP_BYE, 2, srcv, "ciao"); } else if (type == RTCP_RR) { err = rtcp_encode(mb1, RTCP_RR, 0, srcv[0], NULL, NULL); } else { re_printf("unknown type %d\n", type); err = EINVAL; break; } if (err) break; end = mb1->end; mb1->pos = offset; (void)mbuf_write_mem(mb2, mbuf_buf(mb1), mbuf_get_left(mb1)); mb2->pos = offset; /* tx */ mb1->pos = offset; err = srtcp_encrypt(ctx_tx, mb1); if (err) break; TEST_EQUALS(offset, mb1->pos); TEST_ASSERT(mb1->end != end); TEST_EQUALS((mbuf_get_left(mb2) + 4 + tag_len), mbuf_get_left(mb1)); /* rx */ mb1->pos = offset; err = srtcp_decrypt(ctx_rx, mb1); if (err) break; TEST_EQUALS(offset, mb1->pos); TEST_EQUALS(end, mb1->end); TEST_MEMCMP(mbuf_buf(mb2), mbuf_get_left(mb2), mbuf_buf(mb1), mbuf_get_left(mb1)); } out: mem_deref(ctx_tx); mem_deref(ctx_rx); mem_deref(mb1); mem_deref(mb2); return err; } /* * Reference SRTP-packet generated by libsrtp * * cipher: AES-CM-128 * auth: HMAC-SHA1 80-bits tag * master key: 0x22222222222222222222222222222222 * master salt: 0x4444444444444444444444444444 * SSRC: 0x01020304 * Seq: 0x0001 * RTP payload: 0xa5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5 */ static const char *srtp_libsrtp = "800000010000000001020304" "f5b44b7e3ad4eb057bc6480c45df6547bb70bcc2" "7b136e1f3d3a62821b15"; static int test_srtp_libsrtp(void) { uint8_t pkt[12+20+10]; struct srtp *srtp_enc = NULL; re_nonstring static const uint8_t mast_key[16+14] = "\x22\x22\x22\x22\x22\x22\x22\x22" "\x22\x22\x22\x22\x22\x22\x22\x22" "\x44\x44\x44\x44\x44\x44\x44" "\x44\x44\x44\x44\x44\x44\x44"; re_nonstring static const uint8_t rtp_payload[20] = "\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5" "\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5"; struct mbuf *mb; struct rtp_header hdr; int err = 0; memset(&hdr, 0, sizeof(hdr)); hdr.ver = RTP_VERSION; hdr.ssrc = 0x01020304; hdr.seq = 0x0001; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = str_hex(pkt, sizeof(pkt), srtp_libsrtp); if (err) goto out; err = srtp_alloc(&srtp_enc, SRTP_AES_CM_128_HMAC_SHA1_80, mast_key, sizeof(mast_key), 0); if (err) goto out; err = rtp_hdr_encode(mb, &hdr); err |= mbuf_write_mem(mb, rtp_payload, sizeof(rtp_payload)); if (err) goto out; mb->pos = 0; err = srtp_encrypt(srtp_enc, mb); if (err) goto out; TEST_MEMCMP(pkt, sizeof(pkt), mb->buf, mb->end); out: mem_deref(srtp_enc); mem_deref(mb); return err; } /* * Reference SRTCP-packet generated by libsrtp * * cipher: AES-CM-128 * auth: HMAC-SHA1 32-bits tag * master key: 0x22222222222222222222222222222222 * master salt: 0x4444444444444444444444444444 * SSRC: 0x01020304 * RTCP packet: BYE-message */ static const char *srtcp_libsrtp = "81cb00020102030487c9fcdb80000001e9442fcc"; /* ^^^^^^^^________ * index tag */ static int test_srtcp_libsrtp(void) { uint8_t pkt[12+4+4]; struct srtp *srtp_enc = NULL; re_nonstring static const uint8_t mast_key[16+14] = "\x22\x22\x22\x22\x22\x22\x22\x22" "\x22\x22\x22\x22\x22\x22\x22\x22" "\x44\x44\x44\x44\x44\x44\x44" "\x44\x44\x44\x44\x44\x44\x44"; const uint32_t srcv[1] = {0x01020304}; struct mbuf *mb; int err = 0; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = str_hex(pkt, sizeof(pkt), srtcp_libsrtp); if (err) goto out; err = srtp_alloc(&srtp_enc, SRTP_AES_CM_128_HMAC_SHA1_32, mast_key, sizeof(mast_key), 0); if (err) goto out; err = rtcp_encode(mb, RTCP_BYE, 1, srcv, "b"); if (err) goto out; mb->pos = 0; err = srtcp_encrypt(srtp_enc, mb); if (err) goto out; TEST_MEMCMP(pkt, sizeof(pkt), mb->buf, mb->end); out: mem_deref(srtp_enc); mem_deref(mb); return err; } static int send_rtp_packet(struct srtp *srtp, struct mbuf *mb, uint16_t seq) { struct rtp_header hdr; size_t len; int err; memset(&hdr, 0, sizeof(hdr)); hdr.ver = RTP_VERSION; hdr.seq = seq; hdr.ssrc = SSRC; mb->pos = mb->end = 0; err = rtp_hdr_encode(mb, &hdr); err |= mbuf_write_mem(mb, fixed_payload, sizeof(fixed_payload)); if (err) return err; len = mb->end; mb->pos = 0; err = srtp_encrypt(srtp, mb); if (err) return err; TEST_EQUALS(0, mb->pos); TEST_ASSERT(mb->end > len); out: return err; } static int recv_srtp_packet(struct srtp *srtp, struct mbuf *mb) { const size_t len = mb->end; int err = 0; mb->pos = 0; err = srtp_decrypt(srtp, mb); if (err) return err; TEST_EQUALS(0, mb->pos); TEST_ASSERT(mb->end < len); TEST_MEMCMP(fixed_payload, sizeof(fixed_payload), mb->buf + 12, mb->end - 12); out: return err; } static int test_srtp_replay(enum srtp_suite suite) { static const uint8_t key[32+14] = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, }; struct srtp *ctx = NULL; struct mbuf *mb = NULL; const size_t key_len = get_keylen(suite); const size_t salt_len = get_saltlen(suite); int e, err = 0; mb = mbuf_alloc(1024); if (!mb) return ENOMEM; err = srtp_alloc(&ctx, suite, key, key_len + salt_len, 0); if (err) goto out; /* send/receive one RTP packet first */ err = send_rtp_packet(ctx, mb, 42); if (err) goto out; err = srtp_decrypt(ctx, mb); if (err) goto out; /* then send/receive the same packet again, expect replay protection */ err = send_rtp_packet(ctx, mb, 42); if (err) goto out; e = srtp_decrypt(ctx, mb); TEST_EQUALS(EALREADY, e); out: mem_deref(ctx); mem_deref(mb); return err; } static int test_seq_loop(const uint16_t *seqv, size_t seqn) { static const uint8_t key[16+14] = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, }; struct srtp *srtp_tx = NULL, *srtp_rx = NULL; struct mbuf *mb; size_t i; int err; mb = mbuf_alloc(1024); if (!mb) return ENOMEM; /* note: we must use two separate SRTP instances here, since the SSRC is the same */ err = srtp_alloc(&srtp_tx, SRTP_AES_CM_128_HMAC_SHA1_32, key, sizeof(key), 0); err |= srtp_alloc(&srtp_rx, SRTP_AES_CM_128_HMAC_SHA1_32, key, sizeof(key), 0); if (err) goto out; for (i=0; iend; for (i=0; ipos = 0; mb->end = i; (void)srtp_encrypt(ctx, mb); mb->pos = 0; mb->end = i; (void)srtp_decrypt(ctx, mb); } out: mem_deref(ctx); mem_deref(mb); return err; } /* verify that we dont crash on random input */ static int test_srtcp_random(enum srtp_suite suite) { struct srtp *ctx = NULL; struct mbuf *mb = NULL; const size_t key_len = get_keylen(suite); const size_t salt_len = get_saltlen(suite); const uint32_t srcv[2] = {0x12345678, 0x00abcdef}; size_t sz, i; int err = 0; static const uint8_t master_key[16+16+14] = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, }; mb = mbuf_alloc(1024); if (!mb) return ENOMEM; err = srtp_alloc(&ctx, suite, master_key, key_len + salt_len, 0); if (err) goto out; err = rtcp_encode(mb, RTCP_BYE, 2, srcv, "ciao"); if (err) goto out; err = mbuf_fill(mb, 0xd5, 32); if (err) goto out; sz = mb->end; for (i=0; ipos = 0; mb->end = i; (void)srtcp_encrypt(ctx, mb); mb->pos = 0; mb->end = i; (void)srtcp_decrypt(ctx, mb); } out: mem_deref(ctx); mem_deref(mb); return err; } static int test_srtp_unauth(enum srtp_suite suite) { struct srtp *srtp_tx, *srtp_rx; struct mbuf *mb = NULL; const size_t key_len = get_keylen(suite); const size_t salt_len = get_saltlen(suite); int err = 0; static const uint8_t master_key[32 + SALT_LEN_CTR] = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, }; mb = mbuf_alloc(32); if (!mb) return ENOMEM; err = srtp_alloc(&srtp_tx, suite, master_key, key_len+salt_len, 0); err |= srtp_alloc(&srtp_rx, suite, master_key, key_len+salt_len, 0); if (err) goto out; err = send_rtp_packet(srtp_tx, mb, 3); if (err) goto out; /* flip bits in the auth-tag to force authentication error */ mb->buf[mb->end - 1] ^= 0x55; err = recv_srtp_packet(srtp_rx, mb); TEST_EQUALS(EAUTH, err); err = 0; out: mem_deref(srtp_tx); mem_deref(srtp_rx); mem_deref(mb); return err; } /* * Special test for Unencrypted SRTCP. This is a special case in * SDES, See RFC 4568 section 6.3.2 */ static int test_unencrypted_srtcp(void) { struct srtp *srtp = NULL; struct mbuf *mb1 = NULL, *mb2 = NULL; enum srtp_suite suite = SRTP_AES_CM_128_HMAC_SHA1_32; const size_t tag_len = get_taglen(suite); size_t end; uint32_t v; bool ep; int err = 0; const uint32_t srcv[2] = {0x12345678, 0x00abcdef}; static const uint8_t master_key[16+14] = { 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, }; mb1 = mbuf_alloc(1024); mb2 = mbuf_alloc(1024); if (!mb1 || !mb2) { err = ENOMEM; goto out; } err = srtp_alloc(&srtp, suite, master_key, 16 + SALT_LEN_CTR, SRTP_UNENCRYPTED_SRTCP); if (err) goto out; err = rtcp_encode(mb1, RTCP_BYE, 2, srcv, "ciao"); err |= rtcp_encode(mb2, RTCP_BYE, 2, srcv, "ciao"); if (err) goto out; end = mb1->end; /* tx */ mb1->pos = 0; err = srtcp_encrypt(srtp, mb1); if (err) goto out; TEST_EQUALS(0, mb1->pos); mb1->pos = end; v = ntohl(mbuf_read_u32(mb1)); ep = (v >> 31) & 1; mb1->pos = 0; /* verify that RTCP packet is not encrypted */ TEST_ASSERT(ep == false); TEST_MEMCMP(mb2->buf, mb2->end, mb1->buf, mb1->end - 4 - tag_len); /* rx */ err = srtcp_decrypt(srtp, mb1); if (err) goto out; TEST_EQUALS(0, mb1->pos); TEST_MEMCMP(mb2->buf, mb2->end, mb1->buf, mb1->end); out: mem_deref(srtp); mem_deref(mb1); mem_deref(mb2); return err; } static bool have_srtp(void) { static const uint8_t nullkey[30]; struct srtp *srtp = NULL; int err; err = srtp_alloc(&srtp, SRTP_AES_CM_128_HMAC_SHA1_32, nullkey, sizeof(nullkey), 0); mem_deref(srtp); return err != ENOSYS; } /* * test low-level code first, then high-level at the end */ int test_srtp(void) { int err = 0; /* XXX: find a better solution for optional SRTP. perhaps only register this test if SRTP is available? */ if (!have_srtp()) { (void)re_printf("skipping SRTP test\n"); return ESKIPPED; } err = test_srtp_aescm128(); TEST_ERR(err); err = test_srtp_aescm256(); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_32, 3); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_80, 3); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_256_CM_HMAC_SHA1_32, 3); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_256_CM_HMAC_SHA1_80, 3); TEST_ERR(err); err = test_srtp_loop(4, SRTP_AES_CM_128_HMAC_SHA1_32, 3); TEST_ERR(err); err = test_srtp_loop(4, SRTP_AES_CM_128_HMAC_SHA1_80, 3); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_32, 65530); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_80, 65530); TEST_ERR(err); err = test_srtp_libsrtp(); TEST_ERR(err); err = test_srtp_replay(SRTP_AES_CM_128_HMAC_SHA1_32); TEST_ERR(err); err = test_srtp_reordering_and_wrap(); TEST_ERR(err); err = test_srtp_unauth(SRTP_AES_CM_128_HMAC_SHA1_32); TEST_ERR(err); err = test_srtp_random(SRTP_AES_CM_128_HMAC_SHA1_32); TEST_ERR(err); out: return err; } int test_srtcp(void) { int err = 0; if (!have_srtp()) { (void)re_printf("skipping SRTCP test\n"); return ESKIPPED; } err = test_srtcp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_32, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_80, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(0, SRTP_AES_256_CM_HMAC_SHA1_32, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(0, SRTP_AES_256_CM_HMAC_SHA1_80, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(4, SRTP_AES_CM_128_HMAC_SHA1_32, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(4, SRTP_AES_CM_128_HMAC_SHA1_80, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(0, SRTP_AES_CM_128_HMAC_SHA1_32, RTCP_RR); TEST_ERR(err); err = test_srtcp_libsrtp(); TEST_ERR(err); err = test_unencrypted_srtcp(); TEST_ERR(err); err = test_srtcp_random(SRTP_AES_CM_128_HMAC_SHA1_32); TEST_ERR(err); out: return err; } int test_srtp_gcm(void) { int err; if (!have_srtp()) { re_printf("skipping SRTP GCM test\n"); return ESKIPPED; } err = test_srtp_loop(0, SRTP_AES_128_GCM, 3); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_256_GCM, 3); TEST_ERR(err); err = test_srtp_loop(0, SRTP_AES_256_GCM, 65530); TEST_ERR(err); err = test_srtp_unauth(SRTP_AES_256_GCM); TEST_ERR(err); err = test_srtp_replay(SRTP_AES_128_GCM); TEST_ERR(err); err = test_srtp_random(SRTP_AES_128_GCM); TEST_ERR(err); out: return err; } int test_srtcp_gcm(void) { int err; if (!have_srtp()) { re_printf("skipping SRTCP GCM test\n"); return ESKIPPED; } err = test_srtcp_loop(0, SRTP_AES_128_GCM, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(0, SRTP_AES_256_GCM, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(4, SRTP_AES_128_GCM, RTCP_BYE); TEST_ERR(err); err = test_srtcp_loop(0, SRTP_AES_128_GCM, RTCP_RR); TEST_ERR(err); err = test_srtcp_random(SRTP_AES_128_GCM); TEST_ERR(err); out: return err; } ================================================ FILE: test/stun.c ================================================ /** * @file stun.c STUN Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_stun" #define DEBUG_LEVEL 5 #include #define NATTED (true) /* * Test vectors from RFC 5769 */ static const uint8_t tid[] = "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"; static const char *username = "evtj:h6vY"; static const struct pl password = PL("VOkJxbRl1RmTxUk/WvJxBt"); static const unsigned char req[] = "\x00\x01\x00\x58" "\x21\x12\xa4\x42" "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae" "\x80\x22\x00\x10" "STUN test client" "\x00\x24\x00\x04" "\x6e\x00\x01\xff" "\x80\x29\x00\x08" "\x93\x2f\xf9\xb1\x51\x26\x3b\x36" "\x00\x06\x00\x09" "\x65\x76\x74\x6a\x3a\x68\x36\x76\x59\x20\x20\x20" "\x00\x08\x00\x14" "\x9a\xea\xa7\x0c\xbf\xd8\xcb\x56\x78\x1e\xf2\xb5" "\xb2\xd3\xf2\x49\xc1\xb5\x71\xa2" "\x80\x28\x00\x04" "\xe5\x7a\x3b\xcf"; static const unsigned char respv4[] = "\x01\x01\x00\x3c" "\x21\x12\xa4\x42" "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae" "\x80\x22\x00\x0b" "\x74\x65\x73\x74\x20\x76\x65\x63\x74\x6f\x72\x20" "\x00\x20\x00\x08" "\x00\x01\xa1\x47\xe1\x12\xa6\x43" "\x00\x08\x00\x14" "\x2b\x91\xf5\x99\xfd\x9e\x90\xc3\x8c\x74\x89\xf9" "\x2a\xf9\xba\x53\xf0\x6b\xe7\xd7" "\x80\x28\x00\x04" "\xc0\x7d\x4c\x96"; static const unsigned char respv6[] = "\x01\x01\x00\x48" "\x21\x12\xa4\x42" "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae" "\x80\x22\x00\x0b" "\x74\x65\x73\x74\x20\x76\x65\x63\x74\x6f\x72\x20" "\x00\x20\x00\x14" "\x00\x02\xa1\x47" "\x01\x13\xa9\xfa\xa5\xd3\xf1\x79" "\xbc\x25\xf4\xb5\xbe\xd2\xb9\xd9" "\x00\x08\x00\x14" "\xa3\x82\x95\x4e\x4b\xe6\x7b\xf1\x17\x84\xc9\x7c" "\x82\x92\xc2\x75\xbf\xe3\xed\x41" "\x80\x28\x00\x04" "\xc8\xfb\x0b\x4c"; static const uint32_t ice_prio = 0x6e0001ff; static const uint64_t ice_contr = 0x932ff9b151263b36ULL; static const char *client_sw = "STUN test client"; static const char *server_sw = "test vector"; int test_stun_req(void) { struct stun_msg *msg = NULL; struct mbuf *mb; struct stun_attr *attr; int err; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } err = stun_msg_encode(mb, STUN_METHOD_BINDING, STUN_CLASS_REQUEST, tid, NULL, (uint8_t *)password.p, password.l, true, 0x20, 4, STUN_ATTR_SOFTWARE, client_sw, STUN_ATTR_PRIORITY, &ice_prio, STUN_ATTR_CONTROLLED, &ice_contr, STUN_ATTR_USERNAME, username); if (err) goto out; TEST_MEMCMP(req, sizeof(req)-1, mb->buf, mb->end); /* Decode STUN message */ mb->pos = 0; err = stun_msg_decode(&msg, mb, NULL); if (err) goto out; if (STUN_CLASS_REQUEST != stun_msg_class(msg)) goto bad; if (STUN_METHOD_BINDING != stun_msg_method(msg)) goto out; err = stun_msg_chk_mi(msg, (uint8_t *)password.p, password.l); if (err) goto out; err = stun_msg_chk_fingerprint(msg); if (err) goto out; attr = stun_msg_attr(msg, STUN_ATTR_PRIORITY); if (!attr || ice_prio != attr->v.priority) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_CONTROLLED); if (!attr || ice_contr != attr->v.controlled) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_USERNAME); if (!attr || strcmp(username, attr->v.username)) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_SOFTWARE); if (!attr || strcmp(client_sw, attr->v.software)) goto bad; goto out; bad: err = EBADMSG; out: mem_deref(msg); mem_deref(mb); return err; } static int test_resp(const struct pl *resp, const struct sa *addr) { struct stun_msg *msg = NULL; struct stun_attr *attr; struct mbuf *mb = NULL; int err; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } err = stun_msg_encode(mb, STUN_METHOD_BINDING, STUN_CLASS_SUCCESS_RESP, tid, NULL, (uint8_t *)password.p, password.l, true, 0x20, 2, STUN_ATTR_SOFTWARE, server_sw, STUN_ATTR_XOR_MAPPED_ADDR, addr); if (err) goto out; if (resp->l != mb->end || 0 != memcmp(mb->buf, resp->p, mb->end)) { err = EBADMSG; DEBUG_WARNING("compare failed (%J)\n", addr); (void)re_printf("msg: [%02w]\n", mb->buf, mb->end); (void)re_printf("ref: [%02w]\n", resp->p, resp->l); goto out; } /* Decode STUN message */ mb->pos = 0; err = stun_msg_decode(&msg, mb, NULL); if (err) goto out; if (STUN_CLASS_SUCCESS_RESP != stun_msg_class(msg)) goto bad; if (STUN_METHOD_BINDING != stun_msg_method(msg)) goto bad; err = stun_msg_chk_mi(msg, (uint8_t *)password.p, password.l); if (err) goto out; err = stun_msg_chk_fingerprint(msg); if (err) goto out; attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); if (!attr || !sa_cmp(&attr->v.xor_mapped_addr, addr, SA_ALL)) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_SOFTWARE); if (!attr || strcmp(server_sw, attr->v.software)) goto bad; goto out; bad: err = EBADMSG; out: mem_deref(msg); mem_deref(mb); return err; } int test_stun_resp(void) { struct sa maddr; struct pl resp; int err; resp.p = (char *)respv4; resp.l = sizeof(respv4) - 1; err = sa_set_str(&maddr, "192.0.2.1", 32853); if (err) return err; err = test_resp(&resp, &maddr); if (err) return err; resp.p = (char *)respv6; resp.l = sizeof(respv6) - 1; err = sa_set_str(&maddr, "2001:db8:1234:5678:11:2233:4455:6677", 32853); if (err) return err; err = test_resp(&resp, &maddr); return err; } static const unsigned char reqltc[] = "\x00\x01\x00\x60" "\x21\x12\xa4\x42" "\x78\xad\x34\x33\xc6\xad\x72\xc0\x29\xda\x41\x2e" "\x00\x06\x00\x12" "\xe3\x83\x9e\xe3\x83\x88\xe3\x83\xaa\xe3\x83\x83" "\xe3\x82\xaf\xe3\x82\xb9\x00\x00" "\x00\x15\x00\x1c" "\x66\x2f\x2f\x34\x39\x39\x6b\x39\x35\x34\x64\x36" "\x4f\x4c\x33\x34\x6f\x4c\x39\x46\x53\x54\x76\x79" "\x36\x34\x73\x41" "\x00\x14\x00\x0b" "\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6f\x72\x67\x00" "\x00\x08\x00\x14" "\xf6\x70\x24\x65\x6d\xd6\x4a\x3e\x02\xb8\xe0\x71" "\x2e\x85\xc9\xa2\x8c\xa8\x96\x66"; static const uint8_t tid_ltc[] = "\x78\xad\x34\x33\xc6\xad\x72\xc0\x29\xda\x41\x2e"; /* Username: "" (without quotes) unaffected by SASLprep[RFC4013] processing */ static const char *username_ltc = "\xe3\x83\x9e\xe3\x83\x88\xe3\x83" "\xaa\xe3\x83\x83\xe3\x82\xaf\xe3" "\x82\xb9"; /* Password: "TheMtr"" resp "TheMatrIX" (without quotes) before resp after SASLprep processing */ static const char *password_ltc = "TheMatrIX"; static const char *nonce_ltc = "f//499k954d6OL34oL9FSTvy64sA"; static const char *realm_ltc = "example.org"; int test_stun_reqltc(void) { struct stun_msg *msg = NULL; struct stun_attr *attr; struct mbuf *mb; uint8_t md5_hash[MD5_SIZE]; int r, err; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } /* use long-term credentials */ err = md5_printf(md5_hash, "%s:%s:%s", username_ltc, realm_ltc, password_ltc); if (err) goto out; err = stun_msg_encode(mb, STUN_METHOD_BINDING, STUN_CLASS_REQUEST, tid_ltc, NULL, md5_hash, sizeof(md5_hash), false, 0x00, 3, STUN_ATTR_USERNAME, username_ltc, STUN_ATTR_NONCE, nonce_ltc, STUN_ATTR_REALM, realm_ltc); if (err) goto out; r = memcmp(mb->buf, reqltc, mb->end); if ((sizeof(reqltc)-1) != mb->end || 0 != r) { err = EBADMSG; DEBUG_WARNING("compare failed (r=%d)\n", r); (void)re_printf("msg: [%02w]\n", mb->buf, mb->end); (void)re_printf("ref: [%02w]\n", reqltc, sizeof(reqltc)-1); goto out; } /* Decode STUN message */ mb->pos = 0; err = stun_msg_decode(&msg, mb, NULL); if (err) goto out; if (STUN_CLASS_REQUEST != stun_msg_class(msg)) goto bad; if (STUN_METHOD_BINDING != stun_msg_method(msg)) goto bad; err = stun_msg_chk_mi(msg, md5_hash, sizeof(md5_hash)); if (err) goto out; if (EPROTO != stun_msg_chk_fingerprint(msg)) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_USERNAME); if (!attr || strcmp(username_ltc, attr->v.username)) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_NONCE); if (!attr || strcmp(nonce_ltc, attr->v.nonce)) goto bad; attr = stun_msg_attr(msg, STUN_ATTR_REALM); if (!attr || strcmp(realm_ltc, attr->v.realm)) goto bad; goto out; bad: err = EBADMSG; out: mem_deref(msg); mem_deref(mb); return err; } struct test { struct stun *stun; struct udp_sock *us; struct sa mapped_addr; size_t n_resp; int err; }; static void stun_resp_handler(int err, uint16_t scode, const char *reason, const struct stun_msg *msg, void *arg) { struct test *test = arg; struct stun_attr *attr; (void)reason; if (err) goto out; ++test->n_resp; /* verify STUN response */ ASSERT_EQ(0, scode); TEST_EQUALS(0x0101, stun_msg_type(msg)); TEST_EQUALS(STUN_CLASS_SUCCESS_RESP, stun_msg_class(msg)); TEST_EQUALS(STUN_METHOD_BINDING, stun_msg_method(msg)); TEST_EQUALS(0, stun_msg_chk_fingerprint(msg)); attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); TEST_ASSERT(attr != NULL); test->mapped_addr = attr->v.sa; out: if (err) test->err = err; /* done */ re_cancel(); } static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg) { struct test *test = arg; (void)src; (void)stun_recv(test->stun, mb); } static int test_stun_request(int proto, bool natted, const char *laddr_str) { struct stunserver *srv = NULL; struct stun_ctrans *ct = NULL; struct nat *nat = NULL; struct test test; struct sa laddr, public_addr; int err; memset(&test, 0, sizeof(test)); err = stunserver_alloc(&srv, laddr_str); if (err) goto out; err = stun_alloc(&test.stun, NULL, NULL, NULL); if (err) goto out; if (proto == IPPROTO_UDP) { err = sa_set_str(&laddr, laddr_str, 0); TEST_ERR(err); err = udp_listen(&test.us, &laddr, udp_recv_handler, &test); if (err) goto out; err = udp_local_get(test.us, &laddr); TEST_ERR(err); } if (natted) { err = sa_set_str(&public_addr, "4.5.6.7", 0); TEST_ERR(err); err = nat_alloc(&nat, NAT_INBOUND_SNAT, srv->us, &public_addr); if (err) goto out; sa_set_port(&public_addr, sa_port(&laddr)); } else if (proto == IPPROTO_UDP) { public_addr = laddr; } err = stun_request(&ct, test.stun, proto, test.us, stunserver_addr(srv, proto), 0, STUN_METHOD_BINDING, NULL, 0, true, stun_resp_handler, &test, 0); if (err) goto out; TEST_ASSERT(ct != NULL); err = re_main_timeout(100); if (err) goto out; if (srv->err) { err = srv->err; goto out; } if (test.err) { err = test.err; goto out; } /* verify results */ TEST_ASSERT(srv->nrecv >= 1); TEST_EQUALS(1, test.n_resp); if (proto == IPPROTO_UDP) { TEST_SACMP(&public_addr, &test.mapped_addr, SA_ALL); } out: mem_deref(test.stun); mem_deref(test.us); mem_deref(nat); mem_deref(srv); return err; } static int test_stun_req_attributes(void) { struct stun_msg *msg = NULL; struct mbuf *mb; struct stun_attr *attr; const uint64_t rsv_token = 0x1100c0ffee; const uint32_t lifetime = 3600; const uint16_t chan = 0x4000; const uint8_t req_addr_fam = AF_INET; int err; mb = mbuf_alloc(1024); if (!mb) { err = ENOMEM; goto out; } err = stun_msg_encode(mb, STUN_METHOD_BINDING, STUN_CLASS_REQUEST, tid, NULL, NULL, 0, false, 0x00, 4, STUN_ATTR_REQ_ADDR_FAMILY, &req_addr_fam, STUN_ATTR_CHANNEL_NUMBER, &chan, STUN_ATTR_LIFETIME, &lifetime, STUN_ATTR_RSV_TOKEN, &rsv_token); if (err) goto out; /* Decode STUN message */ mb->pos = 0; err = stun_msg_decode(&msg, mb, NULL); if (err) goto out; TEST_EQUALS(STUN_CLASS_REQUEST, stun_msg_class(msg)); TEST_EQUALS(STUN_METHOD_BINDING, stun_msg_method(msg)); /* verify integer attributes of different sizes */ /* 8-bit */ attr = stun_msg_attr(msg, STUN_ATTR_REQ_ADDR_FAMILY); TEST_ASSERT(attr != NULL); TEST_EQUALS(req_addr_fam, attr->v.req_addr_family); /* 16-bit */ attr = stun_msg_attr(msg, STUN_ATTR_CHANNEL_NUMBER); TEST_ASSERT(attr != NULL); TEST_EQUALS(chan, attr->v.channel_number); /* 32-bit */ attr = stun_msg_attr(msg, STUN_ATTR_LIFETIME); TEST_ASSERT(attr != NULL); TEST_EQUALS(lifetime, attr->v.lifetime); /* 64-bit */ attr = stun_msg_attr(msg, STUN_ATTR_RSV_TOKEN); TEST_ASSERT(attr != NULL); TEST_EQUALS(rsv_token, attr->v.rsv_token); out: mem_deref(msg); mem_deref(mb); return err; } static void stun_dns_handler(int err, const struct sa *srv, void *arg) { struct sa *stun_addr = arg; if (err) { DEBUG_WARNING("stun_dns_handler error: %m\n", err); re_cancel(); return; } DEBUG_INFO("stun dns: server = %J\n", srv); *stun_addr = *srv; re_cancel(); } static const char *get_loopback(int af) { switch (af) { case AF_INET: return "127.0.0.1"; case AF_INET6: return "::1"; default: return NULL; } } static int test_stun_discover(int af) { struct dns_server *srv = NULL; struct dnsc *dnsc = NULL; struct stun_dns *stun_dns = NULL; struct sa stun_addr, exp_addr; const char *tld = "example.com"; const char *target = "stun.example.com"; const char *laddr = get_loopback(af); int err; sa_set_str(&exp_addr, laddr, STUN_PORT); err = dns_server_alloc(&srv, laddr); TEST_ERR(err); char srv_name[256] = ""; re_snprintf(srv_name, sizeof(srv_name), "_stun._udp.%s", tld); err = dns_server_add_srv(srv, srv_name, 0, 0, STUN_PORT, target, 3600); TEST_ERR(err); err = dns_server_add_srv(srv, srv_name, 0, 0, STUN_PORT, target, 3600); TEST_ERR(err); err = dns_server_add_a(srv, target, 0x7f000001, 3600); TEST_ERR(err); const uint8_t ipv6_lo[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; err = dns_server_add_aaaa(srv, target, ipv6_lo, 3600); TEST_ERR(err); err = dnsc_alloc(&dnsc, NULL, &srv->addr, 1); TEST_ERR(err); err = stun_server_discover(&stun_dns, dnsc, stun_usage_binding, stun_proto_udp, af, tld, 0, stun_dns_handler, &stun_addr); TEST_ERR(err); err = re_main_timeout(10000); TEST_ERR(err); TEST_SACMP(&exp_addr, &stun_addr, SA_ALL); out: mem_deref(stun_dns); mem_deref(dnsc); mem_deref(srv); return err; } /* * Send a STUN Binding Request to the mock STUN-Server, * and expect a STUN Binding Response. */ int test_stun(void) { int err; err = test_stun_request(IPPROTO_UDP, false, "127.0.0.1"); TEST_ERR(err); err = test_stun_request(IPPROTO_UDP, NATTED, "127.0.0.1"); TEST_ERR(err); err = test_stun_request(IPPROTO_TCP, false, "127.0.0.1"); TEST_ERR(err); err = test_stun_req_attributes(); TEST_ERR(err); if (test_ipv6_supported()) { err = test_stun_request(IPPROTO_UDP, false, "::1"); TEST_ERR(err); } err = test_stun_discover(AF_INET); TEST_ERR(err); if (test_ipv6_supported()) { err = test_stun_discover(AF_INET6); TEST_ERR(err); } out: return err; } ================================================ FILE: test/sys.c ================================================ /** * @file sys.c System Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include "test.h" #define DEBUG_MODULE "test_sys" #define DEBUG_LEVEL 4 #include int test_sys_endian(void) { uint16_t s_le, s_ho; uint8_t *s = (uint8_t *)&s_le; uint32_t l_le, l_ho; uint8_t *l = (uint8_t *)&l_le; uint64_t ll0, ll1 = 0x0102030405060708ULL; /* Little endian: LSB first - 0x1234 * * 0x0000: 0x34 * 0x0001: 0x12 */ s[0] = 0x34; s[1] = 0x12; s_ho = sys_ltohs(s_le); if (0x1234 != s_ho) { DEBUG_WARNING("endian short: 0x%04x\n", s_ho); return EINVAL; } if (s_le != sys_htols(s_ho)) { DEBUG_WARNING("sys_htols failed: 0x%04x\n", sys_htols(s_ho)); return EINVAL; } /* 0x12345678 * * 0x0000: 0x78 * 0x0001: 0x56 * 0x0002: 0x34 * 0x0003: 0x12 */ l[0] = 0x78; l[1] = 0x56; l[2] = 0x34; l[3] = 0x12; l_ho = sys_ltohl(l_le); if (0x12345678 != l_ho) { DEBUG_WARNING("endian long: 0x%08x\n", l_ho); return EINVAL; } if (l_le != sys_htoll(l_ho)) { DEBUG_WARNING("sys_htoll failed: 0x%08x\n", sys_htoll(l_ho)); return EINVAL; } /* Test 64-bit */ ll0 = sys_ntohll(sys_htonll(ll1)); if (ll0 != ll1) { DEBUG_WARNING("endian long-long: 0x%llx\n", ll0); return EINVAL; } return 0; } int test_sys_rand(void) { char str[64]; uint8_t buf[64]; size_t i; int err = 0; volatile uint16_t u16 = rand_u16(); volatile uint32_t u32 = rand_u32(); volatile uint64_t u64 = rand_u64(); char ch = rand_char(); (void)u16; (void)u32; (void)u64; TEST_ASSERT(ch > 0); TEST_ASSERT(isprint(ch)); rand_str(str, sizeof(str)); rand_bytes(buf, sizeof(buf)); for (i = 0; i < (sizeof(str)-1); i++) { TEST_ASSERT(str[i] > 0); TEST_ASSERT(isprint(str[i])); } out: return err; } int test_sys_fs_isdir(void) { int err = 0; bool ret; char path[256]; char file[256]; char *wpath = "/some/path/to/nothing"; re_snprintf(path, sizeof(path), "%s", test_datapath()); re_snprintf(file, sizeof(file), "%s/menu.json", test_datapath()); ret = fs_isdir(path); TEST_EQUALS(true, ret); ret = fs_isdir(NULL); TEST_EQUALS(false, ret); ret = fs_isdir(wpath); TEST_EQUALS(false, ret); ret = fs_isdir(file); TEST_EQUALS(false, ret); out: return err; } int test_sys_fs_isfile(void) { int err = 0; bool ret; char path[256]; char file[256]; char *wpath = "/some/path/to/nothing"; re_snprintf(path, sizeof(path), "%s", test_datapath()); re_snprintf(file, sizeof(file), "%s/menu.json", test_datapath()); ret = fs_isfile(file); TEST_EQUALS(true, ret); ret = fs_isfile(NULL); TEST_EQUALS(false, ret); ret = fs_isfile(wpath); TEST_EQUALS(false, ret); ret = fs_isfile(path); TEST_EQUALS(false, ret); out: return err; } int test_sys_fs_fopen(void) { char filename[256]; FILE *file; int err; /* Use a unique filename to avoid clash when running * multiple instances of test */ re_snprintf(filename, sizeof(filename), "%s/retest_fs_fopen-%llu", test_datapath(), rand_u64()); err = fs_fopen(&file, filename, "w+"); TEST_ERR(err); TEST_EQUALS(true, fs_isfile(filename)); err = fclose(file); TEST_ERR(err); /* Try reopen */ err = fs_fopen(&file, filename, "w+"); TEST_ERR(err); err = fclose(file); TEST_ERR(err); #ifdef WIN32 (void)_unlink(filename); #else (void)unlink(filename); #endif out: return err; } int test_sys_getenv(void) { int err = 0; char *env = NULL; #ifdef WIN32 err = sys_getenv(&env, "HOMEPATH"); #else err = sys_getenv(&env, "HOME"); #endif TEST_ERR(err); TEST_EQUALS(true, str_isset(env)); mem_deref(env); err = sys_getenv(&env, "DOESNOTEXIST"); TEST_EQUALS(ENODATA, err); err = 0; out: return err; } int test_sys_fs_gethome(void) { char path[256]; int err = fs_gethome(path, sizeof(path)); TEST_ERR(err); TEST_EQUALS(true, str_isset(path)); out: return err; } ================================================ FILE: test/tcp.c ================================================ /** * @file tcp.c TCP testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "tcptest" #define DEBUG_LEVEL 5 #include /* * .------. .------. * |Client| |Server| * '------' '------' * * <------ tcp_listen() * tcp_connect() ---> * ------------- TCP [SYN] -----------> * -----> tcp_conn_h * * <----- tcp_accept() * <-------- TCP [SYN, ACK] ---------- * --------- TCP [ACK] --------------> * tcp_estab_h <--- * * tcp_send() ===> * ======= TCP [PSH, ACK] ==========> * <======= TCP [ACK] =============== * =====> tcp_recv_h */ struct tcp_test { struct tcp_sock *ts; struct tcp_conn *tc; struct tcp_conn *tc2; int err; }; static const char *ping = "ping from client to server\n"; static const char *pong = "pong from server 2 client\n"; static void destructor(void *arg) { struct tcp_test *tt = arg; mem_deref(tt->tc2); mem_deref(tt->tc); mem_deref(tt->ts); } static void abort_test(struct tcp_test *tt, int err) { if (err) { tt->err = err; } re_cancel(); } static int send_data(struct tcp_conn *tc, const char *data) { struct mbuf mb; int err; mbuf_init(&mb); err = mbuf_write_str(&mb, data); if (err) goto out; mb.pos = 0; err = tcp_send(tc, &mb); if (err) goto out; out: mbuf_reset(&mb); return err; } static bool mbuf_compare(const struct mbuf *mb, const char *str) { if (mbuf_get_left(mb) != strlen(str)) { DEBUG_WARNING("compare: mbuf=%u str=%u (bytes)\n", mbuf_get_left(mb), strlen(str)); return false; } if (0 != memcmp(mbuf_buf(mb), str, strlen(str))) { DEBUG_WARNING("compare: mbuf=[%b] str=[%s]\n", mbuf_buf(mb), mbuf_get_left(mb), str); return false; } return true; } static void tcp_server_recv_handler(struct mbuf *mb, void *arg) { struct tcp_test *tt = arg; int err; DEBUG_INFO("Server: TCP Receive data (%u bytes)\n", mbuf_get_left(mb)); if (!mbuf_compare(mb, ping)) { abort_test(tt, EBADMSG); return; } err = send_data(tt->tc2, pong); if (err) abort_test(tt, err); } static void tcp_server_close_handler(int err, void *arg) { struct tcp_test *tt = arg; abort_test(tt, err); } static void tcp_server_conn_handler(const struct sa *peer, void *arg) { struct tcp_test *tt = arg; int err; (void)peer; DEBUG_INFO("Server: Incoming CONNECT from %J\n", peer); err = tcp_accept(&tt->tc2, tt->ts, NULL, tcp_server_recv_handler, tcp_server_close_handler, tt); if (err) { abort_test(tt, err); return; } } static void tcp_client_estab_handler(void *arg) { struct tcp_test *tt = arg; int err; DEBUG_INFO("Client: TCP Established\n"); err = send_data(tt->tc, ping); if (err) abort_test(tt, err); } static void tcp_client_recv_handler(struct mbuf *mb, void *arg) { struct tcp_test *tt = arg; DEBUG_INFO("Client: TCP receive: %u bytes\n", mbuf_get_left(mb)); if (!mbuf_compare(mb, pong)) { abort_test(tt, EBADMSG); return; } abort_test(tt, 0); } static void tcp_client_close_handler(int err, void *arg) { struct tcp_test *tt = arg; DEBUG_NOTICE("Client: TCP Close (%m)\n", err); abort_test(tt, err); } int test_tcp(void) { struct tcp_test *tt; struct sa srv; int err; tt = mem_zalloc(sizeof(*tt), destructor); if (!tt) return ENOMEM; err = sa_set_str(&srv, "127.0.0.1", 0); if (err) goto out; err = tcp_listen(&tt->ts, &srv, tcp_server_conn_handler, tt); if (err) goto out; err = tcp_local_get(tt->ts, &srv); if (err) goto out; err = tcp_connect(&tt->tc, &srv, tcp_client_estab_handler, tcp_client_recv_handler, tcp_client_close_handler, tt); if (err) goto out; err = re_main_timeout(500); if (err) goto out; if (tt->err) err = tt->err; out: mem_deref(tt); return err; } #if !defined(WIN32) static int tcp_tos(const char *addr) { struct tcp_test *tt; struct sa srv; int err; tt = mem_zalloc(sizeof(*tt), destructor); if (!tt) return ENOMEM; err = sa_set_str(&srv, addr, 0); TEST_ERR(err); err = tcp_listen(&tt->ts, &srv, tcp_server_conn_handler, tt); TEST_ERR(err); err = tcp_settos(tt->ts, 184); TEST_ERR(err); err = tcp_local_get(tt->ts, &srv); TEST_ERR(err); err = tcp_connect(&tt->tc, &srv, tcp_client_estab_handler, tcp_client_recv_handler, tcp_client_close_handler, tt); TEST_ERR(err); err = re_main_timeout(500); TEST_ERR(err); if (tt->err) err = tt->err; out: mem_deref(tt); return err; } int test_tcp_tos(void) { int err; err = tcp_tos("127.0.0.1"); TEST_ERR(err); if (test_ipv6_supported()) { err = tcp_tos("::1"); TEST_ERR(err); } out: return err; } #else /* Outcome of the TOS test on Windows would be dependent on the * DisableUserTOSSetting Windows registry setting. */ int test_tcp_tos(void) { return 0; } #endif ================================================ FILE: test/telev.c ================================================ /** * @file telev.c Testcode for Telephone-event * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" int test_telev(void) { static const char digits[] = "1234567890ABCD*#"; struct telev *tlv = NULL; struct mbuf *mb; bool marker, expect_end = false; char digit; size_t i; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; err = telev_alloc(&tlv, 1); if (err) goto out; /* Encode all digits */ for (i=0; ipos = 0; i = 0; while (mbuf_get_left(mb) && i #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_IO_H #include #endif #include #include #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include "test.h" #define DEBUG_MODULE "test" #define DEBUG_LEVEL 5 #include #ifdef WIN32 #define open _open #define read _read #define write _write #define close _close #define dup _dup #define dup2 _dup2 #define fileno _fileno #endif typedef int (test_exec_h)(void); struct test { test_exec_h *exec; const char *name; }; #define TEST(a) {a, #a} static const struct test tests[] = { TEST(test_aac), TEST(test_aes), TEST(test_aes_gcm), TEST(test_async), TEST(test_au), TEST(test_aubuf), TEST(test_aulength), TEST(test_aulevel), TEST(test_auposition), TEST(test_auresamp), TEST(test_av1), TEST(test_base64), TEST(test_bfcp), TEST(test_bfcp_bin), TEST(test_bfcp_tcp), TEST(test_bfcp_udp), TEST(test_btrace), TEST(test_conf), TEST(test_crc32), TEST(test_dbg), TEST(test_dd), TEST(test_dns_dname), TEST(test_dns_hdr), TEST(test_dns_proto), TEST(test_dns_reg), TEST(test_dns_rr), TEST(test_dns_rr_dup), TEST(test_dsp), #ifdef USE_TLS TEST(test_dtls), TEST(test_dtls_srtp), #endif TEST(test_dtmf), TEST(test_fir), TEST(test_fmt_gmtime), TEST(test_fmt_hexdump), TEST(test_fmt_human_time), TEST(test_fmt_param), TEST(test_fmt_pl), TEST(test_fmt_pl_alloc_dup), TEST(test_fmt_pl_alloc_str), TEST(test_fmt_pl_float), TEST(test_fmt_pl_i32), TEST(test_fmt_pl_i64), TEST(test_fmt_pl_u32), TEST(test_fmt_pl_u64), TEST(test_fmt_pl_x3264), TEST(test_fmt_print), TEST(test_fmt_regex), TEST(test_fmt_snprintf), TEST(test_fmt_str), TEST(test_fmt_str_bool), TEST(test_fmt_str_error), TEST(test_fmt_str_itoa), TEST(test_fmt_str_wchar), TEST(test_fmt_timestamp), TEST(test_fmt_unicode), TEST(test_fmt_unicode_decode), TEST(test_g711_alaw), TEST(test_g711_ulaw), TEST(test_h264), TEST(test_h264_packet), TEST(test_h264_sps), TEST(test_h265), TEST(test_h265_packet), TEST(test_hash), TEST(test_hmac_sha1), TEST(test_hmac_sha256), TEST(test_http), TEST(test_http_conn), TEST(test_http_conn_large_body), TEST(test_http_large_body), TEST(test_http_loop), TEST(test_http_request_addr), #ifdef USE_TLS TEST(test_https_request_addr), #endif #ifdef USE_TLS TEST(test_https_loop), TEST(test_http_client_set_tls), TEST(test_https_large_body), #endif #ifdef HAVE_TLS1_3_POST_HANDSHAKE_AUTH TEST(test_https_conn_post_handshake), #endif TEST(test_httpauth_chall), TEST(test_httpauth_resp), TEST(test_httpauth_basic_request), TEST(test_httpauth_digest_request), TEST(test_httpauth_digest_response), TEST(test_httpauth_digest_verification), TEST(test_ice_cand), TEST(test_ice_loop), TEST(test_json), TEST(test_json_file), TEST(test_json_unicode), TEST(test_json_bad), TEST(test_json_array), TEST(test_list), TEST(test_list_flush), TEST(test_list_ref), TEST(test_list_sort), TEST(test_mbuf), TEST(test_md5), TEST(test_mem), TEST(test_mem_pool), TEST(test_mem_reallocarray), TEST(test_mem_secure), TEST(test_net_if), TEST(test_mqueue), TEST(test_odict), TEST(test_odict_array), TEST(test_odict_pl), TEST(test_pcp), TEST(test_remain), TEST(test_re_assert_se), TEST(test_rtmp_play), TEST(test_rtmp_publish), #ifdef USE_TLS TEST(test_rtmps_publish), #endif TEST(test_rtp), TEST(test_rtpext), TEST(test_rtcp_encode), TEST(test_rtcp_encode_afb), TEST(test_rtcp_decode), TEST(test_rtcp_decode_badmsg), TEST(test_rtcp_packetloss), TEST(test_rtcp_twcc), TEST(test_rtcp_xr), TEST(test_rtcp_loop), TEST(test_sa_class), TEST(test_sa_cmp), TEST(test_sa_decode), TEST(test_sa_ntop), TEST(test_sa_pton), TEST(test_sa_pton_linklocal), TEST(test_sdp_all), TEST(test_sdp_bfcp), TEST(test_sdp_parse), TEST(test_sdp_oa), TEST(test_sdp_extmap), TEST(test_sdp_disabled_rejected), TEST(test_sdp_interop), TEST(test_sha1), TEST(test_sip_addr), TEST(test_sip_apply), TEST(test_sip_auth), TEST(test_sip_drequestf), TEST(test_sip_dns), TEST(test_sip_hdr), TEST(test_sip_param), TEST(test_sip_parse), TEST(test_sip_via), #ifdef USE_TLS TEST(test_sip_transp_add_client_cert), #endif TEST(test_fmt_trim), TEST(test_sipevent), TEST(test_sipreg_tcp), #ifdef USE_TLS TEST(test_sipreg_tls), #endif TEST(test_sipreg_udp), TEST(test_sipsess), TEST(test_sipsess_100rel_420), TEST(test_sipsess_100rel_421), TEST(test_sipsess_100rel_answer_not_allowed), TEST(test_sipsess_100rel_caller_require), TEST(test_sipsess_100rel_supported), TEST(test_sipsess_blind_transfer), TEST(test_sipsess_reject), TEST(test_sipsess_update_no_sdp), TEST(test_sipsess_update_uac), TEST(test_sipsess_update_uas), TEST(test_srtcp), TEST(test_srtcp_gcm), TEST(test_srtp), TEST(test_srtp_gcm), TEST(test_stun), TEST(test_stun_req), TEST(test_stun_reqltc), TEST(test_stun_resp), TEST(test_sys_endian), TEST(test_sys_fs_fopen), TEST(test_sys_fs_gethome), TEST(test_sys_fs_isdir), TEST(test_sys_fs_isfile), TEST(test_sys_getenv), TEST(test_sys_rand), TEST(test_tcp), TEST(test_tcp_tos), TEST(test_telev), TEST(test_text2pcap), #ifdef USE_TLS TEST(test_tls), TEST(test_tls_ec), TEST(test_tls_selfsigned), TEST(test_tls_certificate), TEST(test_tls_false_cafile_path), TEST(test_tls_cli_conn_change_cert), TEST(test_tls_session_reuse_tls_v12), TEST(test_tls_sni), #endif TEST(test_thread), TEST(test_thread_tss), TEST(test_trace), TEST(test_trice_cand), TEST(test_trice_candpair), TEST(test_trice_checklist), TEST(test_trice_loop), TEST(test_try_into), TEST(test_turn), TEST(test_turn_tcp), TEST(test_udp), TEST(test_udp_tos), TEST(test_unixsock), TEST(test_uri), TEST(test_uri_encode), TEST(test_uri_escape), TEST(test_uri_headers), TEST(test_uri_params_headers), TEST(test_uri_user), TEST(test_vid), TEST(test_vidconv), TEST(test_vidconv_pixel_formats), TEST(test_vidconv_scaling), TEST(test_websock), #ifdef USE_TLS /* combination tests: */ TEST(test_dtls_turn), #endif }; static const struct test tests_integration[] = { TEST(test_dns_cache_http_integration), TEST(test_dns_http_integration), TEST(test_dns_integration), TEST(test_dns_nameservers), TEST(test_net_dst_source_addr_get), TEST(test_rtp_listen), TEST(test_sip_drequestf_network), TEST(test_sipevent_network), TEST(test_sipreg_tcp), #ifdef USE_TLS TEST(test_sipreg_tls), #endif TEST(test_sipreg_udp), TEST(test_tmr_jiffies), TEST(test_tmr_jiffies_usec), TEST(test_turn_thread), TEST(test_thread_cnd_timedwait), TEST(test_cplusplus), }; #ifdef DATA_PATH static char datapath[256] = DATA_PATH; #else static char datapath[256] = "./test/data"; #endif static uint32_t timeout_override; static int dup_stdout; static int dup_stderr; enum test_mode test_mode = TEST_NONE; static void hide_output(void) { dup_stdout = dup(fileno(stdout)); dup_stderr = dup(fileno(stderr)); #ifdef WIN32 int mode = _S_IREAD | _S_IWRITE; #else mode_t mode = S_IWUSR | S_IRUSR; #endif int fd_out = open("stdout.out", O_WRONLY | O_CREAT, mode); if (fd_out < 0) return; (void)dup2(fd_out, fileno(stdout)); int fd_err = open("stderr.out", O_WRONLY | O_CREAT, mode); if (fd_err < 0) return; (void)dup2(fd_err, fileno(stderr)); } static void restore_output(int err) { FILE *f = NULL; char line[1024]; fflush(stdout); fflush(stderr); /* Restore stdout/stderr */ (void)dup2(dup_stdout, fileno(stdout)); (void)dup2(dup_stderr, fileno(stderr)); if (!err) goto out; f = fopen("stdout.out", "r"); if (!f) goto out; while (fgets(line, sizeof(line), f)) { re_fprintf(stdout, "%s", line); } (void)fclose(f); f = fopen("stderr.out", "r"); if (!f) goto out; while (fgets(line, sizeof(line), f)) { re_fprintf(stderr, "%s", line); } (void)fclose(f); out: #ifdef WIN32 (void)_unlink("stdout.out"); (void)_unlink("stderr.out"); #else (void)unlink("stdout.out"); (void)unlink("stderr.out"); #endif } static const struct test *find_test(const char *name) { size_t i; for (i=0; iexec(); re_fhs_flush(); mem_get_stat(&mstat_after); if (mstat_after.blocks_cur > mstat_before.blocks_cur) { mem_debug_tail((uint32_t)(mstat_after.blocks_cur - mstat_before.blocks_cur)); re_assert(false && "Test leaks memory blocks"); } if (mstat_after.bytes_cur > mstat_before.bytes_cur) { mem_debug(); re_assert(false && "Test leaks memory bytes"); } return err; } /** * Run a single testcase in OOM (Out-of-memory) mode. * * Start with 0 blocks free, and increment by 1 until the test passes. * * * Blocks * Free * * /'\ * | * 5 | # * | # # * | # # # * | # # # # * 1 | # # # # # * '--------------> time */ static int testcase_oom(const struct test *test, int levels, bool verbose) { int i; int err = 0; if (verbose) (void)re_fprintf(stderr, " %-26s: ", test->name); /* All memory levels */ for (i=0; iname, i, err); goto out; } } out: if (verbose) (void)re_fprintf(stderr, "oom max %d\n", i); return err; } int test_oom(const char *name, bool verbose) { size_t i; const int levels = 128; int err = 0; test_mode = TEST_MEMORY; if (!verbose) hide_output(); (void)re_fprintf(stderr, "oom tests %u levels: \n", levels); if (name) { const struct test *test = find_test(name); if (!test) { (void)re_fprintf(stderr, "no such test: %s\n", name); err = ENOENT; goto out; } err = testcase_oom(test, levels, verbose); } else { /* All test cases */ for (i=0; i DRYRUN_USEC) break; } usec_avg = 1.0 * (usec_stop - usec_start) / (double)i; n = usec_avg ? (size_t)(REPEATS_USEC / usec_avg) : 0; n = min(REPEATS_MAX, max(n, REPEATS_MIN)); /* now for the real measurement */ usec_start = tmr_jiffies_usec(); for (i=0; iname, usec_avg, i); return 0; } struct timing { const struct test *test; uint64_t nsec_avg; }; /* * The comparison function must return an integer less than, equal to, * or greater than zero if the first argument is considered to be * respectively less than, equal to, or greater than the second. * * If two members compare as equal, their order in the sorted array * is undefined. */ static int timing_cmp(const void *p1, const void *p2) { const struct timing *v1 = p1; const struct timing *v2 = p2; if (v1->nsec_avg < v2->nsec_avg) return 1; else if (v1->nsec_avg > v2->nsec_avg) return -1; else return 0; } int test_perf(const char *name, bool verbose) { int err = 0; unsigned i; (void)verbose; test_mode = TEST_PERF; if (name) { const struct test *test; test = find_test(name); if (!test) { (void)re_fprintf(stderr, "no such test: %s\n", name); return ENOENT; } err = testcase_perf(test, NULL); if (err) return err; } else { struct timing timingv[RE_ARRAY_SIZE(tests)]; memset(&timingv, 0, sizeof(timingv)); /* All test cases */ for (i=0; itest = &tests[i]; err = testcase_perf(&tests[i], &usec_avg); if (!verbose) restore_output(err); if (err) { if (err == ESKIPPED || err == ENOSYS) { re_printf("skipped: %s\n", tests[i].name); tim->test = NULL; continue; } DEBUG_WARNING("perf: %s failed (%m)\n", tests[i].name, err); return err; } tim->nsec_avg = (uint64_t)(1000.0 * usec_avg); } /* sort the timing table by average time */ qsort(timingv, RE_ARRAY_SIZE(timingv), sizeof(timingv[0]), timing_cmp); re_fprintf(stderr, "\nsorted by average timing (slowest on top):\n"); for (i=0; insec_avg / 1000.0; if (!tim->test) continue; re_fprintf(stderr, "%-34s: %10.2f usec\n", tim->test->name, usec_avg); } re_fprintf(stderr, "\n"); } return err; } int test_reg(const char *name, bool verbose) { int err; test_mode = TEST_REGULAR; timeout_override = 10000; (void)re_fprintf(stderr, "regular tests: "); err = test_unit(name, verbose); if (err) return err; (void)re_fprintf(stderr, "\x1b[32mOK\x1b[;m\n"); timeout_override = 0; return 0; } struct thread { const struct test *test; thrd_t tid; int err; }; static int thread_handler(void *arg) { struct thread *thr = arg; int err; err = re_thread_init(); if (err) { DEBUG_WARNING("thread: re_thread_init failed %m\n", err); thr->err = err; return 0; } err = thr->test->exec(); if (err) { if (err == ESKIPPED) { err = 0; } else { DEBUG_WARNING("%s: test failed (%m)\n", thr->test->name, err); } } re_thread_close(); /* safe to write it, main thread is waiting for us */ thr->err = err; return 0; } /* Run all test-cases in multiple threads */ int test_multithread(void) { #define NUM_REPEAT 2 #define NUM_TOTAL (NUM_REPEAT * RE_ARRAY_SIZE(tests)) struct thread threadv[NUM_TOTAL]; size_t test_index=0; size_t i; int err = 0; test_mode = TEST_THREAD; timeout_override = 20000; memset(threadv, 0, sizeof(threadv)); (void)re_fprintf(stderr, "multithread: %zu tests" " with %d repeats (total %zu threads): ", RE_ARRAY_SIZE(tests), NUM_REPEAT, NUM_TOTAL); for (i=0; iname, threadv[i].err, threadv[i].err); err = threadv[i].err; } } if (err) return err; (void)re_fprintf(stderr, "\x1b[32mOK\x1b[;m\n"); timeout_override = 0; return 0; } void test_listcases(void) { size_t i, n, nh; n = RE_ARRAY_SIZE(tests); nh = (n+1)/2; (void)re_printf("\n%zu test cases:\n", n); for (i=0; i= alen; if (wrong) (void)re_fprintf(f, "\x1b[35m"); (void)re_fprintf(f, " %02x", ebuf[pos]); if (wrong) (void)re_fprintf(f, "\x1b[;m"); } else (void)re_fprintf(f, " "); } (void)re_fprintf(f, " "); for (j=0; jname) return EINVAL; (void)re_fprintf(stderr, " %-24s: ", test->name); if (test->exec) err = test->exec(); if (err) DEBUG_WARNING(" %-24s: NOK: %m\n", test->name, err); else (void)re_fprintf(stderr, "\x1b[32mOK\x1b[;m\n"); return err; } for (i=0; iname) continue; (void)re_fprintf(stderr, " %-32s: ", test->name); if (test->exec) err = test->exec(); if (err) { DEBUG_WARNING(" %-24s: NOK: %m\n", test->name, err); break; } else { (void)re_fprintf(stderr, "\x1b[32mOK\x1b[;m\t\n"); } } return err; } ================================================ FILE: test/test.h ================================================ /** * @file test.h Interface to regression testcode * * Copyright (C) 2010 Creytiv.com */ enum test_mode { TEST_NONE, TEST_REGULAR, TEST_MEMORY, TEST_PERF, TEST_THREAD }; /* * Global test mode */ extern enum test_mode test_mode; /* * Special negative error code for a skipped test */ #define ESKIPPED (-1000) #define TEST_EINVAL(func, ...) \ err = func(__VA_ARGS__); \ if (err != EINVAL) \ goto out; #define TEST_EQUALS(expected, actual) \ if ((expected) != (actual)) { \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_EQUALS: %s:%u: %s():" \ " expected=%d(0x%x), actual=%d(0x%x)\n", \ __FILE__, __LINE__, __func__, \ (expected), (expected), \ (actual), (actual)); \ err = EINVAL; \ goto out; \ } #define TEST_NOT_EQUALS(expected, actual) \ if ((expected) == (actual)) { \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_NOT_EQUALS: %s:%u:" \ " expected=%d != actual=%d\n", \ __FILE__, __LINE__, \ (expected), (actual)); \ err = EINVAL; \ goto out; \ } #define TEST_MEMCMP(expected, expn, actual, actn) \ if (expn != actn || \ 0 != memcmp((expected), (actual), (expn))) { \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_MEMCMP: %s:%u:" \ " %s(): failed\n", \ __FILE__, __LINE__, __func__); \ test_hexdump_dual(stderr, \ expected, expn, \ actual, actn); \ err = EINVAL; \ goto out; \ } #define TEST_STRCMP(expected, expn, actual, actn) \ if (expn != actn || \ 0 != memcmp((expected), (actual), (expn))) { \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_STRCMP: %s:%u:" \ " failed\n", \ __FILE__, __LINE__); \ (void)re_fprintf(stderr, \ "expected string: (%zu bytes)\n" \ "\"%b\"\n", \ (size_t)(expn), \ (expected), (size_t)(expn)); \ (void)re_fprintf(stderr, \ "actual string: (%zu bytes)\n" \ "\"%b\"\n", \ (size_t)(actn), \ (actual), (size_t)(actn)); \ err = EINVAL; \ goto out; \ } #define TEST_ASSERT(actual) \ if (!(actual)) { \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_ASSERT: %s:%u:" \ " actual=%d\n", \ __FILE__, __LINE__, \ (actual)); \ err = EINVAL; \ goto out; \ } #define TEST_ERR(err) \ if ((err)) { \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_ERR: %s:%u:" \ " (%m)\n", \ __FILE__, __LINE__, \ (err)); \ goto out; \ } #define TEST_SACMP(expect, actual, flags) \ if (!sa_cmp((expect), (actual), (flags))) { \ \ (void)re_fprintf(stderr, "\n"); \ DEBUG_WARNING("TEST_SACMP: %s:%u:" \ " %s(): failed\n", \ __FILE__, __LINE__, __func__); \ DEBUG_WARNING("expected: %J\n", (expect)); \ DEBUG_WARNING("actual: %J\n", (actual)); \ err = EADDRNOTAVAIL; \ goto out; \ } /* * NOTE: try to reuse macros from Gtest. */ #define ASSERT_EQ(expected, actual) \ if ((expected) != (actual)) { \ DEBUG_WARNING("ASSERT_EQ: %s:%u: %s():" \ " expected=%d(0x%x), actual=%d(0x%x)\n", \ __FILE__, __LINE__, __func__, \ (expected), (expected), \ (actual), (actual)); \ err = EINVAL; \ goto out; \ } #define ASSERT_DOUBLE_EQ(expected, actual, prec) \ if (!test_cmp_double((expected), (actual), (prec))) { \ DEBUG_WARNING("selftest: ASSERT_DOUBLE_EQ: %s:%u:" \ " expected=%f, actual=%f\n", \ __FILE__, __LINE__, \ (double)(expected), (double)(actual)); \ err = EINVAL; \ goto out; \ } #define ASSERT_TRUE(cond) \ if (!(cond)) { \ DEBUG_WARNING("ASSERT_TRUE: %s:%u:\n", \ __FILE__, __LINE__); \ err = EINVAL; \ goto out; \ } /* Module API */ int test_aac(void); int test_aes(void); int test_aes_gcm(void); int test_au(void); int test_aubuf(void); int test_aulevel(void); int test_aulength(void); int test_auposition(void); int test_auresamp(void); int test_async(void); int test_av1(void); int test_dd(void); int test_base64(void); int test_bfcp(void); int test_bfcp_bin(void); int test_bfcp_udp(void); int test_bfcp_tcp(void); int test_btrace(void); int test_conf(void); int test_crc32(void); int test_dbg(void); int test_dns_dname(void); int test_dns_hdr(void); int test_dns_integration(void); int test_dns_nameservers(void); int test_dns_proto(void); int test_dns_reg(void); int test_dns_rr(void); int test_dns_rr_dup(void); int test_dsp(void); int test_dtmf(void); int test_fir(void); int test_fmt_gmtime(void); int test_fmt_hexdump(void); int test_fmt_human_time(void); int test_fmt_param(void); int test_fmt_pl(void); int test_fmt_pl_alloc_dup(void); int test_fmt_pl_alloc_str(void); int test_fmt_pl_float(void); int test_fmt_pl_i32(void); int test_fmt_pl_i64(void); int test_fmt_pl_u32(void); int test_fmt_pl_u64(void); int test_fmt_pl_x3264(void); int test_fmt_print(void); int test_fmt_regex(void); int test_fmt_snprintf(void); int test_fmt_str(void); int test_fmt_str_bool(void); int test_fmt_str_error(void); int test_fmt_str_itoa(void); int test_fmt_str_wchar(void); int test_fmt_timestamp(void); int test_fmt_trim(void); int test_fmt_unicode(void); int test_fmt_unicode_decode(void); int test_g711_alaw(void); int test_g711_ulaw(void); int test_h264(void); int test_h264_sps(void); int test_h264_packet(void); int test_h265(void); int test_h265_packet(void); int test_hash(void); int test_hmac_sha1(void); int test_hmac_sha256(void); int test_http(void); int test_http_loop(void); int test_http_large_body(void); int test_http_conn(void); int test_http_conn_large_body(void); int test_dns_http_integration(void); int test_dns_cache_http_integration(void); int test_http_request_addr(void); #ifdef USE_TLS int test_https_request_addr(void); #endif #ifdef USE_TLS int test_https_loop(void); int test_http_client_set_tls(void); int test_https_large_body(void); #endif #ifdef HAVE_TLS1_3_POST_HANDSHAKE_AUTH int test_https_conn_post_handshake(void); #endif int test_httpauth_chall(void); int test_httpauth_resp(void); int test_httpauth_basic_request(void); int test_httpauth_digest_request(void); int test_httpauth_digest_response(void); int test_httpauth_digest_verification(void); int test_ice_loop(void); int test_ice_cand(void); int test_json(void); int test_json_bad(void); int test_json_file(void); int test_json_unicode(void); int test_json_array(void); int test_list(void); int test_list_flush(void); int test_list_ref(void); int test_list_sort(void); int test_mbuf(void); int test_md5(void); int test_mem(void); int test_mem_pool(void); int test_mem_reallocarray(void); int test_mem_secure(void); int test_mqueue(void); int test_net_if(void); int test_net_dst_source_addr_get(void); int test_odict(void); int test_odict_array(void); int test_odict_pl(void); int test_pcp(void); int test_trice_cand(void); int test_trice_candpair(void); int test_trice_checklist(void); int test_trice_loop(void); int test_remain(void); int test_re_assert_se(void); int test_rtmp_play(void); int test_rtmp_publish(void); #ifdef USE_TLS int test_rtmps_publish(void); #endif int test_rtp(void); int test_rtp_listen(void); int test_rtpext(void); int test_rtcp_encode(void); int test_rtcp_encode_afb(void); int test_rtcp_decode(void); int test_rtcp_decode_badmsg(void); int test_rtcp_packetloss(void); int test_rtcp_twcc(void); int test_rtcp_xr(void); int test_rtcp_loop(void); int test_sa_class(void); int test_sa_cmp(void); int test_sa_decode(void); int test_sa_ntop(void); int test_sa_pton(void); int test_sa_pton_linklocal(void); int test_sdp_all(void); int test_sdp_bfcp(void); int test_sdp_parse(void); int test_sdp_oa(void); int test_sdp_extmap(void); int test_sdp_disabled_rejected(void); int test_sdp_interop(void); int test_sha1(void); int test_sip_addr(void); int test_sip_auth(void); int test_sip_drequestf(void); int test_sip_apply(void); int test_sip_hdr(void); int test_sip_msg(void); int test_sip_param(void); int test_sip_parse(void); int test_sip_via(void); int test_sip_dns(void); #ifdef USE_TLS int test_sip_transp_add_client_cert(void); #endif int test_sipevent(void); int test_sipreg_udp(void); int test_sipreg_tcp(void); #ifdef USE_TLS int test_sipreg_tls(void); #endif int test_sipsess(void); int test_sipsess_reject(void); int test_sipsess_blind_transfer(void); int test_sipsess_100rel_caller_require(void); int test_sipsess_100rel_supported(void); int test_sipsess_100rel_answer_not_allowed(void); int test_sipsess_100rel_420(void); int test_sipsess_100rel_421(void); int test_sipsess_update_uac(void); int test_sipsess_update_uas(void); int test_sipsess_update_no_sdp(void); int test_srtp(void); int test_srtcp(void); int test_srtp_gcm(void); int test_srtcp_gcm(void); int test_stun_req(void); int test_stun_resp(void); int test_stun_reqltc(void); int test_stun(void); int test_sys_endian(void); int test_sys_rand(void); int test_sys_fs_fopen(void); int test_sys_fs_gethome(void); int test_sys_fs_isdir(void); int test_sys_fs_isfile(void); int test_sys_getenv(void); int test_tcp(void); int test_tcp_tos(void); int test_telev(void); int test_text2pcap(void); int test_thread(void); int test_thread_cnd_timedwait(void); int test_thread_tss(void); int test_tmr_jiffies(void); int test_tmr_jiffies_usec(void); int test_try_into(void); int test_turn(void); int test_turn_tcp(void); int test_turn_thread(void); int test_udp(void); int test_udp_tos(void); int test_unixsock(void); int test_uri(void); int test_uri_encode(void); int test_uri_headers(void); int test_uri_user(void); int test_uri_params_headers(void); int test_uri_escape(void); int test_vid(void); int test_vidconv(void); int test_vidconv_scaling(void); int test_vidconv_pixel_formats(void); int test_websock(void); int test_trace(void); #ifdef USE_TLS int test_dtls(void); int test_dtls_srtp(void); int test_tls(void); int test_tls_ec(void); int test_tls_selfsigned(void); int test_tls_certificate(void); int test_tls_false_cafile_path(void); int test_tls_cli_conn_change_cert(void); int test_tls_session_reuse_tls_v12(void); int test_tls_session_reuse(void); int test_tls_sni(void); #endif #ifdef USE_TLS int test_dtls_turn(void); #endif #ifdef USE_TLS extern const char test_certificate_ecdsa[]; #endif /* Integration tests */ int test_integration(const char *name, bool verbose); int test_sipevent_network(void); int test_sip_drequestf_network(void); #ifdef __cplusplus extern "C" { #endif int test_cplusplus(void); #ifdef __cplusplus } #endif /* High-level API */ int test_reg(const char *name, bool verbose); int test_oom(const char *name, bool verbose); int test_perf(const char *name, bool verbose); int test_multithread(void); void test_listcases(void); void test_hexdump_dual(FILE *f, const void *ep, size_t elen, const void *ap, size_t alen); bool test_cmp_double(double a, double b, double precision); int re_main_timeout(uint32_t timeout_ms); int test_load_file(struct mbuf *mb, const char *filename); int test_write_file(struct mbuf *mb, const char *filename); void test_set_datapath(const char *path); const char *test_datapath(void); bool test_ipv6_supported(void); /* * Mock objects */ struct stunserver { struct udp_sock *us; struct tcp_sock *ts; struct tcp_conn *tc; struct mbuf *mb; struct sa laddr; struct sa laddr_tcp; struct sa paddr; uint32_t nrecv; int err; }; int stunserver_alloc(struct stunserver **stunp, const char *laddr); const struct sa *stunserver_addr(const struct stunserver *stun, int proto); struct turnserver { struct udp_sock *us; struct sa laddr; struct tcp_sock *ts; struct sa laddr_tcp; struct tcp_conn *tc; struct sa paddr; struct mbuf *mb; struct udp_sock *us_relay; struct sa cli; struct sa relay; char addr[64]; const char *auth_realm; uint64_t auth_secret; uint16_t error_scode; struct channel { uint16_t nr; struct sa peer; } chanv[4]; size_t chanc; struct sa permv[4]; size_t permc; size_t n_allocate; size_t n_createperm; size_t n_chanbind; size_t n_send; size_t n_raw; size_t n_recv; }; int turnserver_alloc(struct turnserver **turnp, const char *addr); void turnserver_force_error(struct turnserver *turn, uint16_t scode); enum natbox_type { NAT_INBOUND_SNAT, /* NOTE: must be installed on receiving socket */ NAT_FIREWALL, }; /** * A simple NAT-box that can be hooked onto a UDP-socket. * * The NAT behaviour is port-preserving and will rewrite the source * IP-address to the public address. */ struct nat { enum natbox_type type; struct sa public_addr; struct udp_helper *uh; struct udp_sock *us; struct sa bindingv[16]; size_t bindingc; }; int nat_alloc(struct nat **natp, enum natbox_type type, struct udp_sock *us, const struct sa *public_addr); /* * SIP Server */ struct sip_server { struct sip *sip; struct sip_lsnr *lsnr; bool terminate; unsigned n_register_req; unsigned n_options_req; struct sip_msg *sip_msgs[16]; }; int sip_server_alloc(struct sip_server **srvp); int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, enum sip_transp tp); /* * Mock DNS-Server */ struct dns_server { struct udp_sock *us; struct tcp_sock *ts; struct sa addr; struct sa addr_tcp; struct list rrl; /* per TCP-connection: */ struct tcp_conn *tc; struct mbuf *mb; uint16_t flen; }; int dns_server_alloc(struct dns_server **srvp, const char *laddr); int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr, int64_t ttl); int dns_server_add_aaaa(struct dns_server *srv, const char *name, const uint8_t *addr, int64_t ttl); int dns_server_add_srv(struct dns_server *srv, const char *name, uint16_t pri, uint16_t weight, uint16_t port, const char *target, int64_t ttl); void dns_server_flush(struct dns_server *srv); ================================================ FILE: test/thread.c ================================================ /** * @file src/thread.c re threads * * Copyright (C) 2022 Sebastian Reimers */ #include #include #include "test.h" #define DEBUG_MODULE "thread" #define DEBUG_LEVEL 5 #include static int thread_equal(void *thrd) { thrd_t t = *(thrd_t *)thrd; if (thrd_equal(t, thrd_current())) return 0; return EINVAL; } static int thread(void *id) { int n = *(int *)id; if (n != 42) return thrd_error; return EPROTO; } int test_thread(void) { thrd_t thr; thrd_t thr_main = thrd_current(); int err; int id; err = thread_create_name(&thr, "test1", NULL, NULL); TEST_EQUALS(EINVAL, err); id = 23; err = thread_create_name(&thr, "test2", thread, (void *)&id); TEST_ERR(err); thrd_join(thr, &err); TEST_EQUALS(thrd_error, err); id = 42; err = thread_create_name(&thr, "test3", thread, (void *)&id); TEST_ERR(err); thrd_join(thr, &err); TEST_EQUALS(EPROTO, err); err = thread_create_name(&thr, "test_not_equal", thread_equal, &thr_main); TEST_ERR(err); thrd_join(thr, &err); TEST_EQUALS(EINVAL, err); err = thread_create_name(&thr, "test_equal", thread_equal, &thr); TEST_ERR(err); TEST_EQUALS(0, thrd_equal(thr, thrd_current())); thrd_join(thr, &err); TEST_EQUALS(0, err); err = 0; out: return err; } int test_thread_cnd_timedwait(void) { cnd_t cnd; mtx_t mtx; struct timespec tp; int err = 0; cnd_init(&cnd); mtx_init(&mtx, mtx_plain); err = tmr_timespec_get(&tp, 100); TEST_ERR(err); mtx_lock(&mtx); uint64_t start = tmr_jiffies(); int ret = cnd_timedwait(&cnd, &mtx, &tp); TEST_EQUALS(thrd_timedout, ret); uint64_t end = tmr_jiffies(); /* This tests can fail if a spurious wake-up occurs */ if (end - start < 100) { DEBUG_WARNING("cnd_timedwait: early wake-up!\n"); goto out; } if (end - start > 500) { err = ETIMEDOUT; TEST_ERR(err); } out: mtx_unlock(&mtx); return err; } int test_thread_tss(void) { int err = 0; int val = 1234; tss_t key; TEST_EQUALS(thrd_success, tss_create(&key, NULL)); TEST_EQUALS(thrd_success, tss_set(key, &val)); TEST_EQUALS(&val, tss_get(key)); TEST_EQUALS(thrd_success, tss_set(key, NULL)); TEST_EQUALS(NULL, tss_get(key)); out: tss_delete(key); return err; } ================================================ FILE: test/tls.c ================================================ /** * @file tls.c TLS testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "tlstest" #define DEBUG_LEVEL 5 #include struct tls_test { struct tls *tls; struct tls *tls2; struct tls_conn *sc_cli; struct tls_conn *sc_srv; struct tcp_sock *ts; struct tcp_conn *tc_cli; struct tcp_conn *tc_srv; enum tls_keytype keytype; bool estab_cli; bool estab_srv; bool send_done_cli; size_t recv_cli; size_t recv_srv; int err; }; static const char *payload = "0123456789"; static void check(struct tls_test *tt, int err) { if (tt->err == 0) tt->err = err; if (tt->err) re_cancel(); } static void can_send(struct tls_test *tt) { struct mbuf *mb; int err = 0; if (!tt->estab_cli || !tt->estab_srv || tt->send_done_cli) return; mb = mbuf_alloc(256); if (!mb) { err = ENOMEM; goto out; } err = mbuf_write_str(mb, payload); if (err) goto out; mb->pos = 0; err = tcp_send(tt->tc_cli, mb); if (!err) tt->send_done_cli = true; out: mem_deref(mb); check(tt, err); } static void client_estab_handler(void *arg) { struct tls_test *tt = arg; int err = 0; tt->estab_cli = true; can_send(tt); check(tt, err); } static void client_recv_handler(struct mbuf *mb, void *arg) { struct tls_test *tt = arg; int err = 0; if (!tt->estab_cli) { (void)re_fprintf(stderr, "unexpected data received" " on client [%02w]\n", mbuf_buf(mb), mbuf_get_left(mb)); check(tt, EPROTO); } ++tt->recv_cli; TEST_MEMCMP(payload, strlen(payload), mbuf_buf(mb), mbuf_get_left(mb)); out: check(tt, err); /* We are done */ re_cancel(); } static void client_close_handler(int err, void *arg) { struct tls_test *tt = arg; if (!tt->estab_cli) check(tt, err); } static void server_estab_handler(void *arg) { struct tls_test *tt = arg; tt->estab_srv = true; can_send(tt); } static void server_recv_handler(struct mbuf *mb, void *arg) { struct tls_test *tt = arg; int err = 0; if (!tt->estab_srv) { check(tt, EPROTO); return; } ++tt->recv_srv; TEST_MEMCMP(payload, strlen(payload), mbuf_buf(mb), mbuf_get_left(mb)); /* echo */ err = tcp_send(tt->tc_srv, mb); if (err) { DEBUG_WARNING("server: tcp_send error (%m)\n", err); } out: check(tt, err); } static void server_close_handler(int err, void *arg) { struct tls_test *tt = arg; if (!tt->estab_cli) check(tt, err); } static void server_conn_handler(const struct sa *peer, void *arg) { struct tls_test *tt = arg; int err; (void)peer; err = tcp_accept(&tt->tc_srv, tt->ts, server_estab_handler, server_recv_handler, server_close_handler, tt); check(tt, err); err = tls_start_tcp(&tt->sc_srv, tt->tls, tt->tc_srv, 0); check(tt, err); } static int test_tls_base(enum tls_keytype keytype, bool add_ca, int exp_verr, bool test_sess_reuse, int forced_version) { struct tls_test tt; struct sa srv; int err, verr; unsigned long int i, rounds = 1 + (unsigned long int) test_sess_reuse; memset(&tt, 0, sizeof(tt)); tt.keytype = keytype; err = sa_set_str(&srv, "127.0.0.1", 0); if (err) goto out; err = tls_alloc(&tt.tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; if (forced_version >= 0) { TEST_EQUALS(0, tls_set_min_proto_version(tt.tls, forced_version)); TEST_EQUALS(0, tls_set_max_proto_version(tt.tls, forced_version)); } switch (keytype) { case TLS_KEYTYPE_EC: err = tls_set_certificate(tt.tls, test_certificate_ecdsa, strlen(test_certificate_ecdsa)); if (err) goto out; break; default: err = EINVAL; goto out; } if (add_ca) { char cafile[256]; re_snprintf(cafile, sizeof(cafile), "%s/server-ecdsa.pem", test_datapath()); err = tls_add_ca(tt.tls, cafile); if (err) goto out; } err = tcp_listen(&tt.ts, &srv, server_conn_handler, &tt); if (err) goto out; err = tcp_sock_local_get(tt.ts, &srv); if (err) goto out; err = tls_set_session_reuse(tt.tls, test_sess_reuse); if (err) goto out; for (i = 0; i < rounds; i++) { tt.send_done_cli = false; err = tcp_connect(&tt.tc_cli, &srv, client_estab_handler, client_recv_handler, client_close_handler, &tt); if (err) goto out; err = tls_start_tcp(&tt.sc_cli, tt.tls, tt.tc_cli, 0); if (err) goto out; if (exp_verr == 0) { err = tls_set_verify_server(tt.sc_cli, "127.0.0.1"); if (err) goto out; } err = re_main_timeout(800); if (err) goto out; if (tt.err) { err = tt.err; goto out; } TEST_EQUALS(true, tt.estab_cli); TEST_EQUALS(true, tt.estab_srv); TEST_EQUALS(1, tt.recv_cli); TEST_EQUALS(1+i, tt.recv_srv); verr = tls_peer_verify(tt.sc_cli); TEST_EQUALS(exp_verr, verr); if (test_sess_reuse) { TEST_EQUALS(i == 0 ? false : true, tls_session_reused(tt.sc_cli)); } tt.sc_cli = mem_deref(tt.sc_cli); tt.sc_srv = mem_deref(tt.sc_srv); tt.tc_cli = mem_deref(tt.tc_cli); tt.tc_srv = mem_deref(tt.tc_srv); tt.estab_cli = false; tt.estab_srv = false; tt.recv_cli = 0; } out: /* NOTE: close context first */ mem_deref(tt.tls); mem_deref(tt.sc_cli); mem_deref(tt.sc_srv); mem_deref(tt.tc_cli); mem_deref(tt.tc_srv); mem_deref(tt.ts); return err; } int test_tls_session_reuse_tls_v12(void) { return test_tls_base(TLS_KEYTYPE_EC, false, EAUTH, true, TLS1_2_VERSION); } /* TLS v1.3 session reuse is not yet supported by libre */ int test_tls_session_reuse(void) { return test_tls_base(TLS_KEYTYPE_EC, false, EAUTH, true, -1); } int test_tls(void) { return test_tls_base(TLS_KEYTYPE_EC, false, EAUTH, false, -1); } int test_tls_ec(void) { int err; err = test_tls_base(TLS_KEYTYPE_EC, false, EAUTH, false, -1); TEST_ERR(err); err = test_tls_base(TLS_KEYTYPE_EC, true, 0, false, -1); TEST_ERR(err); out: return err; } int test_tls_selfsigned(void) { struct tls *tls = NULL; uint8_t fp[32]; int err; err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; err = tls_set_selfsigned_ec(tls, "re_ec@test", "unknown"); TEST_EQUALS(err, ENOTSUP); err = tls_set_selfsigned_ec(tls, "re_ec@test", "prime256v1"); TEST_ERR(err); /* verify fingerprint of the self-signed certificate */ err = tls_fingerprint(tls, TLS_FINGERPRINT_SHA256, fp, sizeof(fp)); TEST_ERR(err); out: mem_deref(tls); return err; } int test_tls_certificate(void) { struct tls *tls = NULL; re_nonstring static const uint8_t test_fingerprint[32] = "\x50\x5d\x95\x2b\xef\x5b\x6f\x7f" "\x2b\x4a\xa8\x1b\xdd\xe1\x99\xfd" "\x4e\xb5\xc1\x04\xe7\x67\xa7\x48" "\xb1\xf1\x66\x35\x98\xdc\x84\xc6"; uint8_t fp[32]; struct mbuf *mb = NULL; int err; err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; mb = mbuf_alloc(20); if (!mb) { err = ENOMEM; goto out; } err = tls_get_subject(tls, mb); TEST_EQUALS(ENOENT, err); err = tls_set_certificate(tls, test_certificate_ecdsa, strlen(test_certificate_ecdsa)); TEST_EQUALS(0, err); /* verify fingerprint of the certificate */ err = tls_fingerprint(tls, TLS_FINGERPRINT_SHA256, fp, sizeof(fp)); TEST_ERR(err); TEST_MEMCMP(test_fingerprint, sizeof(test_fingerprint), fp, sizeof(fp)); err = tls_get_subject(tls, mb); TEST_ERR(err); out: mem_deref(tls); mem_deref(mb); return err; } int test_tls_false_cafile_path(void) { int err = 0; struct tls *tls = NULL; const char *cafile_wrong = "/some/path/to/wrong/file.crt"; const char *capath_wrong = "/some/path/to/nothing"; err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; err = tls_add_cafile_path(tls, NULL, NULL); TEST_EQUALS(EINVAL, err); err = tls_add_cafile_path(tls, cafile_wrong, NULL); TEST_EQUALS(ENOENT, err); err = tls_add_cafile_path(tls, NULL, capath_wrong); TEST_EQUALS(ENOTDIR, err); err = tls_add_cafile_path(tls, cafile_wrong, capath_wrong); TEST_EQUALS(ENOTDIR, err); err = 0; out: mem_deref(tls); return err; } int test_tls_cli_conn_change_cert(void) { struct tls_test tt; struct sa srv; int err; char clientcert[256]; char clientcert_cn[256]; char *exp_clientcert_cn = "Mr Retest Client Cert"; memset(&tt, 0, sizeof(tt)); tt.keytype = TLS_KEYTYPE_EC; err = sa_set_str(&srv, "127.0.0.1", 0); if (err) goto out; err = tls_alloc(&tt.tls, TLS_METHOD_SSLV23, NULL, NULL); if (err) goto out; tls_set_verify_client_trust_all(tt.tls); err = tls_set_certificate(tt.tls, test_certificate_ecdsa, strlen(test_certificate_ecdsa)); if (err) goto out; err = tcp_listen(&tt.ts, &srv, server_conn_handler, &tt); if (err) goto out; err = tcp_sock_local_get(tt.ts, &srv); if (err) goto out; err = tcp_connect(&tt.tc_cli, &srv, client_estab_handler, client_recv_handler, client_close_handler, &tt); if (err) goto out; err = tls_start_tcp(&tt.sc_cli, tt.tls, tt.tc_cli, 0); if (err) goto out; /* actual test cases*/ err = tls_conn_change_cert(tt.sc_cli, NULL); TEST_EQUALS(EINVAL, err); err = tls_conn_change_cert(NULL, clientcert); TEST_EQUALS(EINVAL, err); memset(clientcert, 0, sizeof(clientcert)); (void)re_snprintf(clientcert, sizeof(clientcert), "%s/not_a_file.pem", test_datapath()); int ret = tls_conn_change_cert(tt.sc_cli, clientcert); ASSERT_TRUE(ret==EINVAL || ret==ENOENT || ret==ENOSYS); if (ret == ENOSYS) { err = 0; goto out; } memset(clientcert, 0, sizeof(clientcert)); (void)re_snprintf(clientcert, sizeof(clientcert), "%s/client_wrongkey.pem", test_datapath()); err = tls_conn_change_cert(tt.sc_cli, clientcert); TEST_EQUALS(EKEYREJECTED, err); memset(clientcert, 0, sizeof(clientcert)); (void)re_snprintf(clientcert, sizeof(clientcert), "%s/client.pem", test_datapath()); err = tls_conn_change_cert(tt.sc_cli, clientcert); if (err) goto out; err = re_main_timeout(800); if (err) goto out; err = tls_peer_common_name(tt.sc_srv, clientcert_cn, sizeof(clientcert_cn)); if (err) { if (!tt.sc_srv) { TEST_EQUALS(EINVAL, err); err = 0; goto out; } } TEST_STRCMP(exp_clientcert_cn, strlen(exp_clientcert_cn), clientcert_cn, strlen(clientcert_cn)); if (tt.err) { err = tt.err; goto out; } out: /* NOTE: close context first */ mem_deref(tt.tls); mem_deref(tt.sc_cli); mem_deref(tt.sc_srv); mem_deref(tt.tc_cli); mem_deref(tt.tc_srv); mem_deref(tt.ts); return err; } /** * SNI Test * * - UAS opens a TLS connection to UAC by sending a client hello with SNI * - UAC chooses a certificate with matching SNI * - UAC verifies the chain and common name of the UAS * - UAS verifies the chain and common name of the UAC * * @return 0 if success, otherwise errorcode */ int test_tls_sni(void) { struct tls_test tt; struct sa srv; int err; const char *dp = test_datapath(); char path[256]; memset(&tt, 0, sizeof(tt)); err = sa_set_str(&srv, "127.0.0.1", 0); TEST_ERR(err); /* UAC global not matching cert (test checks that it is not used) */ re_snprintf(path, sizeof(path), "%s/client.pem", dp); err = tls_alloc(&tt.tls, TLS_METHOD_SSLV23, path, NULL); TEST_ERR(err); /* UAS cert + intermediate CA */ re_snprintf(path, sizeof(path), "%s/sni/server-interm.pem", dp); err = tls_alloc(&tt.tls2, TLS_METHOD_SSLV23, path, NULL); TEST_ERR(err); /* set root CA at UAC and UAS */ re_snprintf(path, sizeof(path), "%s/sni/root-ca.pem", dp); err = tls_add_ca(tt.tls, path); err |= tls_add_ca(tt.tls2, path); TEST_ERR(err); /* UAC cert + intermediate CA */ re_snprintf(path, sizeof(path), "%s/sni/client-interm.pem", dp); err = tls_add_certf(tt.tls, path, "retest.server.org"); TEST_ERR(err); /* UAC listens (as TLS server)*/ err = tcp_listen(&tt.ts, &srv, server_conn_handler, &tt); TEST_ERR(err); err = tcp_sock_local_get(tt.ts, &srv); TEST_ERR(err); /* UAS connects to UAC (as TLS client) */ err = tcp_connect(&tt.tc_cli, &srv, client_estab_handler, client_recv_handler, client_close_handler, &tt); TEST_ERR(err); err = tls_start_tcp(&tt.sc_cli, tt.tls2, tt.tc_cli, 0); TEST_ERR(err); err = tls_set_verify_server(tt.sc_cli, "retest.client.org"); TEST_ERR(err); err = re_main_timeout(800); TEST_ERR(err); if (tt.err) { err = tt.err; goto out; } ASSERT_TRUE(tt.estab_cli); ASSERT_TRUE(tt.estab_srv); ASSERT_EQ(1, tt.recv_cli); ASSERT_EQ(1, tt.recv_srv); err = tls_peer_verify(tt.sc_cli); ASSERT_EQ(0, err); out: /* NOTE: close context first */ mem_deref(tt.tls); mem_deref(tt.tls2); mem_deref(tt.sc_cli); mem_deref(tt.sc_srv); mem_deref(tt.tc_cli); mem_deref(tt.tc_srv); mem_deref(tt.ts); return err; } ================================================ FILE: test/tmr.c ================================================ /** * @file tmr.c Timers testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "testtmr" #define DEBUG_LEVEL 5 #include int test_tmr_jiffies(void) { uint64_t tmr_start, tmr_end, diff; int err = 0; tmr_start = tmr_jiffies(); sys_msleep(1); tmr_end = tmr_jiffies(); diff = tmr_end - tmr_start; TEST_ASSERT(diff >= 1); TEST_ASSERT(diff < 50); out: return err; } int test_tmr_jiffies_usec(void) { uint64_t tmr_start, diff; int i; int err = 0; tmr_start = tmr_jiffies_usec(); diff = 0; for (i = 0; i < 100000 && !diff; i++) diff = tmr_jiffies_usec() - tmr_start; TEST_ASSERT(diff >= 1); TEST_ASSERT(diff < 1000); out: return err; } ================================================ FILE: test/trace.c ================================================ /** * @file trace.c Trace testcode */ #ifdef HAVE_UNISTD_H #include #endif #include #include "test.h" #define DEBUG_MODULE "test_trace" #define DEBUG_LEVEL 5 #include static void test_loop(int count) { int i; for (i=0; i < count; i++) { RE_TRACE_INSTANT_I("test", "Instant", i); } } int test_trace(void) { int err; if (test_mode == TEST_THREAD) return ESKIPPED; err = re_trace_init("test_trace.json"); TEST_ERR(err); RE_TRACE_PROCESS_NAME("retest"); RE_TRACE_THREAD_NAME("test_trace"); RE_TRACE_BEGIN("test", "Test Loop Start"); test_loop(100); RE_TRACE_BEGIN("test", "Flush"); err = re_trace_flush(); TEST_ERR(err); RE_TRACE_END("test", "Flush"); test_loop(25); RE_TRACE_BEGIN_FUNC(); err = re_trace_flush(); TEST_ERR(err); RE_TRACE_END_FUNC(); RE_TRACE_END("test", "Test Loop End"); err = re_trace_close(); TEST_ERR(err); /* Test TRACE after close - should do nothing */ RE_TRACE_BEGIN("test", "test after close"); #ifdef WIN32 (void)_unlink("test_trace.json"); #else (void)unlink("test_trace.json"); #endif out: if (err) re_trace_close(); return err; } ================================================ FILE: test/trice.c ================================================ /** * @file trice.c Trickle-ICE Testcode * * Copyright (C) 2010 - 2022 Alfred E. Heggestad */ #include #include #include "test.h" #define DEBUG_MODULE "test_trice" #define DEBUG_LEVEL 5 #include #define DEBUG 0 #define COMPID 1 struct fixture { struct trice *icem; struct sa laddr; bool controlling; char lufrag[8]; char lpwd[24]; char rufrag[8]; char rpwd[24]; struct trice *icem2; /* result: */ int err; unsigned n_expected_estabh; bool cancel_on_both; /* counters: */ unsigned n_estabh; unsigned n_failh; unsigned n_estabh2; unsigned n_failh2; /* NAT */ struct nat *nat; struct nat *nat2; }; /* * Helper macros */ #define FIXTURE_INIT \ struct fixture _f, *f = &_f; \ int err = fixture_init(f); \ if (err) \ goto out; \ /* todo: 'addr' used as 'base_addr' (hack) */ #define ADD_LOCAL_SRFLX_CANDIDATE(proto, prio, addr) \ \ do { \ struct ice_lcand *_lcand; \ \ err = trice_lcand_add(&_lcand, f->icem, \ COMPID, (proto), \ (prio), (addr), (addr), \ ICE_CAND_TYPE_SRFLX, (addr), \ 0, NULL, 0); \ if (err) goto out; \ TEST_ASSERT(_lcand != NULL); \ \ } while (0); #define add_local_udp_candidate_use(addr) \ \ do { \ struct ice_lcand *_lcand; \ uint32_t _prio; \ \ _prio = ice_cand_calc_prio(ICE_CAND_TYPE_HOST, 0, 1); \ \ err = trice_lcand_add(&_lcand, f->icem, 1, \ IPPROTO_UDP, _prio, \ addr, NULL, \ ICE_CAND_TYPE_HOST, NULL, \ 0, NULL, 0); \ if (err) goto out; \ TEST_ASSERT(_lcand != NULL); \ \ } while (0); #define add_local_tcp_candidate_use(addr, tcptype) \ \ do { \ struct ice_lcand *_lcand; \ uint32_t _prio; \ \ _prio = ice_cand_calc_prio(ICE_CAND_TYPE_HOST, 0, 1); \ \ err = trice_lcand_add(&_lcand, f->icem, 1, \ IPPROTO_TCP, _prio, \ addr, NULL, \ ICE_CAND_TYPE_HOST, NULL, \ tcptype, NULL, 0); \ if (err) goto out; \ TEST_ASSERT(_lcand != NULL); \ \ } while (0); #define add_local_tcp_candidate_use2(addr, tcptype) \ \ do { \ struct ice_lcand *_lcand; \ uint32_t _prio; \ \ _prio = ice_cand_calc_prio(ICE_CAND_TYPE_HOST, 0, 1); \ \ err = trice_lcand_add(&_lcand, f->icem2, 1, \ IPPROTO_TCP, _prio, \ addr, NULL, \ ICE_CAND_TYPE_HOST, NULL, \ tcptype, NULL, 0); \ if (err) goto out; \ TEST_ASSERT(_lcand != NULL); \ \ } while (0); #define ADD_REMOTE_HOST_CANDIDATE(addr) \ \ do { \ uint32_t _prio; \ \ _prio = ice_cand_calc_prio(ICE_CAND_TYPE_HOST, 0, 1); \ \ err = trice_rcand_add(NULL, f->icem, \ 1, "FND", \ IPPROTO_UDP, \ _prio, \ addr, \ ICE_CAND_TYPE_HOST, 0); \ if (err) goto out; \ \ } while (0); #define CHECKLIST_START(fix) \ err = trice_checklist_start((fix)->icem, NULL, 1, \ ice_estab_handler, \ ice_failed_handler, (fix)); \ TEST_ERR(err); \ static void fixture_abort(struct fixture *f, int err); static bool verify_sorted(const struct list *pairl) { struct le *le; uint64_t pprio = 0; if (!pairl) return false; for (le = list_head(pairl); le; le = le->next) { struct ice_candpair *pair = le->data; if (!pprio) { pprio = pair->pprio; continue; } if (pair->pprio > pprio) { DEBUG_WARNING("unsorted list: %llu > %llu\n", pair->pprio, pprio); return false; } } return true; } static bool are_both_established(const struct fixture *f) { if (!f) return false; return f->n_estabh > 0 && f->n_estabh2 > 0; } static void ice_estab_handler(struct ice_candpair *pair, const struct stun_msg *msg, void *arg) { struct fixture *f = arg; int err = 0; ++f->n_estabh; /* TODO: save candidate-pairs, and compare in the test */ TEST_ASSERT(msg != NULL); TEST_ASSERT(pair != NULL); TEST_ASSERT(pair->lcand != NULL); TEST_ASSERT(pair->rcand != NULL); TEST_ASSERT(pair->valid); TEST_EQUALS(ICE_CANDPAIR_SUCCEEDED, pair->state); TEST_ERR(pair->err); TEST_EQUALS(0, pair->scode); TEST_ASSERT((ICE_CAND_TYPE_HOST == pair->rcand->attr.type) || (ICE_CAND_TYPE_PRFLX == pair->rcand->attr.type)); /* exit criteria */ if (f->n_expected_estabh && f->n_estabh >= f->n_expected_estabh) { fixture_abort(f, 0); } if (f->cancel_on_both && are_both_established(f)) { fixture_abort(f, 0); } out: if (err) fixture_abort(f, err); } static void ice_failed_handler(int err, uint16_t scode, struct ice_candpair *pair, void *arg) { struct fixture *f = arg; (void)err; (void)scode; (void)pair; ++f->n_failh; if (trice_checklist_iscompleted(f->icem)) { re_cancel(); } } static void ice_estab_handler2(struct ice_candpair *pair, const struct stun_msg *msg, void *arg) { struct fixture *f = arg; (void)pair; (void)msg; ++f->n_estabh2; if (f->cancel_on_both && are_both_established(f)) { fixture_abort(f, 0); } } static void ice_failed_handler2(int err, uint16_t scode, struct ice_candpair *pair, void *arg) { struct fixture *f = arg; (void)err; (void)scode; (void)pair; re_printf(" ~ ice2 closed (%m)\n", err); ++f->n_failh2; #if 0 re_cancel(); #endif } static int fixture_init(struct fixture *f) { const struct trice_conf conf = { .debug = DEBUG }; int err; if (!f) return EINVAL; memset(f, 0, sizeof(*f)); f->controlling = true; rand_str(f->lufrag, sizeof(f->lufrag)); rand_str(f->lpwd, sizeof(f->lpwd)); rand_str(f->rufrag, sizeof(f->rufrag)); rand_str(f->rpwd, sizeof(f->rpwd)); err = trice_alloc(&f->icem, &conf, f->controlling ? ICE_ROLE_CONTROLLING : ICE_ROLE_CONTROLLED, f->lufrag, f->lpwd); TEST_ERR(err); TEST_ASSERT(f->icem != NULL); err = trice_set_remote_ufrag(f->icem, f->rufrag); TEST_ERR(err); err = trice_set_remote_pwd(f->icem, f->rpwd); TEST_ERR(err); err = sa_set_str(&f->laddr, "127.0.0.1", 0); TEST_ERR(err); out: return err; } static void fixture_close(struct fixture *f) { if (!f) return; f->nat = mem_deref(f->nat); f->nat2 = mem_deref(f->nat2); f->icem2 = mem_deref(f->icem2); f->icem = mem_deref(f->icem); } static void fixture_abort(struct fixture *f, int err) { f->err = err; re_cancel(); } /* ... TEST CASES ... */ static int candidate_local_udp(void) { struct ice_lcand *lcand; FIXTURE_INIT; err = trice_lcand_add(&lcand, f->icem, 1, IPPROTO_UDP, 1234, &f->laddr, NULL, ICE_CAND_TYPE_HOST, NULL, 0, NULL, 0); if (err) goto out; /* verify the new local candidate */ TEST_ASSERT(lcand != NULL); TEST_ASSERT(str_isset(lcand->attr.foundation)); TEST_EQUALS(1, lcand->attr.compid); TEST_EQUALS(IPPROTO_UDP, lcand->attr.proto); TEST_EQUALS(1234, lcand->attr.prio); TEST_SACMP(&f->laddr, &lcand->attr.addr, SA_ADDR); TEST_ASSERT(sa_isset(&lcand->attr.addr, SA_PORT)); TEST_EQUALS(ICE_CAND_TYPE_HOST, lcand->attr.type); TEST_ASSERT(list_contains(trice_lcandl(f->icem), &lcand->le)); TEST_ASSERT(lcand->icem == f->icem); TEST_ASSERT(lcand->us != NULL); TEST_ASSERT(lcand->uh != NULL); TEST_ASSERT(lcand->ts == NULL); out: fixture_close(f); return err; } static int candidate_local_tcp(enum ice_tcptype tcptype) { struct ice_lcand *lcand; FIXTURE_INIT; err = trice_lcand_add(&lcand, f->icem, 1, IPPROTO_TCP, 1234, &f->laddr, NULL, ICE_CAND_TYPE_HOST, NULL, tcptype, NULL, 0); if (err) goto out; /* verify the new local candidate */ TEST_ASSERT(lcand != NULL); TEST_ASSERT(str_isset(lcand->attr.foundation)); TEST_EQUALS(1, lcand->attr.compid); TEST_EQUALS(IPPROTO_TCP, lcand->attr.proto); TEST_EQUALS(1234, lcand->attr.prio); TEST_SACMP(&f->laddr, &lcand->attr.addr, SA_ADDR); if (tcptype == ICE_TCP_ACTIVE) { TEST_ASSERT(!sa_isset(&lcand->attr.addr, SA_PORT)); } else { TEST_ASSERT(sa_isset(&lcand->attr.addr, SA_PORT)); } TEST_EQUALS(ICE_CAND_TYPE_HOST, lcand->attr.type); TEST_ASSERT(list_contains(trice_lcandl(f->icem), &lcand->le)); TEST_ASSERT(lcand->icem == f->icem); TEST_ASSERT(lcand->us == NULL); TEST_ASSERT(lcand->uh == NULL); if (tcptype == ICE_TCP_ACTIVE) { TEST_ASSERT(lcand->ts == NULL); } else { TEST_ASSERT(lcand->ts != NULL); } out: fixture_close(f); return err; } static int candidate_add_5_local(int proto) { int i; FIXTURE_INIT; for (i=0; i<5; i++) { struct sa addr; char buf[64]; re_snprintf(buf, sizeof(buf), "10.0.0.%u", i+1); sa_set_str(&addr, buf, 1000+i); ADD_LOCAL_SRFLX_CANDIDATE(proto, 0, &addr) } TEST_EQUALS(5, list_count(trice_lcandl(f->icem))); TEST_EQUALS(0, list_count(trice_rcandl(f->icem))); TEST_EQUALS(0, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); TEST_EQUALS(0, f->n_estabh); out: fixture_close(f); return err; } static int candidate_find_local_candidate(void) { struct sa addr; struct ice_lcand *cand; FIXTURE_INIT; sa_set_str(&addr, "1.2.3.4", 1234); /* should not exist now */ cand = trice_lcand_find(f->icem, -1, 1, IPPROTO_UDP, &addr); TEST_ASSERT(cand == NULL); ADD_LOCAL_SRFLX_CANDIDATE(IPPROTO_UDP, 0x7e0000ff, &addr); cand = trice_lcand_find(f->icem, -1, 1, IPPROTO_UDP, &addr); TEST_ASSERT(cand != NULL); TEST_EQUALS(ICE_CAND_TYPE_SRFLX, cand->attr.type); TEST_EQUALS(0x7e0000ff, cand->attr.prio); TEST_ASSERT(str_isset(cand->attr.foundation)); TEST_EQUALS(1, cand->attr.compid); TEST_SACMP(&addr, &cand->attr.addr, SA_ALL); TEST_EQUALS(IPPROTO_UDP, cand->attr.proto); out: fixture_close(f); return err; } static int candidate_add_5_remote_candidates(void) { int i; FIXTURE_INIT; for (i=0; i<5; i++) { struct sa addr; char buf[64]; re_snprintf(buf, sizeof(buf), "10.0.0.%u", i+1); sa_set_str(&addr, buf, 1234); ADD_REMOTE_HOST_CANDIDATE(&addr); } TEST_EQUALS(0, list_count(trice_lcandl(f->icem))); TEST_EQUALS(5, list_count(trice_rcandl(f->icem))); TEST_EQUALS(0, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); TEST_EQUALS(0, f->n_estabh); out: fixture_close(f); return err; } static int candidate_find_remote_candidate(void) { struct sa addr; struct ice_rcand *cand; FIXTURE_INIT; sa_set_str(&addr, "1.2.3.4", 1234); /* should not exist now */ cand = trice_rcand_find(f->icem, 1, IPPROTO_UDP, &addr); TEST_ASSERT(cand == NULL); ADD_REMOTE_HOST_CANDIDATE(&addr); cand = trice_rcand_find(f->icem, 1, IPPROTO_UDP, &addr); TEST_ASSERT(cand != NULL); TEST_EQUALS(ICE_CAND_TYPE_HOST, cand->attr.type); TEST_EQUALS(0x7e0000ff, cand->attr.prio); TEST_ASSERT(str_isset(cand->attr.foundation)); TEST_EQUALS(1, cand->attr.compid); TEST_SACMP(&addr, &cand->attr.addr, SA_ALL); TEST_EQUALS(IPPROTO_UDP, cand->attr.proto); out: fixture_close(f); return err; } static int candidate_add_2_local_and_2_remote_candidates(void) { struct sa laddr, raddr; int i; FIXTURE_INIT; sa_set_str(&laddr, "10.0.0.1", 0); sa_set_str(&raddr, "10.0.0.2", 0); for (i=0; i<2; i++) { sa_set_port(&laddr, 10000+i); sa_set_port(&raddr, 20000+i); ADD_LOCAL_SRFLX_CANDIDATE(IPPROTO_UDP, 1234, &laddr) ADD_REMOTE_HOST_CANDIDATE(&raddr); } TEST_EQUALS(2, list_count(trice_lcandl(f->icem))); TEST_EQUALS(2, list_count(trice_rcandl(f->icem))); TEST_EQUALS(4, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); TEST_EQUALS(0, f->n_estabh); TEST_ASSERT(verify_sorted(trice_checkl(f->icem))); out: fixture_close(f); return err; } static int candidate_2_local_duplicates(int proto, uint32_t prio1, uint32_t prio2) { struct sa laddr; struct ice_lcand *lcand; FIXTURE_INIT; sa_set_str(&laddr, "10.0.0.3", 1002); TEST_EQUALS(0, list_count(trice_lcandl(f->icem))); /* add one with Low Priority */ ADD_LOCAL_SRFLX_CANDIDATE(proto, prio1, &laddr); TEST_EQUALS(1, list_count(trice_lcandl(f->icem))); /* add one with High Priority */ ADD_LOCAL_SRFLX_CANDIDATE(proto, prio2, &laddr); TEST_EQUALS(1, list_count(trice_lcandl(f->icem))); /* verify that local candidate has the HIGH prio */ lcand = trice_lcand_find(f->icem, -1, 1, proto, &laddr); TEST_ASSERT(lcand != NULL); TEST_EQUALS(max(prio1, prio2), lcand->attr.prio); out: fixture_close(f); return err; } static int candidate_local_host_and_srflx_with_base(void) { struct fixture f; struct sa laddr, srflx; struct ice_lcand *lcand; int err = 0; err = fixture_init(&f); if (err) goto out; sa_set_str(&laddr, "127.0.0.1", 0); sa_set_str(&srflx, "46.45.1.1", 1002); err = trice_lcand_add(&lcand, f.icem, COMPID, IPPROTO_UDP, 1234, &laddr, NULL, ICE_CAND_TYPE_HOST, NULL, 0, NULL, 0); TEST_ERR(err); TEST_ASSERT(lcand != NULL); laddr = lcand->attr.addr; err = trice_lcand_add(NULL, f.icem, COMPID, IPPROTO_UDP, 1234, &srflx, &laddr, ICE_CAND_TYPE_SRFLX, &laddr, 0, NULL, 0); TEST_ERR(err); TEST_EQUALS(2, list_count(trice_lcandl(f.icem))); /* verify */ lcand = trice_lcand_find(f.icem, ICE_CAND_TYPE_HOST, COMPID, IPPROTO_UDP, &lcand->attr.addr); TEST_ASSERT(lcand != NULL); TEST_EQUALS(ICE_CAND_TYPE_HOST, lcand->attr.type); TEST_SACMP(&laddr, &lcand->attr.addr, SA_ALL); lcand = trice_lcand_find(f.icem, ICE_CAND_TYPE_SRFLX, COMPID, IPPROTO_UDP, &srflx); TEST_ASSERT(lcand != NULL); TEST_EQUALS(ICE_CAND_TYPE_SRFLX, lcand->attr.type); TEST_SACMP(&srflx, &lcand->attr.addr, SA_ALL); TEST_SACMP(&laddr, &lcand->base_addr, SA_ALL); out: fixture_close(&f); return err; } /* 4.1.3. Eliminating Redundant Candidates */ static int candidate_verify_redundant_with_public_ip(void) { struct sa laddr, raddr; struct ice_lcand *lcand; uint32_t prio; FIXTURE_INIT; sa_set_str(&laddr, "127.0.0.1", 0); sa_set_str(&raddr, "10.0.0.4", 1002); prio = ice_cand_calc_prio(ICE_CAND_TYPE_HOST, 0, COMPID); err = trice_lcand_add(&lcand, f->icem, COMPID, IPPROTO_UDP, prio, &laddr, NULL, ICE_CAND_TYPE_HOST, NULL, 0, NULL, 0); TEST_ERR(err); TEST_ASSERT(lcand != NULL); laddr = lcand->attr.addr; prio = ice_cand_calc_prio(ICE_CAND_TYPE_SRFLX, 0, COMPID); err = trice_lcand_add(NULL, f->icem, COMPID, IPPROTO_UDP, prio, &lcand->attr.addr, &lcand->attr.addr, ICE_CAND_TYPE_SRFLX, &lcand->attr.addr, 0, NULL, 0); TEST_ERR(err); ADD_REMOTE_HOST_CANDIDATE(&raddr); TEST_EQUALS(1, list_count(trice_lcandl(f->icem))); TEST_EQUALS(1, list_count(trice_rcandl(f->icem))); TEST_EQUALS(1, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); /* verify the local candidate */ lcand = list_ledata(list_head(trice_lcandl(f->icem))); TEST_EQUALS(ICE_CAND_TYPE_HOST, lcand->attr.type); TEST_SACMP(&laddr, &lcand->attr.addr, SA_ALL); out: fixture_close(f); return err; } /* ... testcases for candidate pairs ... */ static int candpair_add_1_local_and_1_remote_candidate_and_create_pair(void) { struct sa addr; FIXTURE_INIT; sa_set_str(&addr, "10.0.0.5", 1000); ADD_LOCAL_SRFLX_CANDIDATE(IPPROTO_UDP, 1234, &addr); ADD_REMOTE_HOST_CANDIDATE(&addr); /* the checklist is formatted automatically */ TEST_EQUALS(1, list_count(trice_lcandl(f->icem))); TEST_EQUALS(1, list_count(trice_rcandl(f->icem))); TEST_EQUALS(1, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); TEST_EQUALS(0, f->n_estabh); out: fixture_close(f); return err; } static int candpair_combine_ipv4_ipv6_udp_tcp(void) { struct sa addr, addr6; FIXTURE_INIT; sa_set_str(&addr, "10.0.0.6", 1000); sa_set_str(&addr6, "::1", 6000); err |= trice_lcand_add(0, f->icem, 1, IPPROTO_UDP, 1234, &addr, &addr, ICE_CAND_TYPE_SRFLX, &addr, 0, NULL, 0); err |= trice_lcand_add(0, f->icem, 1, IPPROTO_TCP, 1234, &addr, &addr, ICE_CAND_TYPE_SRFLX, &addr, ICE_TCP_ACTIVE, NULL, 0); err |= trice_lcand_add(0, f->icem, 1, IPPROTO_UDP, 1234, &addr6, &addr6, ICE_CAND_TYPE_SRFLX, &addr6, 0, NULL, 0); err |= trice_lcand_add(0, f->icem, 1, IPPROTO_TCP, 1234, &addr6, &addr6, ICE_CAND_TYPE_SRFLX, &addr6, ICE_TCP_ACTIVE, NULL, 0); TEST_ERR(err); ADD_REMOTE_HOST_CANDIDATE(&addr); err |= trice_rcand_add(NULL, f->icem, 1, "FND", IPPROTO_TCP, 1234, &addr, ICE_CAND_TYPE_HOST, ICE_TCP_PASSIVE); if (err) goto out; ADD_REMOTE_HOST_CANDIDATE(&addr6); err |= trice_rcand_add(NULL, f->icem, 1, "FND", IPPROTO_TCP, 1234, &addr6, ICE_CAND_TYPE_HOST, ICE_TCP_PASSIVE); if (err) goto out; TEST_EQUALS(4, list_count(trice_lcandl(f->icem))); TEST_EQUALS(4, list_count(trice_rcandl(f->icem))); TEST_EQUALS(4, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); TEST_EQUALS(0, f->n_estabh); TEST_EQUALS(0, f->n_failh); out: fixture_close(f); return err; } static int candpair_add_many_verify_sorted(void) { struct fixture f; struct sa laddr, raddr; int i, err = 0; err = fixture_init(&f); if (err) goto out; sa_set_str(&laddr, "10.0.0.7", 0); sa_set_str(&raddr, "10.0.0.8", 0); for (i=0; i<4; i++) { uint8_t compid = 1 + i%2; sa_set_port(&laddr, 10000+i); sa_set_port(&raddr, 20000+i); err = trice_lcand_add(0, f.icem, compid, IPPROTO_UDP, i*1000, &laddr, &laddr, ICE_CAND_TYPE_SRFLX, &laddr, 0, NULL, 0); TEST_ERR(err); err = trice_rcand_add(0, f.icem, compid, "FND", IPPROTO_UDP, i*2000, &raddr, ICE_CAND_TYPE_HOST, 0); TEST_ERR(err); } TEST_EQUALS(4, list_count(trice_lcandl(f.icem))); TEST_EQUALS(4, list_count(trice_rcandl(f.icem))); TEST_EQUALS(8, list_count(trice_checkl(f.icem))); TEST_EQUALS(0, list_count(trice_validl(f.icem))); TEST_ASSERT(verify_sorted(trice_checkl(f.icem))); out: fixture_close(&f); return err; } static int candpair_test_pruning(void) { struct sa srflx_addr, remote_addr; struct ice_lcand *lcand; uint32_t prio; FIXTURE_INIT; err |= sa_set_str(&srflx_addr, "95.1.2.3", 50000); err |= sa_set_str(&remote_addr, "10.0.0.9", 10000); TEST_ERR(err); prio = ice_cand_calc_prio(ICE_CAND_TYPE_SRFLX, 0, COMPID); add_local_udp_candidate_use(&f->laddr); lcand = trice_lcand_find(f->icem, -1, COMPID, IPPROTO_UDP, NULL); TEST_ASSERT(lcand != NULL); err = trice_lcand_add(&lcand, f->icem, COMPID, IPPROTO_UDP, prio, &srflx_addr, &lcand->attr.addr, ICE_CAND_TYPE_SRFLX, &lcand->attr.addr, 0, NULL, 0); TEST_ERR(err); TEST_ASSERT(lcand != NULL); ADD_REMOTE_HOST_CANDIDATE(&remote_addr); /* verify that SRFLX candpair was pruned */ TEST_EQUALS(2, list_count(trice_lcandl(f->icem))); TEST_EQUALS(1, list_count(trice_rcandl(f->icem))); TEST_EQUALS(1, list_count(trice_checkl(f->icem))); TEST_EQUALS(0, list_count(trice_validl(f->icem))); out: fixture_close(f); return err; } static int checklist_verify_states(void) { struct fixture f; int err = 0; err = fixture_init(&f); if (err) goto out; TEST_EQUALS(false, trice_checklist_isrunning(f.icem)); /* Start -- Running */ CHECKLIST_START(&f); TEST_EQUALS(true, trice_checklist_isrunning(f.icem)); /* Stop */ trice_checklist_stop(f.icem); TEST_EQUALS(false, trice_checklist_isrunning(f.icem)); out: fixture_close(&f); return err; } static int exchange_candidates(struct trice *dst, const struct trice *src) { struct le *le; int err = 0; for (le = list_head(trice_lcandl(src)); le; le = le->next) { struct ice_cand_attr *cand = le->data; err = trice_rcand_add(NULL, dst, cand->compid, cand->foundation, cand->proto, cand->prio, &cand->addr, cand->type, cand->tcptype); if (err) return err; } return err; } static int checklist_tcp_simple(enum ice_tcptype tcptype) { struct le *le; char *buf = NULL; FIXTURE_INIT; #if 0 trice_conf(f.icem)->debug = true; trice_conf(f.icem)->trace = true; #endif err = trice_alloc(&f->icem2, NULL, !f->controlling ? ICE_ROLE_CONTROLLING : ICE_ROLE_CONTROLLED, f->rufrag, f->rpwd); TEST_ERR(err); err |= trice_set_remote_ufrag(f->icem2, f->lufrag); err |= trice_set_remote_pwd(f->icem2, f->lpwd); TEST_ERR(err); add_local_tcp_candidate_use(&f->laddr, tcptype); add_local_tcp_candidate_use2(&f->laddr, ice_tcptype_reverse(tcptype)); err = exchange_candidates(f->icem, f->icem2); err |= exchange_candidates(f->icem2, f->icem); TEST_ERR(err); CHECKLIST_START(f); err = trice_checklist_start(f->icem2, NULL, 1, ice_estab_handler2, ice_failed_handler2, f); TEST_ERR(err); f->n_expected_estabh = 1; err = re_main_timeout(1000); if (err) goto out; TEST_ERR(f->err); err = re_sdprintf(&buf, "%H", trice_debug, f->icem); TEST_ERR(err); ASSERT_TRUE(str_isset(buf)); TEST_ASSERT(f->n_estabh > 0); TEST_ASSERT(list_count(trice_lcandl(f->icem)) >= 1); TEST_ASSERT(list_count(trice_rcandl(f->icem)) >= 1); TEST_EQUALS(1, list_count(trice_validl(f->icem))); for (le = list_head(trice_validl(f->icem)); le; le = le->next) { struct ice_candpair *pair = le->data; struct ice_lcand *lcand = pair->lcand; TEST_ASSERT(pair->valid); TEST_EQUALS(ICE_CANDPAIR_SUCCEEDED, pair->state); TEST_EQUALS(0, pair->err); TEST_EQUALS(0, pair->scode); TEST_EQUALS(IPPROTO_TCP, lcand->attr.proto); TEST_EQUALS(tcptype, lcand->attr.tcptype); TEST_EQUALS(IPPROTO_TCP, pair->rcand->attr.proto); TEST_EQUALS(ice_tcptype_reverse(tcptype), pair->rcand->attr.tcptype); } out: fixture_close(f); mem_deref(buf); return err; } int test_trice_cand(void) { int err = 0; err |= candidate_local_udp(); err |= candidate_local_tcp(ICE_TCP_ACTIVE); err |= candidate_local_tcp(ICE_TCP_PASSIVE); err |= candidate_local_tcp(ICE_TCP_SO); err |= candidate_add_5_local(IPPROTO_UDP); err |= candidate_add_5_local(IPPROTO_TCP); err |= candidate_find_local_candidate(); err |= candidate_add_5_remote_candidates(); err |= candidate_find_remote_candidate(); err |= candidate_add_2_local_and_2_remote_candidates(); err |= candidate_2_local_duplicates(IPPROTO_UDP, 100, 200); err |= candidate_2_local_duplicates(IPPROTO_UDP, 200, 100); #if 0 err |= candidate_2_local_duplicates(IPPROTO_TCP, 100, 200); #endif err |= candidate_local_host_and_srflx_with_base(); err |= candidate_verify_redundant_with_public_ip(); return err; } int test_trice_candpair(void) { int err = 0; err |= candpair_add_1_local_and_1_remote_candidate_and_create_pair(); err |= candpair_combine_ipv4_ipv6_udp_tcp(); err |= candpair_add_many_verify_sorted(); err |= candpair_test_pruning(); return err; } int test_trice_checklist(void) { int err = 0; err |= checklist_verify_states(); TEST_ERR(err); err |= checklist_tcp_simple(ICE_TCP_ACTIVE); TEST_ERR(err); err |= checklist_tcp_simple(ICE_TCP_PASSIVE); TEST_ERR(err); out: return err; } static int checklist_udp_loop(bool fw_a, bool fw_b) { struct ice_lcand *lcand, *lcand2; struct sa laddr2; uint32_t prio; char *buf = NULL; FIXTURE_INIT; #if 0 trice_conf(f->icem)->debug = true; trice_conf(f->icem)->trace = true; #endif sa_set_str(&laddr2, "127.0.0.1", 0); err = trice_alloc(&f->icem2, NULL, !f->controlling ? ICE_ROLE_CONTROLLING : ICE_ROLE_CONTROLLED, f->rufrag, f->rpwd); TEST_ERR(err); err |= trice_set_remote_ufrag(f->icem2, f->lufrag); err |= trice_set_remote_pwd(f->icem2, f->lpwd); TEST_ERR(err); /* add local HOST candidates */ add_local_udp_candidate_use(&f->laddr); lcand = trice_lcand_find2(f->icem, ICE_CAND_TYPE_HOST, AF_INET); TEST_ASSERT(lcand != NULL); /* install NAT/Firewall */ if (fw_a) { err = nat_alloc(&f->nat, NAT_FIREWALL, lcand->us, NULL); if (err) { re_printf("nat failed\n"); goto out; } } prio = ice_cand_calc_prio(ICE_CAND_TYPE_HOST, 0, COMPID); err = trice_lcand_add(&lcand2, f->icem2, COMPID, IPPROTO_UDP, prio, &laddr2, NULL, ICE_CAND_TYPE_HOST, NULL, 0, NULL, 0); if (err) goto out; /* install NAT/Firewall */ if (fw_b) { err = nat_alloc(&f->nat2, NAT_FIREWALL, lcand2->us, NULL); if (err) { re_printf("nat failed\n"); goto out; } } err = exchange_candidates(f->icem, f->icem2); err |= exchange_candidates(f->icem2, f->icem); TEST_ERR(err); f->cancel_on_both = true; CHECKLIST_START(f); /* NOTE: slow checklist */ err = trice_checklist_start(f->icem2, NULL, 10, ice_estab_handler2, ice_failed_handler2, f); TEST_ERR(err); err = re_main_timeout(2000); if (err) goto out; TEST_ERR(f->err); err = re_sdprintf(&buf, "%H", trice_debug, f->icem); TEST_ERR(err); ASSERT_TRUE(str_isset(buf)); TEST_ASSERT(f->n_estabh > 0); TEST_ASSERT(list_count(trice_lcandl(f->icem)) >= 1); TEST_ASSERT(list_count(trice_rcandl(f->icem)) >= 1); TEST_EQUALS(1, list_count(trice_validl(f->icem))); if (fw_a) { TEST_ASSERT(f->nat->bindingc >= 1); } out: fixture_close(f); mem_deref(buf); return err; } int test_trice_loop(void) { int err = 0; err = checklist_udp_loop(0, 0); TEST_ERR(err); err = checklist_udp_loop(0, 1); TEST_ERR(err); err = checklist_udp_loop(1, 0); TEST_ERR(err); err = checklist_udp_loop(1, 1); TEST_ERR(err); out: return err; } ================================================ FILE: test/turn.c ================================================ /** * @file turn.c TURN testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include #include "test.h" #define DEBUG_MODULE "test_turn" #define DEBUG_LEVEL 5 #include enum rx_state { RX_NULL = 0, RX_DETACH, RX_ATTACH, RX_READY, RX_CLOSE }; struct turntest { struct turnc *turnc; struct turnserver *turnsrv; struct udp_sock *us_cli; struct udp_sock *us_peer; struct tcp_conn *tc; struct sa cli; struct sa peer; struct mbuf *mb; struct tmr tmr; uint32_t lifetime; enum rx_state rx_state; thrd_t thr; mtx_t *mtx; int proto; int err; bool use_chan; size_t n_alloc_resp; size_t n_perm_resp; size_t n_chan_resp; size_t n_peer_recv; }; static const char *test_payload = "guten tag Herr TURN server"; static void destructor(void *arg) { struct turntest *tt = arg; /* NOTE: must be derefed before udp socket */ mem_deref(tt->turnc); mem_deref(tt->us_cli); mem_deref(tt->us_peer); mem_deref(tt->tc); mem_deref(tt->mb); mem_deref(tt->turnsrv); mem_deref(tt->mtx); } static bool is_complete(struct turntest *tt) { return (tt->use_chan ? tt->n_chan_resp >= 1 : tt->n_perm_resp >= 1) && tt->n_peer_recv >= 2; } static void complete_test(struct turntest *tt, int err) { tt->err = err; re_cancel(); } /* send a UDP payload via TURN-Server to a UDP-peer */ static int send_payload(struct turntest *tt, size_t offset, const struct sa *dst, const char *str) { struct mbuf *mb = mbuf_alloc(offset + str_len(str)); int err; if (!mb) return ENOMEM; mb->pos = offset; err = mbuf_write_str(mb, str); if (err) goto out; mb->pos = offset; switch (tt->proto) { case IPPROTO_UDP: err = turnc_send(tt->turnc, dst, mb); break; case IPPROTO_TCP: err = turnc_send(tt->turnc, dst, mb); break; } out: mem_deref(mb); return err; } static void turnc_perm_handler(void *arg) { struct turntest *tt = arg; ++tt->n_perm_resp; /* Headroom for IPv4/IPv6 STUN headers */ const size_t offset = 48; int err = send_payload(tt, offset, &tt->peer, test_payload); if (err) { DEBUG_WARNING("failed to send payload (%m)\n", err); complete_test(tt, err); } } static void turnc_chan_handler(void *arg) { struct turntest *tt = arg; ++tt->n_chan_resp; int err = send_payload(tt, 4, &tt->peer, test_payload); if (err) { DEBUG_WARNING("failed to send payload (%m)\n", err); } if (err) complete_test(tt, err); } static void turnc_handler(int err, uint16_t scode, const char *reason, const struct sa *relay_addr, const struct sa *mapped_addr, const struct stun_msg *msg, void *arg) { struct turntest *tt = arg; (void)reason; (void)msg; ++tt->n_alloc_resp; if (err) { complete_test(tt, err); return; } if (scode) { complete_test(tt, EPROTO); return; } TEST_SACMP(&tt->turnsrv->relay, relay_addr, SA_ALL); TEST_SACMP(&tt->cli, mapped_addr, SA_ALL); if (tt->use_chan) { err = turnc_add_chan(tt->turnc, &tt->peer, turnc_chan_handler, tt); if (err) goto out; err = turnc_add_chan(tt->turnc, &tt->peer, turnc_chan_handler, tt); if (err) goto out; } else { turnserver_force_error(tt->turnsrv, 401); /* Permission is needed for sending data */ err = turnc_add_perm(tt->turnc, &tt->peer, turnc_perm_handler, tt); if (err) goto out; err = turnc_add_perm(tt->turnc, &tt->peer, turnc_perm_handler, tt); if (err) goto out; } out: if (err) complete_test(tt, err); } static void peer_udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct turntest *tt = arg; int err = 0; (void)src; ++tt->n_peer_recv; err = udp_send(tt->us_peer, src, mb); TEST_ERR(err); TEST_MEMCMP(test_payload, strlen(test_payload), mbuf_buf(mb), mbuf_get_left(mb)); mtx_lock(tt->mtx); if (tt->rx_state > RX_NULL) { mtx_unlock(tt->mtx); return; } mtx_unlock(tt->mtx); out: if (err || is_complete(tt)) complete_test(tt, err); } static void cli_udp_recv(const struct sa *src, struct mbuf *mb, void *arg) { struct turntest *tt = arg; (void)src; mtx_lock(tt->mtx); if (tt->rx_state == RX_DETACH) { udp_thread_detach(tt->us_cli); tt->rx_state = RX_ATTACH; } mtx_unlock(tt->mtx); udp_send(tt->us_cli, src, mb); } static void data_handler(struct turntest *tt, struct sa *src, struct mbuf *mb) { /* echo data */ turnc_send(tt->turnc, src, mb); } static void tcp_estab_handler(void *arg) { struct turntest *tt = arg; int err; err = tcp_conn_local_get(tt->tc, &tt->cli); if (err) goto out; err = turnc_alloc(&tt->turnc, NULL, IPPROTO_TCP, tt->tc, 0, &tt->turnsrv->laddr_tcp, "username", "password", tt->lifetime, turnc_handler, tt); if (err) goto out; out: if (err) complete_test(tt, err); } static void tcp_recv_handler(struct mbuf *mb, void *arg) { struct turntest *tl = arg; int err = 0; if (tl->mb) { size_t pos; pos = tl->mb->pos; tl->mb->pos = tl->mb->end; err = mbuf_write_mem(tl->mb, mbuf_buf(mb),mbuf_get_left(mb)); if (err) goto out; tl->mb->pos = pos; } else { tl->mb = mem_ref(mb); } for (;;) { size_t len, pos, end; struct sa src; uint16_t typ; if (mbuf_get_left(tl->mb) < 4) break; typ = ntohs(mbuf_read_u16(tl->mb)); len = ntohs(mbuf_read_u16(tl->mb)); if (typ < 0x4000) len += STUN_HEADER_SIZE; else if (typ < 0x8000) len += 4; else { err = EBADMSG; goto out; } tl->mb->pos -= 4; if (mbuf_get_left(tl->mb) < len) break; pos = tl->mb->pos; end = tl->mb->end; tl->mb->end = pos + len; err = turnc_recv(tl->turnc, &src, tl->mb); if (err) goto out; if (mbuf_get_left(tl->mb)) data_handler(tl, &src, tl->mb); /* 4 byte alignment */ while (len & 0x03) ++len; tl->mb->pos = pos + len; tl->mb->end = end; if (tl->mb->pos >= tl->mb->end) { tl->mb = mem_deref(tl->mb); break; } } out: if (err) complete_test(tl, err); } static void tcp_close_handler(int err, void *arg) { struct turntest *tt = arg; if (err == ECONNRESET) { re_printf("translate ECONNRESET -> ENOMEM\n"); err = ENOMEM; } if (err) complete_test(tt, err); } static int turntest_alloc(struct turntest **ttp, int proto, uint32_t lifetime, const char *addr) { struct turntest *tt; struct sa laddr; int err; tt = mem_zalloc(sizeof(*tt), NULL); if (!tt) return ENOMEM; err = mutex_alloc(&tt->mtx); if (err) goto out; mem_destructor(tt, destructor); tt->proto = proto; tt->lifetime = lifetime; err = sa_set_str(&laddr, addr, 0); if (err) goto out; if (proto == IPPROTO_UDP) { err |= udp_listen(&tt->us_cli, &laddr, cli_udp_recv, tt); if (err) goto out; err = udp_local_get(tt->us_cli, &tt->cli); if (err) goto out; } err = udp_listen(&tt->us_peer, &laddr, peer_udp_recv, tt); if (err) goto out; err = udp_local_get(tt->us_peer, &tt->peer); if (err) goto out; err = turnserver_alloc(&tt->turnsrv, addr); if (err) goto out; switch (proto) { case IPPROTO_UDP: err = turnc_alloc(&tt->turnc, NULL, proto, tt->us_cli, 0, &tt->turnsrv->laddr, "username", "password", lifetime, turnc_handler, tt); break; case IPPROTO_TCP: err = tcp_connect(&tt->tc, &tt->turnsrv->laddr_tcp, tcp_estab_handler, tcp_recv_handler, tcp_close_handler, tt); break; } if (err) goto out; out: if (err) mem_deref(tt); else if (ttp) *ttp = tt; return err; } static int test_turn_param(const char *addr, bool use_chan) { struct turntest *tt; int err; err = turntest_alloc(&tt, IPPROTO_UDP, 600, addr); if (err) return err; tt->use_chan = use_chan; err = re_main_timeout(200); TEST_ERR(err); err = tt->err; TEST_ERR(err); /* verify results after test is complete */ TEST_EQUALS(1, tt->n_alloc_resp); if (use_chan) { TEST_EQUALS(0, tt->n_perm_resp); TEST_EQUALS(1, tt->n_chan_resp); } else { TEST_EQUALS(1, tt->n_perm_resp); TEST_EQUALS(0, tt->n_chan_resp); } TEST_ASSERT(tt->turnsrv->n_allocate >= 1); TEST_EQUALS(2, tt->n_peer_recv); if (use_chan) { TEST_ASSERT(tt->turnsrv->n_chanbind >= 1); TEST_ASSERT(tt->turnsrv->n_raw >= 1); TEST_EQUALS(0, tt->turnsrv->n_send); } else { TEST_EQUALS(0, tt->turnsrv->n_chanbind); TEST_EQUALS(0, tt->turnsrv->n_raw); TEST_EQUALS(2, tt->turnsrv->n_send); } out: mem_deref(tt); return err; } int test_turn(void) { int err = test_turn_param("127.0.0.1", false); TEST_ERR(err); err = test_turn_param("127.0.0.1", true); TEST_ERR(err); if (test_ipv6_supported()) { err = test_turn_param("::1", false); TEST_ERR(err); err = test_turn_param("::1", true); TEST_ERR(err); } out: return err; } static int test_turn_param_tcp(const char *addr, bool use_chan) { struct turntest *tt; int err; err = turntest_alloc(&tt, IPPROTO_TCP, 600, addr); if (err) return err; tt->use_chan = use_chan; err = re_main_timeout(200); TEST_ERR(err); err = tt->err; TEST_ERR(err); /* verify results after test is complete */ TEST_EQUALS(1, tt->n_alloc_resp); if (use_chan) { TEST_EQUALS(0, tt->n_perm_resp); TEST_EQUALS(1, tt->n_chan_resp); } else { TEST_EQUALS(1, tt->n_perm_resp); TEST_EQUALS(0, tt->n_chan_resp); } TEST_ASSERT(tt->turnsrv->n_allocate >= 1); TEST_EQUALS(2, tt->n_peer_recv); if (use_chan) { TEST_ASSERT(tt->turnsrv->n_chanbind >= 1); TEST_ASSERT(tt->turnsrv->n_raw >= 1); TEST_EQUALS(0, tt->turnsrv->n_send); } else { TEST_EQUALS(0, tt->turnsrv->n_chanbind); TEST_EQUALS(0, tt->turnsrv->n_raw); TEST_EQUALS(2, tt->turnsrv->n_send); } out: mem_deref(tt); return err; } int test_turn_tcp(void) { int err; err = test_turn_param_tcp("127.0.0.1", false); TEST_ERR(err); err = test_turn_param_tcp("127.0.0.1", true); TEST_ERR(err); if (test_ipv6_supported()) { err = test_turn_param_tcp("::1", false); TEST_ERR(err); err = test_turn_param_tcp("::1", true); TEST_ERR(err); } out: return err; } static void tmr_handler(void *arg) { struct turntest *tt = arg; mtx_lock(tt->mtx); if (tt->rx_state == RX_CLOSE) re_cancel(); if (tt->rx_state == RX_ATTACH) { udp_thread_attach(tt->us_cli); tt->rx_state = RX_READY; } mtx_unlock(tt->mtx); tmr_start(&tt->tmr, 0, tmr_handler, tt); } static int turn_thread(void *arg) { struct turntest *tt = arg; int err; err = re_thread_init(); if (err) return err; tmr_init(&tt->tmr); tmr_start(&tt->tmr, 0, tmr_handler, tt); err = re_main(NULL); TEST_ERR(err); tmr_cancel(&tt->tmr); out: re_thread_close(); return err; } int test_turn_thread(void) { struct turntest *tt; int err; err = turntest_alloc(&tt, IPPROTO_UDP, 0, "127.0.0.1"); if (err) return err; tt->rx_state = RX_DETACH; err = thread_create_name(&tt->thr, "test_turn_thread", turn_thread, tt); TEST_ERR(err); re_main_timeout(500); mtx_lock(tt->mtx); tt->rx_state = RX_CLOSE; mtx_unlock(tt->mtx); thrd_join(tt->thr, &err); TEST_ERR(err); err = tt->err; TEST_ERR(err); out: mem_deref(tt); return err; } ================================================ FILE: test/types.c ================================================ /** * @file types.c Types Testcode * */ #include #include "test.h" int test_re_assert_se(void) { int err; re_assert(true); re_assert_se(!(err = 0)); return err; } ================================================ FILE: test/udp.c ================================================ /** * @file udp.c UDP testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "udptest" #define DEBUG_LEVEL 5 #include struct udp_test { struct udp_sock *usc; struct udp_sock *uss; struct udp_helper *uh; struct sa cli; struct sa srv; int tindex; int err; }; static const char *data0 = "data from client to server"; static void destructor(void *arg) { struct udp_test *ut = arg; mem_deref(ut->uh); mem_deref(ut->usc); mem_deref(ut->uss); } static int send_data(struct udp_sock *us, const struct sa *peer, const char *data) { struct mbuf *mb = mbuf_alloc(strlen(data) + 1); int err; if (!mb) return ENOMEM; (void)mbuf_write_str(mb, data); mb->pos = 0; err = udp_send(us, peer, mb); mem_deref(mb); return err; } static bool mbuf_compare(const struct mbuf *mb, const char *str) { if (mbuf_get_left(mb) != strlen(str)) return false; if (0 != memcmp(mbuf_buf(mb), str, strlen(str))) return false; return true; } static void udp_recv_client(const struct sa *src, struct mbuf *mb, void *arg) { struct udp_test *ut = arg; switch (ut->tindex++) { case 0: if (!mbuf_compare(mb, data0)) { ut->err = EBADMSG; break; } if (!sa_cmp(src, &ut->srv, SA_ALL)) { ut->err = EPROTO; break; } break; default: ut->err = ERANGE; break; } if (ut->tindex >= 1) re_cancel(); } /* Echo server */ static void udp_recv_server(const struct sa *src, struct mbuf *mb, void *arg) { struct udp_test *ut = arg; int err; err = udp_send(ut->uss, src, mb); if (err) ut->err = err; /* Receive a UDP Datagram on this UDP socket */ udp_recv_packet(ut->usc, &ut->srv, mb); } static bool udp_helper_send(int *err, struct sa *dst, struct mbuf *mb, void *arg) { struct udp_test *ut = arg; const size_t pos = mb->pos; if (!sa_cmp(dst, &ut->srv, SA_ALL)) { *err = EPROTO; return false; } if (!mbuf_compare(mb, data0)) { *err = EBADMSG; return false; } /* Append a fake protocol trailer */ mb->pos = mb->end; *err = mbuf_write_str(mb, "ABCD"); mb->pos = pos; return false; } static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg) { struct udp_test *ut = arg; if (!sa_cmp(src, &ut->srv, SA_ALL)) ut->err = EPROTO; mb->end -= 4; if (!mbuf_compare(mb, data0)) ut->err = EBADMSG; return false; } static int test_udp_param(const char *addr, const char *mcast) { struct udp_sock *uss2; struct udp_test *ut; struct sa group; int layer = 0; int err; ut = mem_zalloc(sizeof(*ut), destructor); if (!ut) return ENOMEM; err = sa_set_str(&ut->cli, addr, 0); err |= sa_set_str(&ut->srv, addr, 0); if (err) goto out; err = udp_listen(&ut->usc, &ut->cli, udp_recv_client, ut); err |= udp_listen(&ut->uss, &ut->srv, udp_recv_server, ut); if (err) goto out; if (mcast) { sa_set_str(&group, mcast, 0); err = udp_multicast_join(ut->usc, &group); TEST_ERR(err); err = udp_multicast_join(ut->uss, &group); TEST_ERR(err); } udp_rxsz_set(ut->usc, 65536); udp_rxsz_set(ut->uss, 65536); err = udp_sockbuf_set(ut->usc, 65536); TEST_ERR(err); err = udp_sockbuf_set(ut->uss, 65536); TEST_ERR(err); udp_rxbuf_presz_set(ut->uss, 16); err = udp_local_get(ut->usc, &ut->cli); err |= udp_local_get(ut->uss, &ut->srv); if (err) goto out; TEST_ASSERT(NULL == udp_helper_find(ut->usc, layer)); err = udp_register_helper(&ut->uh, ut->usc, layer, udp_helper_send, udp_helper_recv, ut); if (err) goto out; TEST_ASSERT(NULL != udp_helper_find(ut->usc, layer)); /* expect failure */ if (!udp_listen(&uss2, &ut->srv, udp_recv_client, ut)) { err = EBUSY; goto out; } /* Send from connected client UDP socket */ err = udp_connect(ut->usc, &ut->srv); if (err) goto out; /* Start test */ err = send_data(ut->usc, &ut->srv, data0); if (err) goto out; err = re_main_timeout(100); if (err) goto out; if (ut->err) err = ut->err; if (mcast) { udp_multicast_leave(ut->usc, &group); udp_multicast_leave(ut->uss, &group); } out: mem_deref(ut); return err; } int test_udp(void) { int err = test_udp_param("127.0.0.1", NULL); TEST_ERR(err); err = test_udp_param("127.0.0.1", "224.0.1.194"); TEST_ERR(err); if (test_ipv6_supported()) { err = test_udp_param("::1", NULL); TEST_ERR(err); } out: return err; } #if !defined(WIN32) static int udp_tos(const char *addr) { struct udp_test *ut; int layer = 0; int err; ut = mem_zalloc(sizeof(*ut), destructor); if (!ut) return ENOMEM; err = sa_set_str(&ut->cli, addr, 0); err |= sa_set_str(&ut->srv, addr, 0); TEST_ERR(err); err = udp_listen(&ut->usc, &ut->cli, udp_recv_client, ut); err |= udp_listen(&ut->uss, &ut->srv, udp_recv_server, ut); TEST_ERR(err); err = udp_settos(ut->usc, 184); err |= udp_settos(ut->uss, 120); TEST_ERR(err); err = udp_local_get(ut->usc, &ut->cli); err |= udp_local_get(ut->uss, &ut->srv); TEST_ERR(err); err = udp_register_helper(&ut->uh, ut->usc, layer, udp_helper_send, udp_helper_recv, ut); TEST_ERR(err); /* Send from connected client UDP socket */ err = udp_connect(ut->usc, &ut->srv); TEST_ERR(err); /* Start test */ err = send_data(ut->usc, &ut->srv, data0); TEST_ERR(err); err = re_main_timeout(100); TEST_ERR(err); if (ut->err) err = ut->err; out: mem_deref(ut); return err; } int test_udp_tos(void) { int err; err = udp_tos("127.0.0.1"); TEST_ERR(err); if (test_ipv6_supported()) { err = udp_tos("::1"); TEST_ERR(err); } out: return err; } #else /* Outcome of the TOS test on Windows would be dependent on the * DisableUserTOSSetting Windows registry setting. */ int test_udp_tos(void) { return 0; } #endif ================================================ FILE: test/unixsock.c ================================================ /** * @file src/unixsock.c Unix domain sockets * * Copyright (C) 2022 Sebastian Reimers */ #ifdef HAVE_UNISTD_H #include #endif #include #include "test.h" #ifdef WIN32 #define unlink _unlink #endif #define DEBUG_MODULE "unixsock" #define DEBUG_LEVEL 5 #include #if HAVE_UNIXSOCK static void http_req_handler(struct http_conn *conn, const struct http_msg *msg, void *arg) { (void)conn; (void)msg; (void)arg; } #endif int test_unixsock(void) { #if HAVE_UNIXSOCK struct sa srv; re_sock_t fd = RE_BAD_SOCK; struct http_sock *sock; int err; char filename[32]; char socket[128]; rand_str(filename, sizeof(filename)); re_snprintf(socket, sizeof(socket), "unix:http_%s.sock", filename); err = sa_set_str(&srv, socket, 0); TEST_ERR(err); err = unixsock_listen_fd(&fd, &srv); TEST_ERR(err); err = http_listen_fd(&sock, fd, http_req_handler, NULL); TEST_ERR(err); mem_deref(sock); err = unlink(&socket[5]); TEST_ERR(err); int error = unixsock_listen_fd(NULL, NULL); ASSERT_EQ(EINVAL, error); re_sock_t fd_dummy = RE_BAD_SOCK; struct sa sa_dummy; sa_set_str(&sa_dummy, "1.2.3.4", 1234); error = unixsock_listen_fd(&fd_dummy, &sa_dummy); ASSERT_EQ(EINVAL, error); out: if (err) (void)unlink(&socket[5]); return err; #else int err = unixsock_listen_fd(NULL, NULL); TEST_EQUALS(ENOTSUP, err); return 0; out: return err; #endif } ================================================ FILE: test/uri.c ================================================ /** * @file uri.c URI Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "testuri" #define DEBUG_LEVEL 5 #include int test_uri(void) { const char *uriv[] = { "sip:user@host:5060;transport=udp", "sip:user@host:5060", "sip:host:5060", "sip:host", "sip:user@81.187.91.2:28481", "sip:user@81.187.91.2:28481", "sip:81.187.91.2:28481", "sips:81.187.91.2:28481", "sip:192.168.43.83:443/wss/;transport=wss", /* RFC 3261 */ "sip:alice@atlanta.com", "sip:alice@atlanta.com;transport=tcp", "sips:alice@atlanta.com?subject=project%20&priority=urgent", "sip:+1-212-555-1212@gateway.com;user=phone", "sips:1212@gateway.com", "sip:alice@192.0.2.4", "sip:atlanta.com;method=REGISTER?to=alice%40atlanta.com", "sip:alice;day=tuesday@atlanta.com", /* IPv6 */ "sip:[::1];transport=udp", "sip:[::1]:1234;transport=udp", "sip:user@[::1];transport=udp", "sip:user@[::1]:1234;transport=udp", "sip:user@[::1]:1234;transport=udp?foo=bar", /* draft-ietf-sipping-ipv6-torture-tests-00 */ "sip:[2001:db8::10]", "sip:[2001:db8::10:5070]", "sip:[2001:db8::10]:5070", "sip:user@[2001:db8::10]", }; struct uri uri; struct mbuf mb; int err = EINVAL; size_t i; mbuf_init(&mb); for (i=0; i") } }; struct mbuf mbe, mbd; int err = EINVAL; size_t i; mbuf_init(&mbd); mbuf_init(&mbe); for (i=0; i #include #include #include "test.h" #define DEBUG_MODULE "vid" #define DEBUG_LEVEL 5 #include static const enum vidfmt fmtv[VID_FMT_N] = { VID_FMT_YUV420P, VID_FMT_YUYV422, VID_FMT_UYVY422, VID_FMT_RGB32, VID_FMT_ARGB, VID_FMT_NV12, VID_FMT_NV21, VID_FMT_YUV444P, VID_FMT_YUV422P, }; static int test_vidsz_cmp(void) { struct vidsz a = {64, 64}, b = {64, 64}, c = {32, 32}; int err = 0; TEST_ASSERT(!vidsz_cmp(NULL, NULL)); TEST_ASSERT( vidsz_cmp(&a, &a)); TEST_ASSERT( vidsz_cmp(&a, &b)); TEST_ASSERT(!vidsz_cmp(&a, &c)); out: return err; } static int test_vidrect_cmp(void) { struct vidrect a = {10, 10, 30, 30}; struct vidrect b = {10, 10, 30, 30}; struct vidrect c = {10, 10, 40, 40}; struct vidrect d = {20, 20, 30, 30}; int err = 0; TEST_ASSERT(!vidrect_cmp(NULL, NULL)); TEST_ASSERT( vidrect_cmp(&a, &a)); TEST_ASSERT( vidrect_cmp(&a, &b)); TEST_ASSERT(!vidrect_cmp(&a, &c)); TEST_ASSERT(!vidrect_cmp(&a, &d)); out: return err; } static int test_vidframe_size(void) { static const struct vidsz vidsz = {32, 32}; size_t i; int err = 0; for (i=0; ilinesize[0]); TEST_ASSERT(vidsz_cmp(&vidsz, &vf->size)); TEST_EQUALS(fmtv[i], vf->fmt); vf = mem_deref(vf); } out: mem_deref(vf); return err; } /* * Create one RGB32 pixel in native endianness */ #define RGB32(r, g, b) (r)<<16 | (g)<<8 | (b) /* * Test a RGB32 Video-frame with 2 x 2 pixels and 3 RGB pixel * * .--+---- * |RG| * |B | * +--+---- * | * | */ static int test_vidframe_rgb32_2x2_red(void) { struct vidframe vf; struct vidsz sz = {2, 2}; uint8_t buf[2*4 + 2*4]; const uint32_t pix[2][2] = { { RGB32(255U, 0, 0), RGB32(0, 255U, 0) }, { RGB32(0, 0, 255U), RGB32(0, 0, 0) } }; int err = 0; memset(buf, 0, sizeof(buf)); vidframe_init_buf(&vf, VID_FMT_RGB32, &sz, buf); TEST_EQUALS(buf, vf.data[0]); TEST_EQUALS(NULL, vf.data[1]); TEST_EQUALS(NULL, vf.data[2]); TEST_EQUALS(NULL, vf.data[3]); TEST_EQUALS(8, vf.linesize[0]); TEST_EQUALS(0, vf.linesize[1]); TEST_EQUALS(0, vf.linesize[2]); TEST_EQUALS(0, vf.linesize[3]); TEST_EQUALS(2, vf.size.w); TEST_EQUALS(2, vf.size.h); TEST_EQUALS(VID_FMT_RGB32, vf.fmt); vidframe_draw_point(&vf, 0, 0, 255, 0, 0); vidframe_draw_point(&vf, 1, 0, 0, 255, 0); vidframe_draw_point(&vf, 0, 1, 0, 0, 255); vidframe_draw_point(&vf, 1, 1, 0, 0, 0); TEST_MEMCMP(pix[0], sizeof(pix[0]), vf.data[0], 8); TEST_MEMCMP(pix[1], sizeof(pix[1]), vf.data[0] + vf.linesize[0], 8); out: return err; } static int test_vidframe_yuv_2x2_white(enum vidfmt fmt, unsigned chroma) { struct vidframe *vf; struct vidsz sz = {2, 2}; const uint8_t ypix[4] = {235, 235, 235, 235}; const uint8_t uvpix[4] = {128, 128, 128, 128}; const unsigned chroma_sq = chroma * chroma; int err; err = vidframe_alloc(&vf, fmt, &sz); if (err) return err; vidframe_fill(vf, 255, 255, 255); TEST_NOT_EQUALS(NULL, vf->data[0]); TEST_NOT_EQUALS(NULL, vf->data[1]); TEST_NOT_EQUALS(NULL, vf->data[2]); TEST_EQUALS(NULL, vf->data[3]); TEST_EQUALS(2, vf->linesize[0]); TEST_EQUALS(chroma, vf->linesize[1]); TEST_EQUALS(chroma, vf->linesize[2]); TEST_EQUALS(0, vf->linesize[3]); TEST_EQUALS(2, vf->size.w); TEST_EQUALS(2, vf->size.h); TEST_EQUALS(fmt, vf->fmt); TEST_ASSERT(chroma_sq <= sizeof(uvpix)); TEST_MEMCMP(ypix, sizeof(ypix), vf->data[0], 4); TEST_MEMCMP(uvpix, chroma_sq, vf->data[1], chroma_sq); TEST_MEMCMP(uvpix, chroma_sq, vf->data[2], chroma_sq); out: mem_deref(vf); return err; } static int test_vid_draw(void) { static const struct vidsz vidsz = {320, 240}; struct vidframe *vf = NULL, *vf2 = NULL; int err = 0; static const enum vidfmt drawfmtv[] = { VID_FMT_YUV420P, VID_FMT_NV12, VID_FMT_NV21, VID_FMT_YUV444P, VID_FMT_YUV422P, }; for (size_t i=0; ifmt == VID_FMT_YUV422P) { ASSERT_EQ(320, vf->linesize[0]); ASSERT_EQ(160, vf->linesize[1]); ASSERT_EQ(160, vf->linesize[2]); ASSERT_EQ( 0, vf->linesize[3]); } for (unsigned x=0; x #include #include #include "test.h" #define DEBUG_MODULE "vidconv" #define DEBUG_LEVEL 5 #include /* * http://en.wikipedia.org/wiki/YCbCr * * ITU-R BT.601 conversion * * Digital YCbCr can derived from digital R'dG'dB'd * (8 bits per sample, each using the full range with * zero representing black and 255 representing white) * according to the following equations: */ #if 0 static double rgb2y_ref(int r, int g, int b) { return 16.0 + (65.738*r)/256 + (129.057*g)/256 + (25.064*b)/256; } static double rgb2u_ref(int r, int g, int b) { return 128 - 37.945*r/256 - 74.494*g/256 + 112.439*b/256; } static double rgb2v_ref(int r, int g, int b) { return 128 + 112.439*r/256 - 94.154*g/256 - 18.285*b/256; } #endif #define CLIP(X) ( (X) > 255 ? 255 : (X) < 0 ? 0 : X) /* RGB -> YUV */ #define RGB2Y(R, G, B) \ CLIP(( ( 66 * (R) + 129 * (G) + 25 * (B) + 128) >> 8) + 16) #define RGB2U(R, G, B) \ CLIP(( ( -38 * (R) - 74 * (G) + 112 * (B) + 128) >> 8) + 128) #define RGB2V(R, G, B) \ CLIP(( ( 112 * (R) - 94 * (G) - 18 * (B) + 128) >> 8) + 128) #define test_vid_rgb2yuv_color(r, g, b) \ \ TEST_EQUALS(RGB2Y(r, g, b), rgb2y(r, g, b)); \ TEST_EQUALS(RGB2U(r, g, b), rgb2u(r, g, b)); \ TEST_EQUALS(RGB2V(r, g, b), rgb2v(r, g, b)); static int test_vid_rgb2yuv(void) { int r, g, b; int err = 0; /* combine 2 color components */ for (r=0; r<256; r++) { for (g=0; g<256; g++) { test_vid_rgb2yuv_color(r, g, 0); } } for (r=0; r<256; r++) { for (b=0; b<256; b++) { test_vid_rgb2yuv_color(r, 0, b); } } for (g=0; g<256; g++) { for (b=0; b<256; b++) { test_vid_rgb2yuv_color(0, g, b); } } out: return err; } static bool vidframe_cmp(const struct vidframe *a, const struct vidframe *b) { int i; if (!a || !b) return false; if (a->fmt != b->fmt) return false; for (i=0; i<4; i++) { if (a->linesize[i] != b->linesize[i]) return false; if (!a->data[i] != !b->data[i]) return false; if (a->data[i] && b->data[i]) { if (memcmp(a->data[i], b->data[i], a->linesize[i])) return false; } } return vidsz_cmp(&a->size, &b->size); } static void vidframe_dump(const struct vidframe *f) { int i; if (!f) return; (void)re_printf("vidframe %u x %u:\n", f->size.w, f->size.h); for (i=0; i<4; i++) { if (f->linesize[i] && f->data[i]) { (void)re_printf("%d: %u bytes [%w]\n", i, f->linesize[i], f->data[i], (size_t)min(f->linesize[i], 16)); } } } static void write_pattern(uint8_t *buf, size_t len) { size_t i; for (i=0; idata[i]) write_pattern(f0->data[i], f0->linesize[i]); } vidconv(f1, f0, &rect1); vidconv(f2, f1, &rect2); if (!vidframe_cmp(f2, f0)) { vidframe_dump(f0); vidframe_dump(f2); err = EBADMSG; goto out; } out: mem_deref(f2); mem_deref(f1); mem_deref(f0); return err; } /* * verify that pixel conversion between different planar and packed * pixel formats is working */ int test_vidconv_pixel_formats(void) { struct plane { size_t sz; const char *data; }; static const struct test { enum vidfmt src_fmt; struct plane src_planev[3]; enum vidfmt dst_fmt; struct plane dst_planev[3]; } testv [] = { /* UYVY422 to YUV420P */ { VID_FMT_UYVY422, { {32, "\x20\x00\x30\x01" "\x21\x02\x31\x03" "\x20\x04\x30\x05" "\x21\x06\x31\x07" "\x22\x08\x32\x09" "\x23\x0a\x33\x0b" "\x22\x0c\x32\x0d" "\x23\x0e\x33\x0f"}, {0,0}, {0,0} }, VID_FMT_YUV420P, { {16, "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"}, {4, "\x20\x21\x22\x23"}, {4, "\x30\x31\x32\x33"} }, }, /* NV12 to YUV420P */ { VID_FMT_NV12, { {16, "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"}, {8, "\x20\x30\x21\x31\x22\x32\x23\x33"}, {0,0} }, VID_FMT_YUV420P, { {16, "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"}, {4, "\x20\x21\x22\x23"}, {4, "\x30\x31\x32\x33"} }, }, /* YUV420P to NV12 */ { VID_FMT_YUV420P, { {16, "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"}, {4, "\x20\x21\x22\x23"}, {4, "\x30\x31\x32\x33"} }, VID_FMT_NV12, { {16, "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"}, {8, "\x20\x30\x21\x31\x22\x32\x23\x33"}, {0,0} }, }, #if 0 /* RGB32 to YUV444P */ { VID_FMT_RGB32, { { (16*4), "\x00\x00\x00\x00" "\x00\x00\x00\x00" /* black */ "\xff\x00\x00\x00" "\xff\x00\x00\x00" /* red */ "\x00\xff\x00\x00" "\x00\xff\x00\x00" /* green */ "\x00\x00\xff\x00" "\x00\x00\xff\x00" /* blue */ "\xff\x00\xff\x00" "\xff\x00\xff\x00" "\x00\xff\xff\x00" "\x00\xff\xff\x00" "\xff\xff\x00\x00" "\xff\xff\x00\x00" "\xff\xff\xff\xff" "\xff\xff\xff\xff"},/* white */ {0, ""}, {0, ""} }, VID_FMT_YUV444P, { {16, "\x10\x10\x29\x29" "\x90\x90\x52\x52" "\x6b\x6b\xd2\xd2" "\xa9\xa9\xeb\xeb"}, {16, "\x80\x80\xf0\xf0" "\x36\x36\x5a\x5a" "\xca\xca\x10\x10" "\xa6\xa6\x80\x80"}, {16, "\x80\x80\x6e\x6e" "\x22\x22\xf0\xf0" "\xde\xde\x92\x92" "\x10\x10\x80\x80"}, }, }, #endif }; struct vidframe *fsrc = NULL, *fdst = NULL; const struct vidsz sz = {4, 4}; unsigned i, p; int err = 0; for (i=0; isrc_fmt), vidfmt_name(test->dst_fmt)); #endif err |= vidframe_alloc(&fsrc, test->src_fmt, &sz); err |= vidframe_alloc(&fdst, test->dst_fmt, &sz); if (err) goto out; for (p=0; p<3; p++) { if (test->src_planev[p].sz) { memcpy(fsrc->data[p], test->src_planev[p].data, test->src_planev[p].sz); } } vidconv(fdst, fsrc, 0); for (p=0; p<3; p++) { size_t size = test->dst_planev[p].sz; if (!test->dst_planev[p].data) continue; TEST_MEMCMP(test->dst_planev[p].data, test->dst_planev[p].sz, fdst->data[p], size); } fdst = mem_deref(fdst); fsrc = mem_deref(fsrc); } out: mem_deref(fsrc); mem_deref(fdst); return err; } static int test_vidconv_center(void) { int err = 0; struct vidframe *dst = NULL; struct vidframe *src = NULL; struct test { struct vidrect r; struct vidsz src_sz; struct vidsz dst_sz; } testv[] = { {.r = {.x = 0, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 320, .h = 180}, .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 180, .h = 320}, .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 1920, .h = 1080}, .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 1080, .h = 1920}, .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 640, .h = 720}, .src_sz = {.w = 1920, .h = 1080}, .dst_sz = {.w = 1280, .h = 720}}, {.r = {.x = 960, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 1024, .h = 768}, /* 4:3 */ .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 320, .h = 320}, /* square */ .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 960, .h = 1080}, .src_sz = {.w = 1078, .h = 1080}, /* yoffs underflow */ .dst_sz = {.w = 1920, .h = 1080}}, {.r = {.x = 0, .y = 0, .w = 960, .h = 900}, .src_sz = {.w = 1080, .h = 1080}, /* xoffs underflow */ .dst_sz = {.w = 1920, .h = 1296}}, }; for (size_t i = 0; i < RE_ARRAY_SIZE(testv); i++) { struct test *test = &testv[i]; err = vidframe_alloc(&src, VID_FMT_YUV420P, &test->src_sz); err |= vidframe_alloc(&dst, VID_FMT_YUV420P, &test->dst_sz); TEST_ERR(err); vidconv_center(dst, src, &test->r); src = mem_deref(src); dst = mem_deref(dst); } out: src = mem_deref(src); dst = mem_deref(dst); return err; } int test_vidconv(void) { int err; err = test_vid_rgb2yuv(); TEST_ERR(err); err = test_vidconv_center(); TEST_ERR(err); out: return err; } int test_vidconv_scaling(void) { int err; err = test_vidconv_scaling_base(VID_FMT_YUV420P); TEST_ERR(err); err = test_vidconv_scaling_base(VID_FMT_NV12); TEST_ERR(err); out: return err; } ================================================ FILE: test/websock.c ================================================ /** * @file websock.c Websockets Testcode * * Copyright (C) 2010 Creytiv.com */ #include #include #include "test.h" #define DEBUG_MODULE "test_websock" #define DEBUG_LEVEL 5 #include struct test { struct websock *ws; struct websock_conn *wc_cli; struct websock_conn *wc_srv; const char *proto; uint32_t n_estab_cli; uint32_t n_recv_cli; uint32_t n_recv_srv; int err; }; static const char test_payload[] = "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef" "0123456789abcdef"; static const char custom_useragent[] = "Retest v0.1"; static void abort_test(struct test *t, int err) { t->err = err; re_cancel(); } static void done(struct test *t) { t->wc_cli = mem_deref(t->wc_cli); t->wc_srv = mem_deref(t->wc_srv); websock_shutdown(t->ws); } static void srv_websock_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb, void *arg) { struct test *test = arg; int err; test->n_recv_srv++; /* ECHO */ err = websock_send(test->wc_srv, hdr->opcode, "%b", mbuf_buf(mb), mbuf_get_left(mb)); if (err) abort_test(test, err); } static void srv_websock_close_handler(int err, void *arg) { struct test *test = arg; (void)test; (void)err; } static void websock_shutdown_handler(void *arg) { abort_test(arg, 0); } static void http_req_handler(struct http_conn *conn, const struct http_msg *msg, void *arg) { struct test *test = arg; int err; TEST_ASSERT(http_msg_hdr_has_value(msg, HTTP_HDR_USER_AGENT, custom_useragent)); if (test->proto) { TEST_ASSERT(http_msg_xhdr_has_value(msg, "Sec-WebSocket-Protocol", test->proto)); } unsigned kaint = 1; if (test->proto) { err = websock_accept_proto(&test->wc_srv, test->proto, test->ws, conn, msg, kaint, srv_websock_recv_handler, srv_websock_close_handler, test); } else { err = websock_accept(&test->wc_srv, test->ws, conn, msg, kaint, srv_websock_recv_handler, srv_websock_close_handler, test); } out: if (err) abort_test(test, err); } static void cli_websock_estab_handler(void *arg) { struct test *test = arg; int err; ASSERT_TRUE(NULL != websock_tcp(test->wc_cli)); test->n_estab_cli++; err = websock_send(test->wc_cli, WEBSOCK_TEXT, test_payload); out: if (err) abort_test(test, err); } static void cli_websock_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb, void *arg) { struct test *test = arg; int err = 0; test->n_recv_cli++; TEST_EQUALS(WEBSOCK_TEXT, hdr->opcode); TEST_STRCMP(test_payload, strlen(test_payload), mbuf_buf(mb), mbuf_get_left(mb)); done(test); out: if (err) abort_test(test, err); } static void cli_websock_close_handler(int err, void *arg) { struct test *test = arg; (void)test; (void)err; /* translate error code */ if (err) { abort_test(test, ENOMEM); } } static int test_websock_loop(const char *proto) { struct http_sock *httpsock = NULL; struct http_cli *http_cli = NULL; struct dnsc *dnsc = NULL; struct sa srv, dns; struct test test; char uri[256]; int err = 0; memset(&test, 0, sizeof(test)); test.proto = proto; err |= sa_set_str(&srv, "127.0.0.1", 0); err |= sa_set_str(&dns, "127.0.0.1", 53); /* note: unused */ if (err) goto out; err = http_listen(&httpsock, &srv, http_req_handler, &test); if (err) goto out; err = tcp_sock_local_get(http_sock_tcp(httpsock), &srv); if (err) goto out; err = dnsc_alloc(&dnsc, NULL, &dns, 1); if (err) goto out; err = http_client_alloc(&http_cli, dnsc); if (err) goto out; err = websock_alloc(&test.ws, websock_shutdown_handler, &test); if (err) goto out; (void)re_snprintf(uri, sizeof(uri), "http://127.0.0.1:%u/", sa_port(&srv)); unsigned kaint = 1; if (proto) { err = websock_connect_proto(&test.wc_cli, proto, test.ws, http_cli, uri, kaint, cli_websock_estab_handler, cli_websock_recv_handler, cli_websock_close_handler, &test, "User-Agent: %s\r\n", custom_useragent); } else { err = websock_connect(&test.wc_cli, test.ws, http_cli, uri, kaint, cli_websock_estab_handler, cli_websock_recv_handler, cli_websock_close_handler, &test, "User-Agent: %s\r\n", custom_useragent); } if (err) goto out; err = re_main_timeout(500); if (err) goto out; if (test.err) { err = test.err; goto out; } /* verify results after traffic is successfully done */ TEST_EQUALS(1, test.n_estab_cli); TEST_EQUALS(1, test.n_recv_cli); TEST_EQUALS(1, test.n_recv_srv); out: mem_deref(httpsock); mem_deref(test.wc_cli); mem_deref(test.ws); mem_deref(test.wc_srv); mem_deref(http_cli); mem_deref(dnsc); return err; } int test_websock(void) { int err = 0; err = test_websock_loop(NULL); TEST_ERR(err); err = test_websock_loop("test"); TEST_ERR(err); out: return err; } ================================================ FILE: tools/genfir.py ================================================ #!/usr/bin/python # # Copyright (C) 2025 Alfred E. Heggestad # ''' FIR filter design and generate C-table ''' import scipy.signal TAPS = 31 CUTOFF = 8000.0 # Hz SRATE = 16000.0 # Hz cutoff = CUTOFF / SRATE coeffs = scipy.signal.firwin(TAPS, cutoff) print("/*") print(" * FIR filter with cutoff %dHz, samplerate %dHz" % (CUTOFF, SRATE)) print(" */") print("static const int16_t fir_lowpass[%d] = {" % (TAPS)) i = 0 for c in coeffs: v = int(c * 32768.0) print(" %5d," % (v), end="") i += 1 if not i % 8: print("") print("") print("};")